diff --git a/web/packages/design/src/theme/themes/bblpTheme.ts b/web/packages/design/src/theme/themes/bblpTheme.ts index 0ead5373bcb06..4c40be56e23b9 100644 --- a/web/packages/design/src/theme/themes/bblpTheme.ts +++ b/web/packages/design/src/theme/themes/bblpTheme.ts @@ -102,6 +102,22 @@ const colors: ThemeColors = { 'rgba(0, 162, 35, 0.18)', 'rgba(0, 162, 35, 0.25)', ], + // TODO rudream: update bblp interactive tonal colors. + danger: [ + 'rgba(255, 98, 87, 0.1)', + 'rgba(255, 98, 87, 0.18)', + 'rgba(255, 98, 87, 0.25)', + ], + alert: [ + 'rgba(255, 171, 0, 0.1)', + 'rgba(255, 171, 0, 0.18)', + 'rgba(255, 171, 0, 0.25)', + ], + informational: [ + 'rgba(0, 158, 255, 0.1)', + 'rgba(0, 158, 255, 0.18)', + 'rgba(0, 158, 255, 0.25)', + ], neutral: neutralColors, }, }, @@ -176,6 +192,13 @@ const colors: ThemeColors = { active: '#D64D22', }, + // TODO rudream: update bblp accent colors. + accent: { + main: 'rgba(0, 158, 255, 1)', + hover: 'rgba(51, 177, 255, 1)', + active: 'rgba(102, 197, 255, 1)', + }, + notice: { background: '#282828', // elevated }, diff --git a/web/packages/design/src/theme/themes/darkTheme.ts b/web/packages/design/src/theme/themes/darkTheme.ts index 50ca78cc55c92..4ef002ce0c008 100644 --- a/web/packages/design/src/theme/themes/darkTheme.ts +++ b/web/packages/design/src/theme/themes/darkTheme.ts @@ -102,6 +102,21 @@ const colors: ThemeColors = { 'rgba(0, 191, 166, 0.18)', 'rgba(0, 191, 166, 0.25)', ], + danger: [ + 'rgba(255, 98, 87, 0.1)', + 'rgba(255, 98, 87, 0.18)', + 'rgba(255, 98, 87, 0.25)', + ], + alert: [ + 'rgba(255, 171, 0, 0.1)', + 'rgba(255, 171, 0, 0.18)', + 'rgba(255, 171, 0, 0.25)', + ], + informational: [ + 'rgba(0, 158, 255, 0.1)', + 'rgba(0, 158, 255, 0.18)', + 'rgba(0, 158, 255, 0.25)', + ], neutral: neutralColors, }, }, @@ -182,6 +197,12 @@ const colors: ThemeColors = { active: '#FFCD66', }, + accent: { + main: 'rgba(0, 158, 255, 1)', + hover: 'rgba(51, 177, 255, 1)', + active: 'rgba(102, 197, 255, 1)', + }, + notice: { background: '#344179', // elevated }, diff --git a/web/packages/design/src/theme/themes/lightTheme.ts b/web/packages/design/src/theme/themes/lightTheme.ts index bd5173f6f81c1..aad4e680aae51 100644 --- a/web/packages/design/src/theme/themes/lightTheme.ts +++ b/web/packages/design/src/theme/themes/lightTheme.ts @@ -101,6 +101,21 @@ const colors: ThemeColors = { 'rgba(0, 125, 107, 0.18)', 'rgba(0, 125, 107, 0.25)', ], + danger: [ + 'rgba(204, 55, 45, 0.1)', + 'rgba(204, 55, 45, 0.18)', + 'rgba(204, 55, 45, 0.25)', + ], + alert: [ + 'rgba(255, 171, 0, 0.1)', + 'rgba(255, 171, 0, 0.18)', + 'rgba(255, 171, 0, 0.25)', + ], + informational: [ + 'rgba(0, 115, 186, 0.1)', + 'rgba(0, 115, 186, 0.18)', + 'rgba(0, 115, 186, 0.25)', + ], neutral: neutralColors, }, }, @@ -181,6 +196,12 @@ const colors: ThemeColors = { active: '#996700', }, + accent: { + main: 'rgba(0, 115, 186, 1)', + hover: 'rgba(0, 92, 149, 1)', + active: 'rgba(0, 69, 112, 1)', + }, + notice: { background: blue[50], }, diff --git a/web/packages/design/src/theme/themes/types.ts b/web/packages/design/src/theme/themes/types.ts index f50af4564ee67..1c691ad68a106 100644 --- a/web/packages/design/src/theme/themes/types.ts +++ b/web/packages/design/src/theme/themes/types.ts @@ -60,6 +60,9 @@ export type ThemeColors = { primary: string[]; neutral: string[]; success: string[]; + danger: string[]; + alert: string[]; + informational: string[]; }; }; @@ -143,6 +146,12 @@ export type ThemeColors = { active: string; }; + accent: { + main: string; + hover: string; + active: string; + }; + notice: { background: string; }; diff --git a/web/packages/design/src/theme/typography.js b/web/packages/design/src/theme/typography.js index dc2a19a1a493a..44610a0b89e2c 100644 --- a/web/packages/design/src/theme/typography.js +++ b/web/packages/design/src/theme/typography.js @@ -85,6 +85,11 @@ const typography = { fontSize: '10px', lineHeight: '16px', }, + subtitle3: { + fontSize: '10px', + fontWeight: regular, + lineHeight: '14px', + }, }; export default typography; diff --git a/web/packages/teleport/src/Notifications/Notification.story.tsx b/web/packages/teleport/src/Notifications/Notification.story.tsx new file mode 100644 index 0000000000000..4eb726b49cfd7 --- /dev/null +++ b/web/packages/teleport/src/Notifications/Notification.story.tsx @@ -0,0 +1,185 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from 'react'; + +import { Flex } from 'design'; + +import { + NotificationSubKind, + Notification as NotificationType, +} from 'teleport/services/notifications'; + +import { Notification } from './Notification'; + +export default { + title: 'Teleport/Notifications', +}; + +export const Notifications = () => { + return ( + props.theme.colors.levels.surface}; + padding: 24px; + width: fit-content; + height: fit-content; + flex-direction: column; + gap: 24px; + `} + > + {mockNotifications.map(notification => { + return ( + + ); + })} + + ); +}; + +const mockNotifications: NotificationType[] = [ + { + id: '1', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestApproved, + createdDate: new Date(Date.now() - 30 * 1000), // 30 seconds ago + clicked: false, + labels: [ + { + name: 'requested-resources', + value: 'node-1,node-2,db-1,db-2,db-3', + }, + { name: 'reviewer', value: 'joe' }, + ], + }, + { + id: '2', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestApproved, + createdDate: new Date(Date.now() - 4 * 60 * 1000), // 4 minutes ago + clicked: false, + labels: [ + { + name: 'requested-role', + value: 'auditor', + }, + { name: 'reviewer', value: 'joe' }, + ], + }, + { + id: '3', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestDenied, + createdDate: new Date(Date.now() - 15 * 60 * 1000), // 15 minutes ago + clicked: false, + labels: [ + { + name: 'requested-role', + value: 'auditor', + }, + { name: 'reviewer', value: 'joe' }, + ], + }, + { + id: '4', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestPending, + createdDate: new Date(Date.now() - 3 * 60 * 60 * 1000), // 3 hours ago + clicked: true, + labels: [ + { + name: 'requested-resources', + value: 'db-2,node-5', + }, + { name: 'requester', value: 'bob' }, + ], + }, + { + id: '5', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestPending, + createdDate: new Date(Date.now() - 15 * 60 * 60 * 1000), // 15 hours ago + clicked: true, + labels: [ + { + name: 'requested-role', + value: 'intern', + }, + { name: 'requester', value: 'bob' }, + ], + }, + { + id: '6', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestNowAssumable, + createdDate: new Date(Date.now() - 24 * 60 * 60 * 1000), // 1 day ago + clicked: true, + labels: [ + { + name: 'requested-resources', + value: 'db-2,node-5', + }, + { name: 'requester', value: 'bob' }, + ], + }, + { + id: '7', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestNowAssumable, + createdDate: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // 3 days ago + clicked: false, + labels: [ + { + name: 'requested-resources', + value: 'node-5', + }, + { name: 'requester', value: 'bob' }, + ], + }, + { + id: '8', + title: '', + description: '', + subKind: NotificationSubKind.AccessRequestNowAssumable, + createdDate: new Date(Date.now() - 2 * 7 * 24 * 60 * 60 * 1000), // 2 weeks ago + clicked: true, + labels: [ + { + name: 'requested-role', + value: 'auditor', + }, + { name: 'requester', value: 'bob' }, + ], + }, + { + id: '9', + title: 'This is an example user-created warning notification', + description: 'This is the text content of a warning notification.', + subKind: NotificationSubKind.UserCreatedWarning, + createdDate: new Date(Date.now() - 93 * 24 * 60 * 60 * 1000), // 3 months ago + clicked: true, + labels: [], + }, +]; diff --git a/web/packages/teleport/src/Notifications/Notification.tsx b/web/packages/teleport/src/Notifications/Notification.tsx new file mode 100644 index 0000000000000..2fa7fcb2fa53e --- /dev/null +++ b/web/packages/teleport/src/Notifications/Notification.tsx @@ -0,0 +1,291 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { formatDistanceToNowStrict } from 'date-fns'; + +import * as Icons from 'design/Icon'; + +import Text from 'design/Text'; +import { ButtonSecondary } from 'design/Button'; +import { MenuIcon, MenuItem } from 'shared/components/MenuAction'; +import Dialog, { + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from 'design/Dialog'; +import { Theme } from 'design/theme/themes/types'; + +import { Notification as NotificationType } from 'teleport/services/notifications'; + +import { + NotificationContent, + notificationContentFactory, +} from './notificationContentFactory'; + +export function Notification({ + notification, +}: { + notification: NotificationType; +}) { + const content = notificationContentFactory(notification); + + // Whether to show the text content dialog. This is only ever used for user-created notifications which only contain informational text + // and don't redirect to any page. + const [showTextContentDialog, setShowTextContentDialog] = useState(false); + + let AccentIcon; + switch (content.type) { + case 'success': + case 'success-alt': + AccentIcon = Icons.Check; + break; + case 'informational': + AccentIcon = Icons.Info; + break; + case 'warning': + AccentIcon = Icons.Warning; + break; + case 'failure': + AccentIcon = Icons.Cross; + break; + } + + const formattedDate = formatDate(notification.createdDate); + + function onMarkAsClicked() { + // TODO rudream - add mark as clicked functionality + } + + function onHideNotification() { + // TODO rudream - add hide notification functionality + } + + function onNotificationClick() { + if (content.kind === 'text') { + setShowTextContentDialog(true); + return; + } + // TODO rudream - add notification redirect functionality + } + + return ( + <> + + + + + + + + + + + + {content.title} + {content.kind === 'redirect' && content.quickAction && ( + + {content.quickAction.buttonText} + + )} + + + {formattedDate} + + Mark as read + + Hide this notification + + + + + + {content.kind === 'text' && ( + + + {content.title} + + {content.textContent} + + setShowTextContentDialog(false)} + size="small" + > + Close + + + + )} + + ); +} + +// formatDate returns how long ago the provided date is in a readable and concise format, ie. "2h ago" +function formatDate(date: Date) { + let distance = formatDistanceToNowStrict(date); + + distance = distance + .replace(/seconds?/g, 's') + .replace(/minutes?/g, 'm') + .replace(/hours?/g, 'h') + .replace(/days?/g, 'd') + .replace(/months?/g, 'mo') + .replace(/years?/g, 'y') + .replace(' ', ''); + + return `${distance} ago`; +} + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: flex-start; + gap: ${props => props.theme.space[3]}px; + max-width: 400px; + padding: ${props => props.theme.space[3]}px; + border-radius: ${props => props.theme.radii[3]}px; + cursor: pointer; + + background: ${props => props.theme.colors.interactive.tonal.primary[0]}; + &:hover { + background: ${props => props.theme.colors.interactive.tonal.primary[1]}; + } + + ${props => + props.clicked && + ` + background: ${props.theme.colors.interactive.tonal.neutral[0]}; + &:hover { + background: ${props.theme.colors.interactive.tonal.neutral[1]}; + } + `} +`; + +const ContentContainer = styled.div` + display: flex; + justify-content: space-between; + gap: ${props => props.theme.space[2]}px; + width: 100%; +`; + +const ContentBody = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: ${props => props.theme.space[2]}px; +`; + +const SideContent = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: ${props => props.theme.space[2]}px; + align-items: center; + white-space: nowrap; +`; + +const GraphicContainer = styled.div` + height: 48px; + width: 48px; + position: relative; + flex-shrink: 0; +`; + +function getIconColors( + theme: Theme, + type: NotificationContent['type'] +): { + primary: string; + secondary: string; +} { + switch (type) { + case 'success': + return { + primary: theme.colors.success.main, + secondary: theme.colors.interactive.tonal.success[0], + }; + case 'success-alt': + return { + primary: theme.colors.accent.main, + secondary: theme.colors.interactive.tonal.informational[0], + }; + case 'informational': + return { + primary: theme.colors.brand, + secondary: theme.colors.interactive.tonal.primary[0], + }; + case `warning`: + return { + primary: theme.colors.warning.main, + secondary: theme.colors.interactive.tonal.alert[0], + }; + case 'failure': + return { + primary: theme.colors.error.main, + secondary: theme.colors.interactive.tonal.danger[0], + }; + } +} + +const MainIconContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + border-radius: ${props => props.theme.radii[3]}px; + position: absolute; + z-index: 1; + top: 0; + left: 0; + + border: ${props => props.theme.borders[1]}; + border-color: ${props => getIconColors(props.theme, props.type).primary}; + + background-color: ${props => + getIconColors(props.theme, props.type).secondary}; +`; + +const AccentIconContainer = styled.div` + height: 18px; + width: 18px; + display: flex; + align-items: center; + justify-content: center; + border-radius: ${props => props.theme.radii[2]}px; + position: absolute; + z-index: 2; + bottom: 0; + right: 0; + + background-color: ${props => getIconColors(props.theme, props.type).primary}; +`; diff --git a/web/packages/teleport/src/Notifications/notificationContentFactory.tsx b/web/packages/teleport/src/Notifications/notificationContentFactory.tsx new file mode 100644 index 0000000000000..fa2256e04bfc5 --- /dev/null +++ b/web/packages/teleport/src/Notifications/notificationContentFactory.tsx @@ -0,0 +1,231 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as Icons from 'design/Icon'; +import { IconProps } from 'design/Icon/Icon'; +import React from 'react'; + +import { + Notification as NotificationType, + NotificationSubKind, +} from 'teleport/services/notifications'; +import { Label } from 'teleport/types'; + +export function notificationContentFactory({ + subKind, + description, + labels, + ...notification +}: NotificationType): NotificationContent { + let notificationContent: NotificationContent; + + switch (subKind) { + case NotificationSubKind.DefaultInformational: + case NotificationSubKind.UserCreatedInformational: + notificationContent = { + kind: 'text', + title: notification.title, + textContent: description, + type: 'informational', + icon: Icons.Notification, + }; + break; + + case NotificationSubKind.DefaultWarning: + case NotificationSubKind.UserCreatedWarning: + notificationContent = { + kind: 'text', + title: notification.title, + textContent: description, + type: 'warning', + icon: Icons.Notification, + }; + break; + + case NotificationSubKind.AccessRequestApproved: { + let title; + + const reviewer = getLabelValue(labels, 'reviewer'); + const requestedResources = getLabelValue(labels, 'requested-resources'); + const numRequestedResources = requestedResources.length + ? requestedResources.split(',').length + : 0; + + // Check if it is a resource request or a role request. + if (numRequestedResources) { + title = `${reviewer} approved your access request for ${numRequestedResources} resource${ + numRequestedResources > 1 ? 's' : '' + }.`; + } else { + const requestedRole = getLabelValue(labels, 'requested-role'); + title = `${reviewer} approved your access request for the '${requestedRole}' role.`; + } + + notificationContent = { + kind: 'redirect', + title, + type: 'success', + icon: Icons.Users, + redirectRoute: '/', //TODO: rudream - handle enterprise routes + quickAction: { + onClick: () => null, //TODO: rudream - handle assuming roles from quick action button + buttonText: 'Assume Roles', + }, + }; + break; + } + + case NotificationSubKind.AccessRequestDenied: { + let title; + + const reviewer = getLabelValue(labels, 'reviewer'); + const requestedResources = getLabelValue(labels, 'requested-resources'); + const numRequestedResources = requestedResources.length + ? requestedResources.split(',').length + : 0; + + // Check if it is a resource request or a role request. + if (numRequestedResources) { + title = `${reviewer} denied your access request for ${numRequestedResources} resource${ + numRequestedResources > 1 ? 's' : '' + }.`; + } else { + const requestedRole = getLabelValue(labels, 'requested-role'); + title = `${reviewer} denied your access request for the '${requestedRole}' role.`; + } + + notificationContent = { + kind: 'redirect', + title, + type: 'failure', + icon: Icons.Users, + redirectRoute: '/', //TODO: rudream - handle enterprise routes + }; + break; + } + + case NotificationSubKind.AccessRequestPending: { + let title; + + const requester = getLabelValue(labels, 'requester'); + const requestedResources = getLabelValue(labels, 'requested-resources'); + const numRequestedResources = requestedResources.length + ? requestedResources.split(',').length + : 0; + + // Check if it is a resource request or a role request. + if (numRequestedResources) { + title = `${requester} requested access to ${numRequestedResources} resource${ + numRequestedResources > 1 ? 's' : '' + }.`; + } else { + const requestedRole = getLabelValue(labels, 'requested-role'); + title = `${requester} requested access to the '${requestedRole}' role.`; + } + + notificationContent = { + kind: 'redirect', + title, + type: 'informational', + icon: Icons.UserList, + redirectRoute: '/', //TODO: rudream - handle enterprise routes + }; + break; + } + + case NotificationSubKind.AccessRequestNowAssumable: { + let title; + let buttonText; + + const requestedResources = getLabelValue(labels, 'requested-resources'); + const numRequestedResources = requestedResources.length + ? requestedResources.split(',').length + : 0; + + // Check if it is a resource request or a role request. + if (numRequestedResources) { + if (numRequestedResources === 1) { + title = `"${requestedResources}" is now available to access.`; + } else { + title = `${numRequestedResources} resources are now available to access.`; + } + buttonText = 'Access Now'; + } else { + const requestedRole = getLabelValue(labels, 'requested-role'); + title = `"${requestedRole}" is now ready to assume.`; + buttonText = 'Assume Role'; + } + + notificationContent = { + kind: 'redirect', + title, + type: 'success-alt', + icon: Icons.Users, + redirectRoute: '/', //TODO: rudream - handle enterprise routes + quickAction: { + onClick: () => null, //TODO: rudream - handle assuming roles from quick action button + buttonText: buttonText, + }, + }; + break; + } + } + + return notificationContent; +} + +type NotificationContentBase = { + /** title is the title of the notification. */ + title: string; + /** type is the type of notification this is, this will determine the style of this notification (color and sub-icon). */ + type: 'success' | 'success-alt' | 'informational' | 'warning' | 'failure'; + /** icon is the icon to render for this notification. This should be an icon from `design/Icon`. */ + icon: React.FC; +}; + +/** For notifications that are clickable and redirect you to a page, and may also optionally include a quick action. */ +type NotificationContentRedirect = NotificationContentBase & { + kind: 'redirect'; + /** redirectRoute is the route the user should be redirected to when clicking the notification, if any. */ + redirectRoute: string; + quickAction?: { + /** onClick is what should be run when the user clicks on the quick action button */ + onClick: () => void; + /** buttonText is the text that should be shown on the quick action button */ + buttonText: string; + }; +}; + +/** For notifications that only contain text and are not interactive in any other way. This is used for user-created notifications. */ +type NotificationContentText = NotificationContentBase & { + kind: 'text'; + /** textContent is the text content of the notification, this is used for user-created notifications and will contain the text that will be shown in a modal upon clicking the notification. */ + textContent: string; +}; + +export type NotificationContent = + | NotificationContentRedirect + | NotificationContentText; + +// getLabelValue returns the value of a label for a given key. +function getLabelValue(labels: Label[], key: string): string { + const label = labels.find(label => { + return label.name === key; + }); + return label?.value || ''; +} diff --git a/web/packages/teleport/src/services/notifications/index.ts b/web/packages/teleport/src/services/notifications/index.ts new file mode 100644 index 0000000000000..ed965b13f72eb --- /dev/null +++ b/web/packages/teleport/src/services/notifications/index.ts @@ -0,0 +1,19 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from './types'; diff --git a/web/packages/teleport/src/services/notifications/types.ts b/web/packages/teleport/src/services/notifications/types.ts new file mode 100644 index 0000000000000..dafceead11c09 --- /dev/null +++ b/web/packages/teleport/src/services/notifications/types.ts @@ -0,0 +1,49 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Label } from 'teleport/types'; + +export type Notification = { + /** id is the uuid of this notification */ + id: string; + /* subKind is a string which represents which type of notification this is, ie. "access-request-approved" */ + subKind: NotificationSubKind; + /** createdDate is when the notification was created. */ + createdDate: Date; + /** clicked is whether this notification has been clicked on by this user. */ + clicked: boolean; + /** labels are this notification's labels, this is where the notification's metadata is stored.*/ + labels: Label[]; + /** title is the title of this notification. It is preferred to not use this and instead construct a title dynamically using metadata from the labels. */ + title: string; + /** description is the description of this notification. It is preferred to not use this and instead construct a description dynamically using metadata from the labels. */ + description: string; +}; + +/** NotificationSubKind is the subkind of notifications, these should be kept in sync with TBD (TODO: rudream - add backend counterpart location here) */ +export enum NotificationSubKind { + DefaultInformational = 'default-informational', + DefaultWarning = 'default-warning', + UserCreatedInformational = 'user-created-informational', + UserCreatedWarning = 'user-created-warning', + AccessRequestPending = 'access-request-pending', + AccessRequestApproved = 'access-request-approved', + AccessRequestDenied = 'access-request-denied', + /** AccessRequestNowAssumable is the notification for when an approved access request that was scheduled for a later date is now assumable. */ + AccessRequestNowAssumable = 'access-request-now-assumable', +}