"use client"; import "./styles.css"; import { useMemo, useState } from "react"; import { Group, Indicator, Progress, Text } from "@mantine/core"; import type { DataTableColumn, DataTableSortStatus } from "mantine-datatable"; import { DataTable } from "mantine-datatable"; import { Activity, Battery, Cpu, HardDrive, MemoryStick, Monitor, Network, Server, Thermometer, Wifi, } from "lucide-react"; import { clientApi } from "@homarr/api/client"; import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; import type { BeszelSystemRow } from "../beszel/_shared/types"; import { loadAvgColor, statusColorMap, thresholdColor } from "../beszel/_shared/colors"; import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format"; import { useBeszelFilteredSystems, useBeszelSystemsSubscription } from "../beszel/_shared/hooks"; const directionMultiplier: Record = { asc: 1, desc: -1 }; type SystemRowWithKey = BeszelSystemRow & { _key: string }; interface SizeConfig { iconSize: number; fontSize: "xs" | "sm"; progressSize: "xs" | "sm"; cellPadding: number; valueMiw: number; } const getSizeConfig = (width: number): SizeConfig => { if (width < 400) { return { iconSize: 10, fontSize: "xs", progressSize: "xs", cellPadding: 2, valueMiw: 30, }; } return { iconSize: 14, fontSize: "sm", progressSize: "sm", cellPadding: 4, valueMiw: 38, }; }; export default function BeszelSystemTableWidget({ options, integrationIds, isEditMode, width, }: WidgetComponentProps<"beszelSystemTable">) { const t = useScopedI18n("widget.beszelSystemTable"); const { data: results = [] } = clientApi.widget.beszel.getSystems.useQuery( { integrationIds }, { staleTime: 5_000, refetchInterval: 5_000, retry: false }, ); const size = getSizeConfig(width); useBeszelSystemsSubscription(integrationIds, !isEditMode); const filteredSystems = useBeszelFilteredSystems(results, options.statusFilter); const [sortStatus, setSortStatus] = useState>({ columnAccessor: options.sortBy, direction: options.sortDirection as "asc" | "desc", }); const sortedSystems = useMemo(() => { const accessor = sortStatus.columnAccessor as keyof BeszelSystemRow; const dir = directionMultiplier[sortStatus.direction] ?? 1; return [...filteredSystems].toSorted((a, b) => { const aVal = a[accessor] ?? 0; const bVal = b[accessor] ?? 0; if (typeof aVal === "string" && typeof bVal === "string") return aVal.localeCompare(bVal) * dir; if (typeof aVal === "number" && typeof bVal === "number") return (aVal - bVal) * dir; return 0; }); }, [filteredSystems, sortStatus]); const PercentCell = ({ value }: { value: number }) => ( {formatPercent(value)} ); const columns = useMemo((): DataTableColumn[] => { const cols: (DataTableColumn | false)[] = [ { accessor: "name", title: ( {t("column.system")} ), sortable: true, render: (record) => ( {record.name} ), }, options.showCpu && { accessor: "cpu", title: ( {t("column.cpu")} ), sortable: true, render: (record) => , }, options.showMemory && { accessor: "memory", title: ( {t("column.memory")} ), sortable: true, render: (record) => , }, options.showDisk && { accessor: "disk", title: ( {t("column.disk")} ), sortable: true, render: (record) => , }, options.showGpu && { accessor: "gpu", title: ( {t("column.gpu")} ), sortable: true, render: (record) => , }, options.showLoadAvg && { accessor: "loadAvg", title: ( {t("column.loadAvg")} ), sortable: true, render: (record) => ( {formatLoadAvg(record.loadAvg)} ), }, options.showNet && { accessor: "netBytes", title: ( {t("column.net")} ), sortable: true, render: (record) => ( {formatByteRate(record.netBytes)} ), }, options.showTemp && { accessor: "temp", title: ( {t("column.temp")} ), sortable: true, render: (record) => {formatTemp(record.temp, false)}, }, options.showBattery && { accessor: "battery", title: ( {t("column.battery")} ), render: (record) => {record.battery ? `${record.battery[0]}%` : "—"}, }, options.showServices && { accessor: "services", title: ( {t("column.services")} ), sortable: true, render: (record) => {record.services}, }, options.showUptime && { accessor: "uptime", title: ( {t("column.uptime")} ), sortable: true, render: (record) => {formatUptime(record.uptime)}, }, options.showAgent && { accessor: "agentVersion", title: ( {t("column.agent")} ), sortable: true, render: (record) => {record.agentVersion}, }, ]; return cols.filter(Boolean) as DataTableColumn[]; }, [options, t, size]); return ( ); }