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,
|
||||
RcDropdownTrigger
|
||||
} from '@components/RcDropdown';
|
||||
import { NotificationLevel, Notification as NotificationType } from '@shell/types/notifications';
|
||||
|
||||
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(() => {
|
||||
return store.getters['notifications/unreadCount'] === 0 ? '' : 'unread';
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue
|
|||
import { BadgeState } from '@components/BadgeState';
|
||||
import CommunityLinks from '@shell/components/CommunityLinks.vue';
|
||||
import SingleClusterInfo from '@shell/components/SingleClusterInfo.vue';
|
||||
import HomePageDynamicContent from '@shell/components/HomePageDynamicContent.vue';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import { MANAGEMENT, CAPI, COUNT } from '@shell/config/types';
|
||||
import { NAME as MANAGER } from '@shell/config/product/manager';
|
||||
|
|
@ -47,6 +48,7 @@ export default defineComponent({
|
|||
SingleClusterInfo,
|
||||
TabTitle,
|
||||
ResourceTable,
|
||||
HomePageDynamicContent,
|
||||
},
|
||||
|
||||
mixins: [PageHeaderActions, Preset],
|
||||
|
|
@ -610,6 +612,7 @@ export default defineComponent({
|
|||
pref-key="welcomeBanner"
|
||||
data-testid="home-banner-graphic"
|
||||
/>
|
||||
<HomePageDynamicContent location="banner" />
|
||||
<IndentedPanel class="mt-20 mb-20">
|
||||
<div class="row home-panels">
|
||||
<div class="col main-panel">
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ async function saveEncryptedNotification(getters: any, notification: Notificatio
|
|||
primaryAction: notification.primaryAction,
|
||||
secondaryAction: notification.secondaryAction,
|
||||
preference: notification.preference,
|
||||
handlerName: notification.handlerName
|
||||
handlerName: notification.handlerName,
|
||||
data: notification.data,
|
||||
};
|
||||
|
||||
const localStorageKey = getters['localStorageKey'];
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export enum NotificationLevel {
|
|||
Success, // eslint-disable-line no-unused-vars
|
||||
Warning, // 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
|
||||
// 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;
|
||||
// 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
|
||||
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> = {
|
||||
announcement: NotificationLevel.Announcement,
|
||||
info: NotificationLevel.Info,
|
||||
warning: NotificationLevel.Warning,
|
||||
homepage: NotificationLevel.Hidden,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -49,6 +54,7 @@ export async function processAnnouncements(context: Context, announcements: Anno
|
|||
|
||||
// Check type
|
||||
const targetSplit = announcement.target.split('/');
|
||||
const target = targetSplit[0];
|
||||
|
||||
if (targetSplit[0] === 'notification') {
|
||||
// Show a notification
|
||||
|
|
@ -85,6 +91,7 @@ export async function processAnnouncements(context: Context, announcements: Anno
|
|||
title: announcement.title,
|
||||
message: announcement.message,
|
||||
handlerName: DynamicContentAnnouncementHandlerName,
|
||||
data,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,12 +108,18 @@ export type Announcement = {
|
|||
target: string; // Where the announcement should be shown
|
||||
version?: string; // Version or semver expression for when to show this announcement
|
||||
audience?: 'admin' | 'all'; // Audience - show for just Admins or for all users
|
||||
icon?: string;
|
||||
cta?: {
|
||||
primary: CallToAction, // Must have a primary call to action, if we have a cta field
|
||||
secondary?: CallToAction,
|
||||
}
|
||||
};
|
||||
|
||||
export type AnnouncementNotificationData = {
|
||||
icon?: string;
|
||||
location: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main type for the metadata that is retrieved from the dynamic content endpoint
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue