From 515b285ef758e7a66778020f726c7aaf29b66a75 Mon Sep 17 00:00:00 2001 From: ajnart <49837342+ajnart@users.noreply.github.com> Date: Mon, 8 Jun 2026 13:54:14 +0000 Subject: [PATCH] style: auto-fix lint and format --- packages/api/src/router/board.ts | 10 +- packages/api/src/router/widgets/beszel.ts | 38 ++- .../src/beszel/beszel-integration.ts | 101 +++---- .../beszel/test/beszel-integration.spec.ts | 1 - packages/request-handler/src/beszel.ts | 5 +- .../widgets/src/beszel-alerts/component.tsx | 28 +- .../src/beszel-system-grid/component.tsx | 258 +++++++++++++++--- .../widgets/src/beszel-system-grid/index.ts | 30 +- .../src/beszel-system-stats/component.tsx | 33 ++- .../src/beszel-system-table/component.tsx | 11 +- .../widgets/src/beszel-system-table/index.ts | 34 ++- packages/widgets/src/beszel/_shared/chart.tsx | 22 +- packages/widgets/src/beszel/_shared/colors.ts | 18 +- .../widgets/src/beszel/_shared/tooltip.tsx | 4 +- 14 files changed, 370 insertions(+), 223 deletions(-) diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index 286b16b23..acd2367c9 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -613,7 +613,7 @@ export const boardRouter = createTRPCRouter({ boardId: board.id, }); - const sortedLayouts = board.layouts.sort((layoutA, layoutB) => layoutA.columnCount - layoutB.columnCount); + const sortedLayouts = board.layouts.toSorted((layoutA, layoutB) => layoutA.columnCount - layoutB.columnCount); // Fallback to biggest if none exists with columnCount bigger than addedLayout.columnCount const layoutToClone = // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -1215,7 +1215,7 @@ export const boardRouter = createTRPCRouter({ }); return { - inherited: dbGroupPermissions.sort((permissionA, permissionB) => { + inherited: dbGroupPermissions.toSorted((permissionA, permissionB) => { return permissionA.group.name.localeCompare(permissionB.group.name); }), users: userPermissions @@ -1223,7 +1223,7 @@ export const boardRouter = createTRPCRouter({ user, permission, })) - .sort((permissionA, permissionB) => { + .toSorted((permissionA, permissionB) => { return (permissionA.user.name ?? "").localeCompare(permissionB.user.name ?? ""); }), groups: dbGroupBoardPermission @@ -1234,7 +1234,7 @@ export const boardRouter = createTRPCRouter({ }, permission, })) - .sort((permissionA, permissionB) => { + .toSorted((permissionA, permissionB) => { return permissionA.group.name.localeCompare(permissionB.group.name); }), }; @@ -1586,7 +1586,7 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL, use ...otherBoardProperties, layouts: layouts .map(({ boardId: _, ...layout }) => layout) - .sort((layoutA, layoutB) => layoutA.breakpoint - layoutB.breakpoint), + .toSorted((layoutA, layoutB) => layoutA.breakpoint - layoutB.breakpoint), sections: sections.map(({ collapseStates, ...section }) => parseSection({ ...section, diff --git a/packages/api/src/router/widgets/beszel.ts b/packages/api/src/router/widgets/beszel.ts index e137dd069..2a2d08332 100644 --- a/packages/api/src/router/widgets/beszel.ts +++ b/packages/api/src/router/widgets/beszel.ts @@ -5,33 +5,29 @@ import { beszelAlertsRequestHandler, beszelStatsRequestHandler, beszelSystemsRequestHandler, - type BeszelAlertsData, - type BeszelStatsData, - type BeszelSystemRow, } from "@homarr/request-handler/beszel"; +import type { BeszelAlertsData, BeszelStatsData, BeszelSystemRow } from "@homarr/request-handler/beszel"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; import { createTRPCRouter, publicProcedure } from "../../trpc"; export const beszelRouter = createTRPCRouter({ - getSystems: publicProcedure - .concat(createManyIntegrationMiddleware("query", "beszel")) - .query(async ({ ctx }) => { - const results = await Promise.all( - ctx.integrations.map(async (integration) => { - const innerHandler = beszelSystemsRequestHandler.handler(integration, {}); - const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); - return { - integrationId: integration.id, - integrationName: integration.name, - integrationUrl: integration.url, - systems: data, - updatedAt: timestamp, - }; - }), - ); - return results; - }), + getSystems: publicProcedure.concat(createManyIntegrationMiddleware("query", "beszel")).query(async ({ ctx }) => { + const results = await Promise.all( + ctx.integrations.map(async (integration) => { + const innerHandler = beszelSystemsRequestHandler.handler(integration, {}); + const { data, timestamp } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }); + return { + integrationId: integration.id, + integrationName: integration.name, + integrationUrl: integration.url, + systems: data, + updatedAt: timestamp, + }; + }), + ); + return results; + }), subscribeSystems: publicProcedure .concat(createManyIntegrationMiddleware("query", "beszel")) diff --git a/packages/integrations/src/beszel/beszel-integration.ts b/packages/integrations/src/beszel/beszel-integration.ts index aee441797..83fa155e5 100644 --- a/packages/integrations/src/beszel/beszel-integration.ts +++ b/packages/integrations/src/beszel/beszel-integration.ts @@ -44,17 +44,14 @@ export class BeszelIntegration extends Integration { return existingSession; } - const response = await fetchWithTrustedCertificatesAsync( - this.url("/api/collections/users/auth-with-password"), - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - identity: this.getSecretValue("username"), - password: this.getSecretValue("password"), - }), - }, - ); + const response = await fetchWithTrustedCertificatesAsync(this.url("/api/collections/users/auth-with-password"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + identity: this.getSecretValue("username"), + password: this.getSecretValue("password"), + }), + }); if (!response.ok) { throw new ResponseError(response); @@ -66,10 +63,7 @@ export class BeszelIntegration extends Integration { return session; } - private async fetchWithAuthAsync( - url: URL, - options: { method?: string; body?: string } = {}, - ) { + private async fetchWithAuthAsync(url: URL, options: { method?: string; body?: string } = {}) { let session = await this.authenticateAsync(); const doFetch = (token: string) => @@ -97,8 +91,6 @@ export class BeszelIntegration extends Integration { return response; } - - public async getSystemsAsync(): Promise { const response = await this.fetchWithAuthAsync( this.url("/api/collections/systems/records", { perPage: "500", sort: "-updated" }), @@ -114,11 +106,7 @@ export class BeszelIntegration extends Integration { return (await response.json()) as BeszelSystemDetails; } - public async getSystemStatsAsync( - systemId: string, - type = "1m", - perPage = 60, - ): Promise { + public async getSystemStatsAsync(systemId: string, type = "1m", perPage = 60): Promise { const filter = `system='${escapeFilterValue(systemId)}' && type='${escapeFilterValue(type)}'`; const response = await this.fetchWithAuthAsync( this.url("/api/collections/system_stats/records", { @@ -186,9 +174,7 @@ export class BeszelIntegration extends Integration { if (systemId) { params.filter = `system='${escapeFilterValue(systemId)}'`; } - const response = await this.fetchWithAuthAsync( - this.url("/api/collections/alerts/records", params), - ); + const response = await this.fetchWithAuthAsync(this.url("/api/collections/alerts/records", params)); const data = (await response.json()) as PocketBaseListResponse; return data.items; } @@ -201,29 +187,24 @@ export class BeszelIntegration extends Integration { if (systemId) { params.filter = `system='${escapeFilterValue(systemId)}'`; } - const response = await this.fetchWithAuthAsync( - this.url("/api/collections/alerts_history/records", params), - ); + const response = await this.fetchWithAuthAsync(this.url("/api/collections/alerts_history/records", params)); const data = (await response.json()) as PocketBaseListResponse; return data.items; } public async createAlertAsync(systemId: string, input: CreateAlertInput): Promise { const session = await this.authenticateAsync(); - const response = await this.fetchWithAuthAsync( - this.url("/api/collections/alerts/records"), - { - method: "POST", - body: JSON.stringify({ - system: systemId, - user: session.userId, - name: input.name, - value: input.value, - min: input.min ?? 0, - triggered: false, - }), - }, - ); + const response = await this.fetchWithAuthAsync(this.url("/api/collections/alerts/records"), { + method: "POST", + body: JSON.stringify({ + system: systemId, + user: session.userId, + name: input.name, + value: input.value, + min: input.min ?? 0, + triggered: false, + }), + }); return (await response.json()) as BeszelAlert; } @@ -239,37 +220,29 @@ export class BeszelIntegration extends Integration { } public async deleteAlertAsync(alertId: string): Promise { - await this.fetchWithAuthAsync( - this.url(`/api/collections/alerts/records/${alertId}` as `/${string}`), - { method: "DELETE" }, - ); + await this.fetchWithAuthAsync(this.url(`/api/collections/alerts/records/${alertId}` as `/${string}`), { + method: "DELETE", + }); } public async pauseSystemAsync(systemId: string): Promise { - await this.fetchWithAuthAsync( - this.url(`/api/collections/systems/records/${systemId}` as `/${string}`), - { - method: "PATCH", - body: JSON.stringify({ status: "paused" }), - }, - ); + await this.fetchWithAuthAsync(this.url(`/api/collections/systems/records/${systemId}` as `/${string}`), { + method: "PATCH", + body: JSON.stringify({ status: "paused" }), + }); } public async resumeSystemAsync(systemId: string): Promise { - await this.fetchWithAuthAsync( - this.url(`/api/collections/systems/records/${systemId}` as `/${string}`), - { - method: "PATCH", - body: JSON.stringify({ status: "pending" }), - }, - ); + await this.fetchWithAuthAsync(this.url(`/api/collections/systems/records/${systemId}` as `/${string}`), { + method: "PATCH", + body: JSON.stringify({ status: "pending" }), + }); } public async deleteSystemAsync(systemId: string): Promise { - await this.fetchWithAuthAsync( - this.url(`/api/collections/systems/records/${systemId}` as `/${string}`), - { method: "DELETE" }, - ); + await this.fetchWithAuthAsync(this.url(`/api/collections/systems/records/${systemId}` as `/${string}`), { + method: "DELETE", + }); } protected async testingAsync(input: IntegrationTestingInput): Promise { diff --git a/packages/integrations/src/beszel/test/beszel-integration.spec.ts b/packages/integrations/src/beszel/test/beszel-integration.spec.ts index 2320b214b..e24e91b6a 100644 --- a/packages/integrations/src/beszel/test/beszel-integration.spec.ts +++ b/packages/integrations/src/beszel/test/beszel-integration.spec.ts @@ -144,7 +144,6 @@ describe.skipIf(!shouldRun)("BeszelIntegration (real instance)", () => { }); }); - describe("alert CRUD", () => { let alertId: string; diff --git a/packages/request-handler/src/beszel.ts b/packages/request-handler/src/beszel.ts index 3e6ca63d9..ca99ed19d 100644 --- a/packages/request-handler/src/beszel.ts +++ b/packages/request-handler/src/beszel.ts @@ -60,7 +60,6 @@ export const beszelSystemsRequestHandler = createCachedIntegrationRequestHandler queryKey: "beszelSystems", }); - export interface BeszelAlertsData { alerts: BeszelAlert[]; history: BeszelAlertHistory[]; @@ -74,9 +73,7 @@ export const beszelAlertsRequestHandler = createCachedIntegrationRequestHandler< async requestAsync(integration, input) { const instance = await createIntegrationAsync(integration); const alerts = await instance.getAlertsAsync(); - const history = input.includeHistory - ? await instance.getAlertHistoryAsync(undefined, input.maxHistoryItems) - : []; + const history = input.includeHistory ? await instance.getAlertHistoryAsync(undefined, input.maxHistoryItems) : []; return { alerts, history }; }, cacheDuration: dayjs.duration(15, "seconds"), diff --git a/packages/widgets/src/beszel-alerts/component.tsx b/packages/widgets/src/beszel-alerts/component.tsx index 6a900521a..167b2ce1d 100644 --- a/packages/widgets/src/beszel-alerts/component.tsx +++ b/packages/widgets/src/beszel-alerts/component.tsx @@ -6,17 +6,7 @@ import { IconBellOff, IconCircleCheck, IconFlame, IconHistory } from "@tabler/ic 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 { Activity, Battery, Cpu, HardDrive, MemoryStick, Monitor, Network, Server, Thermometer } from "lucide-react"; import { clientApi } from "@homarr/api/client"; import { useScopedI18n } from "@homarr/translation/client"; @@ -90,7 +80,9 @@ export default function BeszelAlertsWidget({ const history = useMemo(() => { const all = results.flatMap((r) => r.history.map((h) => ({ ...h, _key: `${r.integrationId}:${h.id}` }))); - return all.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()).slice(0, options.maxHistoryItems); + 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); @@ -104,7 +96,9 @@ export default function BeszelAlertsWidget({ - {t("empty")} + + {t("empty")} + )} @@ -245,9 +239,7 @@ function AlertRow({ name, value, min, systemName, triggered }: AlertRowProps) { style={{ borderRadius: "var(--mantine-radius-sm)", borderLeft: `3px solid var(--mantine-color-${triggered ? "red" : "green"}-6)`, - backgroundColor: triggered - ? "var(--mantine-color-red-light)" - : "var(--mantine-color-default-hover)", + backgroundColor: triggered ? "var(--mantine-color-red-light)" : "var(--mantine-color-default-hover)", }} > @@ -256,7 +248,9 @@ function AlertRow({ name, value, min, systemName, triggered }: AlertRowProps) { {systemName} - · + + · + {name} diff --git a/packages/widgets/src/beszel-system-grid/component.tsx b/packages/widgets/src/beszel-system-grid/component.tsx index f9a081bbd..83efb69b6 100644 --- a/packages/widgets/src/beszel-system-grid/component.tsx +++ b/packages/widgets/src/beszel-system-grid/component.tsx @@ -38,9 +38,51 @@ interface SizeConfig { } const sizeThresholds: [number, SizeConfig][] = [ - [0, { iconSize: 10, fontSize: "10px", progressSize: "xs", labelMiw: 40, valueMiw: 30, rowHeight: 18, cardPadding: 4, badgeHeight: 20, badgeSize: "xs", gap: 2 }], - [160, { iconSize: 12, fontSize: "xs", progressSize: "xs", labelMiw: 50, valueMiw: 34, rowHeight: 22, cardPadding: 6, badgeHeight: 22, badgeSize: "xs", gap: 6 }], - [280, { iconSize: 14, fontSize: "xs", progressSize: "sm", labelMiw: 55, valueMiw: 38, rowHeight: 24, cardPadding: 8, badgeHeight: 26, badgeSize: "sm", gap: 8 }], + [ + 0, + { + iconSize: 10, + fontSize: "10px", + progressSize: "xs", + labelMiw: 40, + valueMiw: 30, + rowHeight: 18, + cardPadding: 4, + badgeHeight: 20, + badgeSize: "xs", + gap: 2, + }, + ], + [ + 160, + { + iconSize: 12, + fontSize: "xs", + progressSize: "xs", + labelMiw: 50, + valueMiw: 34, + rowHeight: 22, + cardPadding: 6, + badgeHeight: 22, + badgeSize: "xs", + gap: 6, + }, + ], + [ + 280, + { + iconSize: 14, + fontSize: "xs", + progressSize: "sm", + labelMiw: 55, + valueMiw: 38, + rowHeight: 24, + cardPadding: 8, + badgeHeight: 26, + badgeSize: "sm", + gap: 8, + }, + ], ]; const getSizeConfig = (cellWidth: number, cellHeight: number): SizeConfig => { @@ -85,15 +127,21 @@ interface MetricRowProps { const MetricValue = ({ value, progress, size }: Pick) => ( - {value} - {progress && } + + {value} + + {progress && ( + + )} ); const MetricRow = ({ icon, label, value, progress, size }: MetricRowProps) => ( {icon} - {label} + + {label} + ); @@ -107,50 +155,170 @@ interface SystemCardProps { } const metricRenderers = [ - { key: "showCpu", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.cpu")} value={formatPercent(s.cpu)} progress={{ value: s.cpu, color: thresholdColor(s.cpu) }} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showCpu }, - { key: "showMemory", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.memory")} value={formatPercent(s.memory)} progress={{ value: s.memory, color: thresholdColor(s.memory) }} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showMemory }, - { key: "showDisk", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.disk")} value={formatPercent(s.disk)} progress={{ value: s.disk, color: thresholdColor(s.disk) }} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showDisk }, - { key: "showGpu", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.gpu")} value={formatPercent(s.gpu)} progress={{ value: s.gpu, color: thresholdColor(s.gpu) }} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showGpu && s.gpu > 0 }, - { key: "showLoadAvg", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.loadAvg")} value={formatLoadAvg(s.loadAvg)} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showLoadAvg && s.loadAvg !== null }, - { key: "showNet", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.net")} value={formatByteRate(s.netBytes)} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showNet }, - { key: "showTemp", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.temp")} value={formatTemp(s.temp, false)} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showTemp && s.temp !== null }, - { key: "showBattery", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => { - const batteryIcons = [Battery, BatteryCharging] as const; - const Icon = batteryIcons[Math.min(1, s.battery?.[1] ?? 0)]!; - return } label={t("metric.battery")} value={`${s.battery?.[0] ?? 0}%`} size={sz} />; - }, visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showBattery && s.battery !== null }, - { key: "showServices", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.services")} value={String(s.services)} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showServices }, - { key: "showUptime", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.uptime")} value={formatUptime(s.uptime)} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showUptime }, - { key: "showAgent", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } label={t("metric.agent")} value={s.agentVersion} size={sz} /> - ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showAgent }, + { + key: "showCpu", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.cpu")} + value={formatPercent(s.cpu)} + progress={{ value: s.cpu, color: thresholdColor(s.cpu) }} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showCpu, + }, + { + key: "showMemory", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.memory")} + value={formatPercent(s.memory)} + progress={{ value: s.memory, color: thresholdColor(s.memory) }} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showMemory, + }, + { + key: "showDisk", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.disk")} + value={formatPercent(s.disk)} + progress={{ value: s.disk, color: thresholdColor(s.disk) }} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showDisk, + }, + { + key: "showGpu", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.gpu")} + value={formatPercent(s.gpu)} + progress={{ value: s.gpu, color: thresholdColor(s.gpu) }} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showGpu && s.gpu > 0, + }, + { + key: "showLoadAvg", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.loadAvg")} + value={formatLoadAvg(s.loadAvg)} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showLoadAvg && s.loadAvg !== null, + }, + { + key: "showNet", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.net")} + value={formatByteRate(s.netBytes)} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showNet, + }, + { + key: "showTemp", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.temp")} + value={formatTemp(s.temp, false)} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showTemp && s.temp !== null, + }, + { + key: "showBattery", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => { + const batteryIcons = [Battery, BatteryCharging] as const; + const Icon = batteryIcons[Math.min(1, s.battery?.[1] ?? 0)]!; + return ( + } + label={t("metric.battery")} + value={`${s.battery?.[0] ?? 0}%`} + size={sz} + /> + ); + }, + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showBattery && s.battery !== null, + }, + { + key: "showServices", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.services")} + value={String(s.services)} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showServices, + }, + { + key: "showUptime", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.uptime")} + value={formatUptime(s.uptime)} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showUptime, + }, + { + key: "showAgent", + render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( + } + label={t("metric.agent")} + value={s.agentVersion} + size={sz} + /> + ), + visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showAgent, + }, ] as const; const SystemCard = ({ system, options, t, size, maxMetrics }: SystemCardProps) => { - const visibleMetrics = metricRenderers - .filter((m) => m.visible(system, options)) - .slice(0, maxMetrics); + const visibleMetrics = metricRenderers.filter((m) => m.visible(system, options)).slice(0, maxMetrics); return ( - + ({ - statusFilter: factory.select({ defaultValue: "all", options: statusOptions }), - showCpu: factory.switch({ defaultValue: true }), - showMemory: factory.switch({ defaultValue: true }), - showDisk: factory.switch({ defaultValue: true }), - showGpu: factory.switch({ defaultValue: true }), - showLoadAvg: factory.switch({ defaultValue: true }), - showNet: factory.switch({ defaultValue: true }), - showTemp: factory.switch({ defaultValue: true }), - showBattery: factory.switch({ defaultValue: true }), - showServices: factory.switch({ defaultValue: true }), - showUptime: factory.switch({ defaultValue: true }), - showAgent: factory.switch({ defaultValue: true }), - }), - ); + return optionsBuilder.from((factory) => ({ + statusFilter: factory.select({ defaultValue: "all", options: statusOptions }), + showCpu: factory.switch({ defaultValue: true }), + showMemory: factory.switch({ defaultValue: true }), + showDisk: factory.switch({ defaultValue: true }), + showGpu: factory.switch({ defaultValue: true }), + showLoadAvg: factory.switch({ defaultValue: true }), + showNet: factory.switch({ defaultValue: true }), + showTemp: factory.switch({ defaultValue: true }), + showBattery: factory.switch({ defaultValue: true }), + showServices: factory.switch({ defaultValue: true }), + showUptime: factory.switch({ defaultValue: true }), + showAgent: factory.switch({ defaultValue: true }), + })); }, }).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index 2e34f2be2..7e00aa08d 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -60,19 +60,28 @@ export default function BeszelSystemStatsWidget({ const BYTES_TO_GB = 1024 * 1024 * 1024; const cpuMap = useCallback((s: { cpu: number }) => ({ [t("chart.cpu.series")]: s.cpu }), [t]); - const memoryMap = useCallback((s: { mu: number; mb: number }) => ({ - [t("chart.memory.series")]: s.mu / BYTES_TO_GB, - [t("chart.memory.cache")]: (s.mb ?? 0) / BYTES_TO_GB, - }), [t]); + const memoryMap = useCallback( + (s: { mu: number; mb: number }) => ({ + [t("chart.memory.series")]: s.mu / BYTES_TO_GB, + [t("chart.memory.cache")]: (s.mb ?? 0) / BYTES_TO_GB, + }), + [t], + ); const diskMap = useCallback((s: { du: number }) => ({ [t("chart.disk.series")]: s.du / BYTES_TO_GB }), [t]); - const diskIOMap = useCallback((s: { dr?: number; dw?: number }) => ({ - [t("chart.diskIO.read")]: s.dr ?? 0, - [t("chart.diskIO.write")]: s.dw ?? 0, - }), [t]); - const networkMap = useCallback((s: { ns: number; nr: number }) => ({ - [t("chart.network.sent")]: s.ns, - [t("chart.network.recv")]: s.nr, - }), [t]); + const diskIOMap = useCallback( + (s: { dr?: number; dw?: number }) => ({ + [t("chart.diskIO.read")]: s.dr ?? 0, + [t("chart.diskIO.write")]: s.dw ?? 0, + }), + [t], + ); + const networkMap = useCallback( + (s: { ns: number; nr: number }) => ({ + [t("chart.network.sent")]: s.ns, + [t("chart.network.recv")]: s.nr, + }), + [t], + ); const cpuData = useSystemChartData(statsResult?.systemStats, cpuMap); const memoryData = useSystemChartData(statsResult?.systemStats, memoryMap); diff --git a/packages/widgets/src/beszel-system-table/component.tsx b/packages/widgets/src/beszel-system-table/component.tsx index c4db549e0..1422ccce9 100644 --- a/packages/widgets/src/beszel-system-table/component.tsx +++ b/packages/widgets/src/beszel-system-table/component.tsx @@ -68,7 +68,7 @@ export default function BeszelSystemTableWidget({ const sortedSystems = useMemo(() => { const accessor = sortStatus.columnAccessor as keyof BeszelSystemRow; const dir = directionMultiplier[sortStatus.direction] ?? 1; - return [...filteredSystems].sort((a, b) => { + 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; @@ -161,10 +161,7 @@ export default function BeszelSystemTableWidget({ sortable: true, render: (record) => ( - + {formatLoadAvg(record.loadAvg)} ), @@ -203,9 +200,7 @@ export default function BeszelSystemTableWidget({ {t("column.battery")} ), - render: (record) => ( - {record.battery ? `${record.battery[0]}%` : "—"} - ), + render: (record) => {record.battery ? `${record.battery[0]}%` : "—"}, }, options.showServices && { accessor: "services", diff --git a/packages/widgets/src/beszel-system-table/index.ts b/packages/widgets/src/beszel-system-table/index.ts index bd3e002da..eb8e9a2a8 100644 --- a/packages/widgets/src/beszel-system-table/index.ts +++ b/packages/widgets/src/beszel-system-table/index.ts @@ -28,23 +28,21 @@ export const { definition, componentLoader } = createWidgetDefinition("beszelSys supportedIntegrations: ["beszel"], integrationsRequired: true, createOptions() { - return optionsBuilder.from( - (factory) => ({ - statusFilter: factory.select({ defaultValue: "all", options: statusOptions }), - sortBy: factory.select({ defaultValue: "name", options: sortOptions }), - sortDirection: factory.select({ defaultValue: "asc", options: sortDirectionOptions }), - showCpu: factory.switch({ defaultValue: true }), - showMemory: factory.switch({ defaultValue: true }), - showDisk: factory.switch({ defaultValue: true }), - showGpu: factory.switch({ defaultValue: true }), - showLoadAvg: factory.switch({ defaultValue: true }), - showNet: factory.switch({ defaultValue: true }), - showTemp: factory.switch({ defaultValue: true }), - showBattery: factory.switch({ defaultValue: true }), - showServices: factory.switch({ defaultValue: true }), - showUptime: factory.switch({ defaultValue: true }), - showAgent: factory.switch({ defaultValue: true }), - }), - ); + return optionsBuilder.from((factory) => ({ + statusFilter: factory.select({ defaultValue: "all", options: statusOptions }), + sortBy: factory.select({ defaultValue: "name", options: sortOptions }), + sortDirection: factory.select({ defaultValue: "asc", options: sortDirectionOptions }), + showCpu: factory.switch({ defaultValue: true }), + showMemory: factory.switch({ defaultValue: true }), + showDisk: factory.switch({ defaultValue: true }), + showGpu: factory.switch({ defaultValue: true }), + showLoadAvg: factory.switch({ defaultValue: true }), + showNet: factory.switch({ defaultValue: true }), + showTemp: factory.switch({ defaultValue: true }), + showBattery: factory.switch({ defaultValue: true }), + showServices: factory.switch({ defaultValue: true }), + showUptime: factory.switch({ defaultValue: true }), + showAgent: factory.switch({ defaultValue: true }), + })); }, }).withDynamicImport(() => import("./component")); diff --git a/packages/widgets/src/beszel/_shared/chart.tsx b/packages/widgets/src/beszel/_shared/chart.tsx index 5a2ba1d58..f943e89cc 100644 --- a/packages/widgets/src/beszel/_shared/chart.tsx +++ b/packages/widgets/src/beszel/_shared/chart.tsx @@ -8,7 +8,6 @@ import dayjs from "dayjs"; import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; - const formatTime = (timestamp: string) => dayjs(timestamp).format("HH:mm"); const yAxisBase = { tickMargin: 0, tick: { fontSize: 10 } } as const; @@ -22,8 +21,14 @@ interface ChartPanelProps { export const ChartPanel = ({ title, subtitle, children }: ChartPanelProps) => ( - {title} - {subtitle && {subtitle}} + + {title} + + {subtitle && ( + + {subtitle} + + )} {children} @@ -41,7 +46,12 @@ export const BeszelAreaChart = ({ yAxisFormatter, yAxisDomain, yAxisProps, ...pr withDots={false} withXAxis withYAxis - yAxisProps={{ ...yAxisBase, ...(yAxisDomain ? { domain: yAxisDomain } : {}), tickFormatter: yAxisFormatter, ...yAxisProps }} + yAxisProps={{ + ...yAxisBase, + ...(yAxisDomain ? { domain: yAxisDomain } : {}), + tickFormatter: yAxisFormatter, + ...yAxisProps, + }} {...props} /> ); @@ -52,7 +62,7 @@ export const useSystemChartData = ( ) => useMemo(() => { if (!systemStats) return []; - return [...systemStats].reverse().map((r) => ({ + return [...systemStats].toReversed().map((r) => ({ time: formatTime(r.created), ...mapFn(r.stats), })); @@ -86,7 +96,7 @@ export const useDockerChartData = ( useMemo(() => { if (!containerStats?.length) return []; const extract = defaultContainerExtractors[metric]!; - return [...containerStats].reverse().map((record) => { + return [...containerStats].toReversed().map((record) => { const point: Record = { time: formatTime(record.created) }; for (const name of containerNames) { point[name] = extract(record.stats.find((c) => c.n === name)); diff --git a/packages/widgets/src/beszel/_shared/colors.ts b/packages/widgets/src/beszel/_shared/colors.ts index b2394602d..a1fb0cf98 100644 --- a/packages/widgets/src/beszel/_shared/colors.ts +++ b/packages/widgets/src/beszel/_shared/colors.ts @@ -26,7 +26,19 @@ export const alertColorMap: Record = { }; export const containerColors = [ - "blue.6", "teal.6", "orange.6", "grape.6", "cyan.6", - "pink.6", "lime.6", "violet.6", "indigo.6", "yellow.6", - "red.6", "green.6", "blue.4", "teal.4", "orange.4", + "blue.6", + "teal.6", + "orange.6", + "grape.6", + "cyan.6", + "pink.6", + "lime.6", + "violet.6", + "indigo.6", + "yellow.6", + "red.6", + "green.6", + "blue.4", + "teal.4", + "orange.4", ]; diff --git a/packages/widgets/src/beszel/_shared/tooltip.tsx b/packages/widgets/src/beszel/_shared/tooltip.tsx index 83d3984e1..8f89fa4d4 100644 --- a/packages/widgets/src/beszel/_shared/tooltip.tsx +++ b/packages/widgets/src/beszel/_shared/tooltip.tsx @@ -78,9 +78,7 @@ const PortalTooltipContent = ({ active, label, payload, formatter, showTotal }: if (!isActive) return null; - const sorted = [...(payload ?? [])] - .filter((p) => p.value > 0) - .sort((a, b) => b.value - a.value); + const sorted = [...(payload ?? [])].filter((p) => p.value > 0).toSorted((a, b) => b.value - a.value); if (!sorted.length) return null;