mirror of https://github.com/rancher/dashboard.git
Add home page announcements
This commit is contained in:
parent
07fc4842e5
commit
56bcea43ab
|
|
@ -0,0 +1,124 @@
|
||||||
|
<script>
|
||||||
|
import { NotificationLevel } from '@shell/types/notifications';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HomePageDynamicContent',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
location: {
|
||||||
|
type: String,
|
||||||
|
default: 'banner'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
// Return the un-read hidden notifications for display on the home page
|
||||||
|
dynamicContent() {
|
||||||
|
let hiddenNotifications = this.$store.getters['notifications/all'].filter((n) => n.level === NotificationLevel.Hidden && !n.read);
|
||||||
|
|
||||||
|
hiddenNotifications = hiddenNotifications.filter((n) => n.data?.location === this.location);
|
||||||
|
|
||||||
|
return hiddenNotifications.length > 0 ? hiddenNotifications[0] : undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// Invoke action on either the primary or secondary buttons
|
||||||
|
// This can open a URL in a new tab OR navigate to an application route
|
||||||
|
action(action) {
|
||||||
|
if (action.target) {
|
||||||
|
window.open(action.target, '_blank');
|
||||||
|
} else if (action.route) {
|
||||||
|
try {
|
||||||
|
this.$router.push(action.route);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error navigating to route for the notification action', e); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Notification action must either specify a "target" or a "route"'); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
},
|
||||||
|
markRead(notification) {
|
||||||
|
this.$store.dispatch('notifications/markRead', notification.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="dynamicContent"
|
||||||
|
class="home-page-dynamic-content"
|
||||||
|
>
|
||||||
|
<div class="dc-content">
|
||||||
|
<div class="dc-title">
|
||||||
|
{{ dynamicContent.title }}
|
||||||
|
</div>
|
||||||
|
<div class="dc-message">
|
||||||
|
{{ dynamicContent.message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dc-actions">
|
||||||
|
<button
|
||||||
|
v-if="dynamicContent.primaryAction"
|
||||||
|
role="button"
|
||||||
|
class="btn btn-sm role-primary"
|
||||||
|
@click.stop.prevent="action(dynamicContent.primaryAction)"
|
||||||
|
>
|
||||||
|
{{ dynamicContent.primaryAction.label }}
|
||||||
|
</button>
|
||||||
|
<i
|
||||||
|
class="icon icon-close"
|
||||||
|
@click="markRead(dynamicContent)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.home-page-dynamic-content {
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.dc-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dc-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dc-message {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dc-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 8px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--primary);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -11,9 +11,11 @@ import {
|
||||||
RcDropdownSeparator,
|
RcDropdownSeparator,
|
||||||
RcDropdownTrigger
|
RcDropdownTrigger
|
||||||
} from '@components/RcDropdown';
|
} from '@components/RcDropdown';
|
||||||
|
import { NotificationLevel, Notification as NotificationType } from '@shell/types/notifications';
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const allNotifications = computed(() => store.getters['notifications/all']);
|
// We don't want any hidden notifications showing in the notification center (these are shown elsewhere, e.g. home page dynamic content announcements)
|
||||||
|
const allNotifications = computed(() => store.getters['notifications/all'].filter((n: NotificationType) => n.level !== NotificationLevel.Hidden));
|
||||||
const unreadLevelClass = computed(() => {
|
const unreadLevelClass = computed(() => {
|
||||||
return store.getters['notifications/unreadCount'] === 0 ? '' : 'unread';
|
return store.getters['notifications/unreadCount'] === 0 ? '' : 'unread';
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue
|
||||||
import { BadgeState } from '@components/BadgeState';
|
import { BadgeState } from '@components/BadgeState';
|
||||||
import CommunityLinks from '@shell/components/CommunityLinks.vue';
|
import CommunityLinks from '@shell/components/CommunityLinks.vue';
|
||||||
import SingleClusterInfo from '@shell/components/SingleClusterInfo.vue';
|
import SingleClusterInfo from '@shell/components/SingleClusterInfo.vue';
|
||||||
|
import HomePageDynamicContent from '@shell/components/HomePageDynamicContent.vue';
|
||||||
import { mapGetters, mapState } from 'vuex';
|
import { mapGetters, mapState } from 'vuex';
|
||||||
import { MANAGEMENT, CAPI, COUNT } from '@shell/config/types';
|
import { MANAGEMENT, CAPI, COUNT } from '@shell/config/types';
|
||||||
import { NAME as MANAGER } from '@shell/config/product/manager';
|
import { NAME as MANAGER } from '@shell/config/product/manager';
|
||||||
|
|
@ -47,6 +48,7 @@ export default defineComponent({
|
||||||
SingleClusterInfo,
|
SingleClusterInfo,
|
||||||
TabTitle,
|
TabTitle,
|
||||||
ResourceTable,
|
ResourceTable,
|
||||||
|
HomePageDynamicContent,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PageHeaderActions, Preset],
|
mixins: [PageHeaderActions, Preset],
|
||||||
|
|
@ -610,6 +612,7 @@ export default defineComponent({
|
||||||
pref-key="welcomeBanner"
|
pref-key="welcomeBanner"
|
||||||
data-testid="home-banner-graphic"
|
data-testid="home-banner-graphic"
|
||||||
/>
|
/>
|
||||||
|
<HomePageDynamicContent location="banner" />
|
||||||
<IndentedPanel class="mt-20 mb-20">
|
<IndentedPanel class="mt-20 mb-20">
|
||||||
<div class="row home-panels">
|
<div class="row home-panels">
|
||||||
<div class="col main-panel">
|
<div class="col main-panel">
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@ async function saveEncryptedNotification(getters: any, notification: Notificatio
|
||||||
primaryAction: notification.primaryAction,
|
primaryAction: notification.primaryAction,
|
||||||
secondaryAction: notification.secondaryAction,
|
secondaryAction: notification.secondaryAction,
|
||||||
preference: notification.preference,
|
preference: notification.preference,
|
||||||
handlerName: notification.handlerName
|
handlerName: notification.handlerName,
|
||||||
|
data: notification.data,
|
||||||
};
|
};
|
||||||
|
|
||||||
const localStorageKey = getters['localStorageKey'];
|
const localStorageKey = getters['localStorageKey'];
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export enum NotificationLevel {
|
||||||
Success, // eslint-disable-line no-unused-vars
|
Success, // eslint-disable-line no-unused-vars
|
||||||
Warning, // eslint-disable-line no-unused-vars
|
Warning, // eslint-disable-line no-unused-vars
|
||||||
Error, // eslint-disable-line no-unused-vars
|
Error, // eslint-disable-line no-unused-vars
|
||||||
|
Hidden, // eslint-disable-line no-unused-vars
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,6 +53,8 @@ export type EncryptedNotification = {
|
||||||
// Handler to be associated with this notification that can invoke additional behaviour when the notification changes
|
// Handler to be associated with this notification that can invoke additional behaviour when the notification changes
|
||||||
// This is the name of the handler (the handlers are added as extensions). Notifications are persisted in the store, so can't use functions.
|
// This is the name of the handler (the handlers are added as extensions). Notifications are persisted in the store, so can't use functions.
|
||||||
handlerName?: string;
|
handlerName?: string;
|
||||||
|
// Additional data to be stored with the notification (optional)
|
||||||
|
data?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,15 @@ import { DynamicContentAnnouncementHandlerName } from './notification-handler';
|
||||||
// Prefixes used in the notifications IDs created here
|
// Prefixes used in the notifications IDs created here
|
||||||
export const ANNOUNCEMENT_PREFIX = 'announcement-';
|
export const ANNOUNCEMENT_PREFIX = 'announcement-';
|
||||||
|
|
||||||
|
const TARGET_NOTIFICATION_CENTER = 'notification';
|
||||||
|
const TARGET_HOME_PAGE = 'homepage';
|
||||||
|
const ALLOWED_TARGETS = [TARGET_NOTIFICATION_CENTER, TARGET_HOME_PAGE];
|
||||||
|
|
||||||
const ALLOWED_NOTIFICATIONS: Record<string, NotificationLevel> = {
|
const ALLOWED_NOTIFICATIONS: Record<string, NotificationLevel> = {
|
||||||
announcement: NotificationLevel.Announcement,
|
announcement: NotificationLevel.Announcement,
|
||||||
info: NotificationLevel.Info,
|
info: NotificationLevel.Info,
|
||||||
warning: NotificationLevel.Warning,
|
warning: NotificationLevel.Warning,
|
||||||
|
homepage: NotificationLevel.Hidden,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,6 +54,7 @@ export async function processAnnouncements(context: Context, announcements: Anno
|
||||||
|
|
||||||
// Check type
|
// Check type
|
||||||
const targetSplit = announcement.target.split('/');
|
const targetSplit = announcement.target.split('/');
|
||||||
|
const target = targetSplit[0];
|
||||||
|
|
||||||
if (targetSplit[0] === 'notification') {
|
if (targetSplit[0] === 'notification') {
|
||||||
// Show a notification
|
// Show a notification
|
||||||
|
|
@ -85,6 +91,7 @@ export async function processAnnouncements(context: Context, announcements: Anno
|
||||||
title: announcement.title,
|
title: announcement.title,
|
||||||
message: announcement.message,
|
message: announcement.message,
|
||||||
handlerName: DynamicContentAnnouncementHandlerName,
|
handlerName: DynamicContentAnnouncementHandlerName,
|
||||||
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (announcement.cta?.primary) {
|
if (announcement.cta?.primary) {
|
||||||
|
|
@ -101,7 +108,7 @@ export async function processAnnouncements(context: Context, announcements: Anno
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Adding announcement with ID ${ id } (title: ${ announcement.title })`);
|
logger.info(`Adding announcement with ID ${ id } (title: ${ announcement.title }, target: ${ announcement.target })`);
|
||||||
|
|
||||||
await dispatch('notifications/add', notification);
|
await dispatch('notifications/add', notification);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,12 +108,18 @@ export type Announcement = {
|
||||||
target: string; // Where the announcement should be shown
|
target: string; // Where the announcement should be shown
|
||||||
version?: string; // Version or semver expression for when to show this announcement
|
version?: string; // Version or semver expression for when to show this announcement
|
||||||
audience?: 'admin' | 'all'; // Audience - show for just Admins or for all users
|
audience?: 'admin' | 'all'; // Audience - show for just Admins or for all users
|
||||||
|
icon?: string;
|
||||||
cta?: {
|
cta?: {
|
||||||
primary: CallToAction, // Must have a primary call to action, if we have a cta field
|
primary: CallToAction, // Must have a primary call to action, if we have a cta field
|
||||||
secondary?: CallToAction,
|
secondary?: CallToAction,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AnnouncementNotificationData = {
|
||||||
|
icon?: string;
|
||||||
|
location: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main type for the metadata that is retrieved from the dynamic content endpoint
|
* Main type for the metadata that is retrieved from the dynamic content endpoint
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue