StoredNotifications.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { css, cx } from '@emotion/css';
  2. import React, { useRef, useState } from 'react';
  3. import { useEffectOnce } from 'react-use';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { Alert, Button, Checkbox, Icon, useStyles2 } from '@grafana/ui';
  6. import { StoredNotificationItem } from 'app/core/components/AppNotifications/StoredNotificationItem';
  7. import {
  8. clearAllNotifications,
  9. clearNotification,
  10. readAllNotifications,
  11. selectWarningsAndErrors,
  12. selectLastReadTimestamp,
  13. } from 'app/core/reducers/appNotification';
  14. import { useDispatch, useSelector } from 'app/types';
  15. export function StoredNotifications() {
  16. const dispatch = useDispatch();
  17. const notifications = useSelector((state) => selectWarningsAndErrors(state.appNotifications));
  18. const [selectedNotificationIds, setSelectedNotificationIds] = useState<string[]>([]);
  19. const allNotificationsSelected = notifications.every((notification) =>
  20. selectedNotificationIds.includes(notification.id)
  21. );
  22. const lastReadTimestamp = useRef(useSelector((state) => selectLastReadTimestamp(state.appNotifications)));
  23. const styles = useStyles2(getStyles);
  24. useEffectOnce(() => {
  25. dispatch(readAllNotifications(Date.now()));
  26. });
  27. const clearSelectedNotifications = () => {
  28. if (allNotificationsSelected) {
  29. dispatch(clearAllNotifications());
  30. } else {
  31. selectedNotificationIds.forEach((id) => {
  32. dispatch(clearNotification(id));
  33. });
  34. }
  35. setSelectedNotificationIds([]);
  36. };
  37. const handleAllCheckboxToggle = (isChecked: boolean) => {
  38. setSelectedNotificationIds(isChecked ? notifications.map((n) => n.id) : []);
  39. };
  40. const handleCheckboxToggle = (id: string) => {
  41. setSelectedNotificationIds((prevState) => {
  42. if (!prevState.includes(id)) {
  43. return [...prevState, id];
  44. } else {
  45. return prevState.filter((notificationId) => notificationId !== id);
  46. }
  47. });
  48. };
  49. if (notifications.length === 0) {
  50. return (
  51. <div className={styles.noNotifsWrapper}>
  52. <Icon name="bell" size="xxl" />
  53. <span>Notifications you have received will appear here.</span>
  54. </div>
  55. );
  56. }
  57. return (
  58. <div className={styles.wrapper}>
  59. <Alert
  60. severity="info"
  61. title="This page displays past errors and warnings. Once dismissed, they cannot be retrieved."
  62. />
  63. <div className={styles.topRow}>
  64. <Checkbox
  65. value={allNotificationsSelected}
  66. onChange={(event: React.ChangeEvent<HTMLInputElement>) => handleAllCheckboxToggle(event.target.checked)}
  67. />
  68. <Button disabled={selectedNotificationIds.length === 0} onClick={clearSelectedNotifications}>
  69. Dismiss notifications
  70. </Button>
  71. </div>
  72. <ul className={styles.list}>
  73. {notifications.map((notif) => (
  74. <li key={notif.id} className={styles.listItem}>
  75. <StoredNotificationItem
  76. className={cx({ [styles.newItem]: notif.timestamp > lastReadTimestamp.current })}
  77. isSelected={selectedNotificationIds.includes(notif.id)}
  78. onClick={() => handleCheckboxToggle(notif.id)}
  79. severity={notif.severity}
  80. title={notif.title}
  81. timestamp={notif.timestamp}
  82. traceId={notif.traceId}
  83. >
  84. <span>{notif.text}</span>
  85. </StoredNotificationItem>
  86. </li>
  87. ))}
  88. </ul>
  89. </div>
  90. );
  91. }
  92. function getStyles(theme: GrafanaTheme2) {
  93. return {
  94. topRow: css({
  95. alignItems: 'center',
  96. display: 'flex',
  97. gap: theme.spacing(2),
  98. }),
  99. list: css({
  100. display: 'flex',
  101. flexDirection: 'column',
  102. }),
  103. listItem: css({
  104. alignItems: 'center',
  105. display: 'flex',
  106. gap: theme.spacing(2),
  107. listStyle: 'none',
  108. position: 'relative',
  109. }),
  110. newItem: css({
  111. '&::before': {
  112. content: '""',
  113. height: '100%',
  114. position: 'absolute',
  115. left: '-7px',
  116. top: 0,
  117. background: theme.colors.gradients.brandVertical,
  118. width: theme.spacing(0.5),
  119. borderRadius: theme.shape.borderRadius(1),
  120. },
  121. }),
  122. noNotifsWrapper: css({
  123. display: 'flex',
  124. flexDirection: 'column',
  125. alignItems: 'center',
  126. gap: theme.spacing(1),
  127. }),
  128. wrapper: css({
  129. display: 'flex',
  130. flexDirection: 'column',
  131. gap: theme.spacing(2),
  132. }),
  133. };
  134. }