"use client"; import { useMemo } from "react"; import { Badge, Box, Center, Divider, Group, Loader, ScrollArea, Stack, Text, ThemeIcon, Tooltip, VisuallyHidden, } from "@mantine/core"; import { IconBellOff, IconCircleCheck, IconFlame, IconHistory } from "@tabler/icons-react"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import type { LucideIcon } from "lucide-react"; import { Activity, Battery, Cpu, HardDrive, MemoryStick, Monitor, Network, Server, Thermometer } from "lucide-react"; import { clientApi } from "@homarr/api/client"; import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; import { IntegrationErrorIndicator } from "../common/integration-error-indicator"; import { getUsableWidgetQueryData } from "../common/query-state"; const alertIconMap: Record = { CPU: Cpu, Memory: MemoryStick, Disk: HardDrive, Bandwidth: Network, Temperature: Thermometer, "Load Average": Activity, LoadAvg1: Activity, LoadAvg5: Activity, LoadAvg15: Activity, Status: Server, GPU: Monitor, Battery: Battery, }; dayjs.extend(relativeTime); export default function BeszelAlertsWidget({ options, integrationIds, isEditMode, height, }: WidgetComponentProps<"beszelAlerts">) { const t = useScopedI18n("widget.beszelAlerts"); const alertsInput = useMemo( () => ({ integrationIds, includeHistory: options.showHistory, maxHistoryItems: options.maxHistoryItems }), [integrationIds, options.showHistory, options.maxHistoryItems], ); const alertsQuery = clientApi.widget.beszel.getAlerts.useQuery(alertsInput); const results = getUsableWidgetQueryData(alertsQuery) ?? []; const { isPending } = alertsQuery; const systemNameMap = useMemo(() => { const map: Record = {}; for (const r of results) { if (r.systemNameMap) { Object.assign(map, r.systemNameMap); } } return map; }, [results]); const alerts = useMemo( () => results.flatMap((r) => r.alerts.map((a) => ({ ...a, _key: `${r.integrationId}:${a.id}` }))), [results], ); const history = useMemo(() => { const all = results.flatMap((r) => r.history.map((h) => ({ ...h, _key: `${r.integrationId}:${h.id}` }))); return all .toSorted((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()) .slice(0, options.maxHistoryItems); }, [results, options.maxHistoryItems]); const triggeredAlerts = alerts.filter((a) => a.triggered); const okAlerts = alerts.filter((a) => !a.triggered); const showOkAlerts = triggeredAlerts.length === 0 || height >= 260; const showHistory = options.showHistory && height >= 360; const showAlertDescriptions = height >= 190; if (isPending) { return (
); } return ( {alerts.length === 0 && ( {t("empty")} )} {triggeredAlerts.length > 0 && ( {t("status.triggered")} ({triggeredAlerts.length}) {triggeredAlerts.map((alert) => ( ))} )} {triggeredAlerts.length > 0 && showOkAlerts && okAlerts.length > 0 && } {showOkAlerts && okAlerts.length > 0 && ( {t("status.ok")} ({okAlerts.length}) {okAlerts.map((alert) => ( ))} )} {showHistory && history.length > 0 && ( <> {t("history")} {history.map((entry) => { const systemName = systemNameMap[entry.system] ?? entry.system; const isResolved = !!entry.resolved; const HistoryIcon = alertIconMap[entry.name] ?? Server; return ( {entry.name} {systemName} {isResolved ? t("resolved") : t("status.triggered")} {dayjs(entry.created).fromNow()} ); })} )} ); } interface AlertRowProps { name: string; value: number; min: number; systemName: string; triggered: boolean; showDescription: boolean; } const unitSuffixMap: Record = { CPU: "%", Memory: "%", Disk: "%", GPU: "%", Battery: "%", Temperature: "°", Bandwidth: " MB/s", "Load Average": "", LoadAvg1: "", LoadAvg5: "", LoadAvg15: "", Status: "", }; function formatAlertDescription(name: string, value: number, min: number): string { const suffix = unitSuffixMap[name] ?? ""; if (name === "Status") return `down for ${min} min`; return `exceeds ${value}${suffix} over ${min} min`; } function AlertRow({ name, value, min, systemName, triggered, showDescription }: AlertRowProps) { const Icon = alertIconMap[name] ?? Server; const description = formatAlertDescription(name, value, min); return ( {systemName} · {name} {showDescription && ( {description} )} {!showDescription && {description}} ); }