prometheus/web/ui/mantine-ui/src/components/NotificationsIcon.tsx
Julien 6cde0096e2 Add notifications to the web UI when configuration reload fails.
This commit introduces a new `/api/v1/notifications/live` endpoint that
utilizes Server-Sent Events (SSE) to stream notifications to the web UI.
This is used to display alerts such as when a configuration reload
has failed.

I opted for SSE over WebSockets because SSE is simpler to implement and
more robust for our use case. Since we only need one-way communication
from the server to the client, SSE fits perfectly without the overhead
of establishing and maintaining a two-way WebSocket connection.

When the SSE connection fails, we go back to a classic
/api/v1/notifications API endpoint.

This commit also contains the required UI changes for the new Mantine UI.

Signed-off-by: Julien <roidelapluie@o11y.eu>
2024-09-27 15:28:38 +02:00

63 lines
2.6 KiB
TypeScript

import { ActionIcon, Indicator, Popover, Card, Text, Stack, ScrollArea, Group } from "@mantine/core";
import { IconBell, IconAlertTriangle, IconNetworkOff } from "@tabler/icons-react";
import { useNotifications } from '../state/useNotifications';
import { actionIconStyle } from "../styles";
import { useSettings } from '../state/settingsSlice';
import { formatTimestamp } from "../lib/formatTime";
const NotificationsIcon = () => {
const { notifications, isConnectionError } = useNotifications();
const { useLocalTime } = useSettings();
return (
(notifications.length === 0 && !isConnectionError) ? null : (
<Indicator
color={"red"}
size={16}
label={isConnectionError ? "!" : notifications.length}
>
<Popover position="bottom-end" shadow="md" withArrow>
<Popover.Target>
<ActionIcon color="gray" title="Notifications" aria-label="Notifications" size={32}>
<IconBell style={actionIconStyle}/>
</ActionIcon>
</Popover.Target>
<Popover.Dropdown>
<Stack gap="xs">
<Text fw={700} size="xs" color="dimmed" ta="center">Notifications</Text>
<ScrollArea.Autosize mah={200}>
{ isConnectionError ? (
<Card p="xs" color="red">
<Group wrap="nowrap">
<IconNetworkOff color="red" size={20} />
<Stack gap="0">
<Text size="sm" fw={500}>Real-time notifications interrupted.</Text>
<Text size="xs" color="dimmed">Please refresh the page or check your connection.</Text>
</Stack>
</Group>
</Card>
) : notifications.length === 0 ? (
<Text ta="center" color="dimmed">No notifications</Text>
) : (notifications.map((notification, index) => (
<Card key={index} p="xs">
<Group wrap="nowrap">
<IconAlertTriangle color="red" size={20} />
<Stack style={{ maxWidth: 250 }} gap={0}>
<Text size="sm" fw={500}>{notification.text}</Text>
<Text size="xs" color="dimmed">{formatTimestamp(new Date(notification.date).valueOf() / 1000, useLocalTime)}</Text>
</Stack>
</Group>
</Card>
)))}
</ScrollArea.Autosize>
</Stack>
</Popover.Dropdown>
</Popover>
</Indicator>
)
);
};
export default NotificationsIcon;