From 627a34728ec851a4c69ea0832cf8d40ae995514a Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 5 Mar 2026 18:14:25 +0000 Subject: [PATCH] feat(ui): add dashboard health and autosync overview widget --- app/dashboard/page.tsx | 274 +++++++++++++++++++++++++++++++++++------ 1 file changed, 234 insertions(+), 40 deletions(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index b075711..41f59d3 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,6 +1,7 @@ import { SignOutButton } from "@/components/auth/sign-out-button"; import { DashboardControls } from "@/components/dashboard/dashboard-controls"; import { getServerAgencyContextOrRedirect } from "@/lib/agency"; +import { formatDateTime } from "@/lib/dates"; import type { Database } from "@/lib/types"; import { createSupabaseServerClient } from "@/lib/supabase/server"; import Link from "next/link"; @@ -17,6 +18,10 @@ type ActionLogSummary = Pick< Database["public"]["Tables"]["actions_log"]["Row"], "target_id" | "status" | "action" | "created_at" | "error_message" >; +type AutosyncLatestLogSummary = Pick< + Database["public"]["Tables"]["actions_log"]["Row"], + "created_at" +>; type HealthLogSummary = Pick< Database["public"]["Tables"]["actions_log"]["Row"], | "id" @@ -28,6 +33,64 @@ type HealthLogSummary = Pick< | "metadata" >; +const RELATIVE_TIME_FORMATTER = new Intl.RelativeTimeFormat("en", { + numeric: "auto", +}); + +function getLatestTimestamp(values: Array) { + let latestIso: string | null = null; + let latestMs = Number.NEGATIVE_INFINITY; + + for (const value of values) { + if (!value) continue; + const parsed = new Date(value).getTime(); + if (Number.isNaN(parsed)) continue; + if (parsed > latestMs) { + latestMs = parsed; + latestIso = value; + } + } + + return latestIso; +} + +function formatRelativeTime(value?: string | null) { + if (!value) return null; + + const targetMs = new Date(value).getTime(); + if (Number.isNaN(targetMs)) return null; + + const deltaSeconds = Math.round((targetMs - Date.now()) / 1000); + const absSeconds = Math.abs(deltaSeconds); + + if (absSeconds < 60) { + return RELATIVE_TIME_FORMATTER.format(deltaSeconds, "second"); + } + + const deltaMinutes = Math.round(deltaSeconds / 60); + if (Math.abs(deltaMinutes) < 60) { + return RELATIVE_TIME_FORMATTER.format(deltaMinutes, "minute"); + } + + const deltaHours = Math.round(deltaMinutes / 60); + if (Math.abs(deltaHours) < 24) { + return RELATIVE_TIME_FORMATTER.format(deltaHours, "hour"); + } + + const deltaDays = Math.round(deltaHours / 24); + if (Math.abs(deltaDays) < 30) { + return RELATIVE_TIME_FORMATTER.format(deltaDays, "day"); + } + + const deltaMonths = Math.round(deltaDays / 30); + if (Math.abs(deltaMonths) < 12) { + return RELATIVE_TIME_FORMATTER.format(deltaMonths, "month"); + } + + const deltaYears = Math.round(deltaMonths / 12); + return RELATIVE_TIME_FORMATTER.format(deltaYears, "year"); +} + function normalizeDomainLike(value: string) { return value.trim().toLowerCase().replace(/\.$/, ""); } @@ -59,6 +122,7 @@ export default async function DashboardPage() { { data: domains }, { data: billing }, { data: autoSyncLogs }, + { data: latestAutoSyncLog }, { data: healthLogs }, ] = (await Promise.all([ supabase @@ -100,6 +164,16 @@ export default async function DashboardPage() { .eq("action", "plesk_sync_auto") .order("created_at", { ascending: false }) .limit(200), + supabase + .from("actions_log") + .select("created_at") + .eq("agency_id", context.agencyId) + .eq("target_type", "plesk_instance") + .eq("action", "autosync_completed") + .eq("status", "success") + .order("created_at", { ascending: false }) + .limit(1) + .maybeSingle(), supabase .from("actions_log") .select( @@ -173,6 +247,7 @@ export default async function DashboardPage() { }, { data: BillingSummary | null }, { data: ActionLogSummary[] | null }, + { data: AutosyncLatestLogSummary | null }, { data: HealthLogSummary[] | null }, ]; @@ -293,6 +368,40 @@ export default async function DashboardPage() { healthEventsByInstance.set(log.target_id, existing); } + const healthCounts = { ok: 0, degraded: 0, down: 0, unknown: 0 }; + + for (const instance of instances ?? []) { + const status = (instance.health_status ?? "unknown") as + | "ok" + | "degraded" + | "down" + | "unknown"; + if (status === "ok") healthCounts.ok += 1; + else if (status === "degraded") healthCounts.degraded += 1; + else if (status === "down") healthCounts.down += 1; + else healthCounts.unknown += 1; + } + + const latestHealthCheckAt = getLatestTimestamp( + (instances ?? []).map((instance) => instance.health_last_checked_at), + ); + + const autoSyncEnabledCount = (instances ?? []).filter( + (instance) => instance.auto_sync_enabled, + ).length; + + const latestAutoSyncFromInstances = getLatestTimestamp( + (instances ?? []).map((instance) => instance.last_auto_sync_at), + ); + + const latestAutoSyncAt = + latestAutoSyncFromInstances ?? latestAutoSyncLog?.created_at ?? null; + + const downInstanceNames = (instances ?? []) + .filter((instance) => instance.health_status === "down") + .slice(0, 3) + .map((instance) => instance.name || instance.base_url); + return (
@@ -313,6 +422,89 @@ export default async function DashboardPage() {
+
+
+
+

+ Instance Health +

+
+ + Ok: {healthCounts.ok} + + + Degraded: {healthCounts.degraded} + + + Down: {healthCounts.down} + + + Unknown: {healthCounts.unknown} + +
+
+ + {healthCounts.down > 0 ? ( +
+ {healthCounts.down} instance{healthCounts.down === 1 ? "" : "s"}{" "} + down + {downInstanceNames.length > 0 + ? `: ${downInstanceNames.join(", ")}${healthCounts.down > downInstanceNames.length ? ", …" : ""}` + : ""} +
+ ) : null} + +
+

+ + Last Health Check: + {" "} + + {formatRelativeTime(latestHealthCheckAt) ?? "Never"} + +

+

+ Auto Sync:{" "} + Enabled on {autoSyncEnabledCount} instance + {autoSyncEnabledCount === 1 ? "" : "s"} +

+

+ Last run:{" "} + + {formatRelativeTime(latestAutoSyncAt) ?? "Not yet run"} + +

+
+ +
+ + Instances + + + Activity + +
+
+
+

Plesk Instances

@@ -338,47 +530,49 @@ export default async function DashboardPage() { Instances query failed: {instancesError.message}
) : null} - { - const joinedStatusByLocalId = domain.subscription_id - ? subscriptionStatusById.get(domain.subscription_id) - : null; - const joinedStatusByExternalId = domain.external_subscription_id - ? subscriptionStatusByExternalId.get( - `${domain.plesk_instance_id}:${domain.external_subscription_id}`, - ) - : null; - const joinedStatusByName = subscriptionStatusByInstanceAndName.get( - `${domain.plesk_instance_id}:${normalizeDomainLike(domain.domain_name)}`, - ); - const joinedStatusBySuffix = findBestSuffixStatusMatch( - normalizeDomainLike(domain.domain_name), - subscriptionStatusNameCandidatesByInstance.get( - domain.plesk_instance_id, - ) ?? [], - ); - const joinedStatus = - joinedStatusByLocalId ?? - joinedStatusByExternalId ?? - joinedStatusByName ?? - joinedStatusBySuffix; +
+ { + const joinedStatusByLocalId = domain.subscription_id + ? subscriptionStatusById.get(domain.subscription_id) + : null; + const joinedStatusByExternalId = domain.external_subscription_id + ? subscriptionStatusByExternalId.get( + `${domain.plesk_instance_id}:${domain.external_subscription_id}`, + ) + : null; + const joinedStatusByName = subscriptionStatusByInstanceAndName.get( + `${domain.plesk_instance_id}:${normalizeDomainLike(domain.domain_name)}`, + ); + const joinedStatusBySuffix = findBestSuffixStatusMatch( + normalizeDomainLike(domain.domain_name), + subscriptionStatusNameCandidatesByInstance.get( + domain.plesk_instance_id, + ) ?? [], + ); + const joinedStatus = + joinedStatusByLocalId ?? + joinedStatusByExternalId ?? + joinedStatusByName ?? + joinedStatusBySuffix; - return { - ...domain, - status: joinedStatus ?? "unknown", - subscription_name: domain.subscription_id - ? (subscriptionDisplayNameById.get(domain.subscription_id) ?? - null) - : null, - }; - })} - autoSyncByInstance={Object.fromEntries(autoSyncByInstance.entries())} - healthEventsByInstance={Object.fromEntries( - healthEventsByInstance.entries(), - )} - /> + return { + ...domain, + status: joinedStatus ?? "unknown", + subscription_name: domain.subscription_id + ? (subscriptionDisplayNameById.get(domain.subscription_id) ?? + null) + : null, + }; + })} + autoSyncByInstance={Object.fromEntries(autoSyncByInstance.entries())} + healthEventsByInstance={Object.fromEntries( + healthEventsByInstance.entries(), + )} + /> +
); }