From 813cad613ef5593f37cfaf66f4ed3098e3a0d217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Mon, 17 Aug 2026 21:11:56 +0200 Subject: [PATCH] feat: simplify traefik widget to a route count, add coolify resource count Traefik's widget listed every router with rule/TLS/status detail, which reads as noise now that other widgets in the dashboard show a single headline stat - collapse it to a route count (with an "enabled" secondary stat when some routers aren't) plus the existing middlewares count as a small footer line, same visual language as the new service widgets. Coolify's widget only reported project count; /api/v1/resources gives the deployed-resource count (apps/services/databases across all projects), which is the more useful "how much is actually running" number, so it's added as a second stat alongside Projects. --- components/widgets/traefik/Widget.tsx | 38 +++++++++++++-------------- lib/collectors/services/coolify.ts | 26 ++++++++++++------ 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/components/widgets/traefik/Widget.tsx b/components/widgets/traefik/Widget.tsx index 4299789..410d57e 100644 --- a/components/widgets/traefik/Widget.tsx +++ b/components/widgets/traefik/Widget.tsx @@ -1,6 +1,5 @@ "use client"; -import { Lock } from "@phosphor-icons/react/ssr"; import type { Widget } from "@/lib/config/schema"; import { useWidgetSubscription } from "@/lib/ws/client"; import { SkeletonLines } from "@/components/widgets/Skeleton"; @@ -12,6 +11,7 @@ export function TraefikWidget({ widget, widgetId }: { widget: TraefikWidget; wid const result = useWidgetSubscription(widgetId); const data = result?.type === "traefik" ? result.data : null; const errorMessage = result?.type === "error" ? result.message : null; + const enabledCount = data?.routers.filter((router) => router.status === "enabled").length ?? 0; return ( @@ -19,34 +19,34 @@ export function TraefikWidget({ widget, widgetId }: { widget: TraefikWidget; wid {widget.name} {data && ( - {data.entrypoints.map((ep) => ep.address).join(" ยท ") || `${data.entrypoints.length} entrypoints`} + {data.entrypoints.length} entrypoints )} {errorMessage && {errorMessage}} {!result && !errorMessage && } {data && ( -
- {data.routers.map((router) => ( -
- - {router.name.replace(/@.*$/, "")} - - - {router.tls && } - - - {router.status} - + <> +
+
+ + {data.routers.length} + Routes
- ))} - + {enabledCount !== data.routers.length && ( +
+ + {enabledCount} + + Enabled +
+ )} +
+ {data.middlewaresCount} middlewares -
+ )} ); diff --git a/lib/collectors/services/coolify.ts b/lib/collectors/services/coolify.ts index ec11e87..9eb23d4 100644 --- a/lib/collectors/services/coolify.ts +++ b/lib/collectors/services/coolify.ts @@ -8,23 +8,33 @@ interface CoolifyProject { name: string; } -export async function collectCoolify( - widget: CoolifyServiceWidget -): Promise<{ stats: ServiceStat[]; detail?: string[] }> { - const base = serviceBaseUrl(widget); - const response = await fetch(`${base}/api/v1/projects`, { - headers: { Authorization: `Bearer ${widget.apiToken}` }, +async function fetchCoolify(base: string, path: string, apiToken: string): Promise { + const response = await fetch(`${base}${path}`, { + headers: { Authorization: `Bearer ${apiToken}` }, signal: AbortSignal.timeout(5_000), }); if (!response.ok) { throw new Error(`Coolify API returned ${response.status}`); } - const projects = (await response.json()) as CoolifyProject[]; + return (await response.json()) as T; +} + +export async function collectCoolify( + widget: CoolifyServiceWidget +): Promise<{ stats: ServiceStat[]; detail?: string[] }> { + const base = serviceBaseUrl(widget); + const [projects, resources] = await Promise.all([ + fetchCoolify(base, "/api/v1/projects", widget.apiToken), + fetchCoolify(base, "/api/v1/resources", widget.apiToken), + ]); const names = projects.slice(0, DETAIL_LIMIT).map((p) => p.name); if (projects.length > DETAIL_LIMIT) names.push(`+${projects.length - DETAIL_LIMIT} more`); return { - stats: [{ label: "Projects", value: String(projects.length) }], + stats: [ + { label: "Projects", value: String(projects.length) }, + { label: "Resources", value: String(resources.length) }, + ], detail: names.length > 0 ? names : undefined, }; }