|
|
|
@ -1,9 +1,9 @@ |
|
|
|
|
import { css, cx } from '@emotion/css'; |
|
|
|
|
import React, { useRef } from 'react'; |
|
|
|
|
import React, { useRef, useState } from 'react'; |
|
|
|
|
import { useEffectOnce } from 'react-use'; |
|
|
|
|
|
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data'; |
|
|
|
|
import { Button, Icon, useStyles2 } from '@grafana/ui'; |
|
|
|
|
import { Button, Checkbox, Icon, useStyles2 } from '@grafana/ui'; |
|
|
|
|
import { StoredNotificationItem } from 'app/core/components/AppNotifications/StoredNotificationItem'; |
|
|
|
|
import { |
|
|
|
|
clearAllNotifications, |
|
|
|
@ -17,6 +17,7 @@ import { useDispatch, useSelector } from 'app/types'; |
|
|
|
|
export function StoredNotifications() { |
|
|
|
|
const dispatch = useDispatch(); |
|
|
|
|
const notifications = useSelector((state) => selectWarningsAndErrors(state.appNotifications)); |
|
|
|
|
const [selectedNotificationIds, setSelectedNotificationIds] = useState<string[]>([]); |
|
|
|
|
const lastReadTimestamp = useRef(useSelector((state) => selectLastReadTimestamp(state.appNotifications))); |
|
|
|
|
const styles = useStyles2(getStyles); |
|
|
|
|
|
|
|
|
@ -24,14 +25,27 @@ export function StoredNotifications() { |
|
|
|
|
dispatch(readAllNotifications(Date.now())); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const onClearNotification = (id: string) => { |
|
|
|
|
const clearSelectedNotifications = () => { |
|
|
|
|
selectedNotificationIds.forEach((id) => { |
|
|
|
|
dispatch(clearNotification(id)); |
|
|
|
|
}); |
|
|
|
|
setSelectedNotificationIds([]); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const clearAllNotifs = () => { |
|
|
|
|
dispatch(clearAllNotifications()); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleCheckboxToggle = (id: string, isChecked: boolean) => { |
|
|
|
|
setSelectedNotificationIds((prevState) => { |
|
|
|
|
if (isChecked && !prevState.includes(id)) { |
|
|
|
|
return [...prevState, id]; |
|
|
|
|
} else { |
|
|
|
|
return prevState.filter((notificationId) => notificationId !== id); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (notifications.length === 0) { |
|
|
|
|
return ( |
|
|
|
|
<div className={styles.noNotifsWrapper}> |
|
|
|
@ -44,19 +58,28 @@ export function StoredNotifications() { |
|
|
|
|
return ( |
|
|
|
|
<div className={styles.wrapper}> |
|
|
|
|
This page displays all past errors and warnings. Once dismissed, they cannot be retrieved. |
|
|
|
|
<Button variant="destructive" onClick={clearAllNotifs} className={styles.clearAll}> |
|
|
|
|
Clear all notifications |
|
|
|
|
<div className={styles.topRow}> |
|
|
|
|
<Button |
|
|
|
|
variant="destructive" |
|
|
|
|
onClick={selectedNotificationIds.length === 0 ? clearAllNotifs : clearSelectedNotifications} |
|
|
|
|
className={styles.clearAll} |
|
|
|
|
> |
|
|
|
|
{selectedNotificationIds.length === 0 ? 'Clear all notifications' : 'Clear selected notifications'} |
|
|
|
|
</Button> |
|
|
|
|
</div> |
|
|
|
|
<ul className={styles.list}> |
|
|
|
|
{notifications.map((notif) => ( |
|
|
|
|
<li |
|
|
|
|
key={notif.id} |
|
|
|
|
className={cx(styles.listItem, { [styles.newItem]: notif.timestamp > lastReadTimestamp.current })} |
|
|
|
|
> |
|
|
|
|
<li key={notif.id} className={styles.listItem}> |
|
|
|
|
<Checkbox |
|
|
|
|
value={selectedNotificationIds.includes(notif.id)} |
|
|
|
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => |
|
|
|
|
handleCheckboxToggle(notif.id, event.target.checked) |
|
|
|
|
} |
|
|
|
|
/> |
|
|
|
|
<StoredNotificationItem |
|
|
|
|
className={cx(styles.notification, { [styles.newItem]: notif.timestamp > lastReadTimestamp.current })} |
|
|
|
|
severity={notif.severity} |
|
|
|
|
title={notif.title} |
|
|
|
|
onRemove={() => onClearNotification(notif.id)} |
|
|
|
|
timestamp={notif.timestamp} |
|
|
|
|
traceId={notif.traceId} |
|
|
|
|
> |
|
|
|
@ -71,6 +94,11 @@ export function StoredNotifications() { |
|
|
|
|
|
|
|
|
|
function getStyles(theme: GrafanaTheme2) { |
|
|
|
|
return { |
|
|
|
|
topRow: css({ |
|
|
|
|
alignItems: 'center', |
|
|
|
|
display: 'flex', |
|
|
|
|
justifyContent: 'flex-end', |
|
|
|
|
}), |
|
|
|
|
smallText: css({ |
|
|
|
|
fontSize: theme.typography.pxToRem(10), |
|
|
|
|
color: theme.colors.text.secondary, |
|
|
|
@ -90,9 +118,10 @@ function getStyles(theme: GrafanaTheme2) { |
|
|
|
|
gap: theme.spacing(1), |
|
|
|
|
}), |
|
|
|
|
listItem: css({ |
|
|
|
|
listStyle: 'none', |
|
|
|
|
gap: theme.spacing(1), |
|
|
|
|
alignItems: 'center', |
|
|
|
|
display: 'flex', |
|
|
|
|
gap: theme.spacing(2), |
|
|
|
|
listStyle: 'none', |
|
|
|
|
position: 'relative', |
|
|
|
|
}), |
|
|
|
|
newItem: css({ |
|
|
|
@ -113,6 +142,10 @@ function getStyles(theme: GrafanaTheme2) { |
|
|
|
|
alignItems: 'center', |
|
|
|
|
gap: theme.spacing(1), |
|
|
|
|
}), |
|
|
|
|
notification: css({ |
|
|
|
|
flex: 1, |
|
|
|
|
position: 'relative', |
|
|
|
|
}), |
|
|
|
|
wrapper: css({ |
|
|
|
|
display: 'flex', |
|
|
|
|
flexDirection: 'column', |
|
|
|
|