Style cleanups, mostly for web notifications and startup alert

Some of the changes are a bit unreadable because the previous files were not
saved with the project's linter / auto-formatter settings applied. But it's
basically:

* For icons that are not Mantine-native components, use the rem() function
  for computing their size, so they scale correctly with the root font size.
  See https://mantine.dev/styles/rem/.
* Try a different icon for the notifications tray, since the bell icon was
  already used for Prometheus alerts. Other candidates from
  https://tabler.io/icons would be IconExclamationCircle or
  IconDeviceDesktopExclamation or IconMessageCircleExclamation.
* The server startup alert looked a bit cramped, introduced a Stack to add
  spacing between the text and the progress bar.
* Added a bit of spacing between notification text and date. Things looked
  cramped. To make things look ok with that, I also top-aligned the
  notification text and icon.

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-10-04 13:54:03 +02:00
parent 023146e918
commit 3d2194f561
4 changed files with 121 additions and 63 deletions

View file

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

View file

@ -4,7 +4,7 @@ import { useAppDispatch } from "../state/hooks";
import { updateSettings, useSettings } from "../state/settingsSlice"; import { updateSettings, useSettings } from "../state/settingsSlice";
import { useSuspenseAPIQuery } from "../api/api"; import { useSuspenseAPIQuery } from "../api/api";
import { WALReplayStatus } from "../api/responseTypes/walreplay"; import { WALReplayStatus } from "../api/responseTypes/walreplay";
import { Progress, Alert } from "@mantine/core"; import { Progress, Alert, Stack } from "@mantine/core";
import { useSuspenseQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
const STATUS_STARTING = "is starting up..."; const STATUS_STARTING = "is starting up...";
@ -57,14 +57,12 @@ const ReadinessLoader: FC = () => {
// Only call WAL replay status API if the service is starting up. // Only call WAL replay status API if the service is starting up.
const shouldQueryWALReplay = statusMessage === STATUS_STARTING; const shouldQueryWALReplay = statusMessage === STATUS_STARTING;
const { const { data: walData, isSuccess: walSuccess } =
data: walData, useSuspenseAPIQuery<WALReplayStatus>({
isSuccess: walSuccess, path: "/status/walreplay",
} = useSuspenseAPIQuery<WALReplayStatus>({ key: ["walreplay", queryKey],
path: "/status/walreplay", enabled: shouldQueryWALReplay, // Only enabled when service is starting up.
key: ["walreplay", queryKey], });
enabled: shouldQueryWALReplay, // Only enabled when service is starting up.
});
useEffect(() => { useEffect(() => {
if (ready) { if (ready) {
@ -80,14 +78,18 @@ const ReadinessLoader: FC = () => {
return ( return (
<Alert <Alert
color="yellow" color="yellow"
title={"Prometheus " + (agentMode && "Agent "||"") + (statusMessage || STATUS_LOADING)} title={
icon={<IconAlertTriangle/>} "Prometheus " +
((agentMode && "Agent ") || "") +
(statusMessage || STATUS_LOADING)
}
icon={<IconAlertTriangle />}
maw={500} maw={500}
mx="auto" mx="auto"
mt="lg" mt="lg"
> >
{shouldQueryWALReplay && walSuccess && walData && ( {shouldQueryWALReplay && walSuccess && walData && (
<> <Stack>
<strong> <strong>
Replaying WAL ({walData.data.current}/{walData.data.max}) Replaying WAL ({walData.data.current}/{walData.data.max})
</strong> </strong>
@ -95,9 +97,13 @@ const ReadinessLoader: FC = () => {
size="xl" size="xl"
animated animated
color="yellow" color="yellow"
value={((walData.data.current - walData.data.min + 1) / (walData.data.max - walData.data.min + 1)) * 100} value={
((walData.data.current - walData.data.min + 1) /
(walData.data.max - walData.data.min + 1)) *
100
}
/> />
</> </Stack>
)} )}
</Alert> </Alert>
); );

View file

@ -4,6 +4,7 @@ import {
Badge, Badge,
Card, Card,
Group, Group,
rem,
Stack, Stack,
Text, Text,
Tooltip, Tooltip,
@ -135,11 +136,15 @@ export default function RulesPage() {
<Group gap="xs" wrap="nowrap"> <Group gap="xs" wrap="nowrap">
{r.type === "alerting" ? ( {r.type === "alerting" ? (
<Tooltip label="Alerting rule" withArrow> <Tooltip label="Alerting rule" withArrow>
<IconBell size={15} /> <IconBell
style={{ width: rem(15), height: rem(15) }}
/>
</Tooltip> </Tooltip>
) : ( ) : (
<Tooltip label="Recording rule" withArrow> <Tooltip label="Recording rule" withArrow>
<IconTimeline size={15} /> <IconTimeline
style={{ width: rem(15), height: rem(15) }}
/>
</Tooltip> </Tooltip>
)} )}
<Text>{r.name}</Text> <Text>{r.name}</Text>

View file

@ -21,6 +21,7 @@ import {
Skeleton, Skeleton,
Stack, Stack,
Table, Table,
rem,
} from "@mantine/core"; } from "@mantine/core";
import { escapeString } from "../../../lib/escapeString"; import { escapeString } from "../../../lib/escapeString";
import serializeNode from "../../../promql/serialize"; import serializeNode from "../../../promql/serialize";
@ -326,7 +327,9 @@ const LabelsExplorer: FC<LabelsExplorerProps> = ({
title="Cancel" title="Cancel"
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
> >
<IconX size={18} /> <IconX
style={{ width: rem(18), height: rem(18) }}
/>
</Button> </Button>
</Group> </Group>
) : ( ) : (