feat(ui): show instance health badges and health panel
This commit is contained in:
@@ -17,6 +17,16 @@ type ActionLogSummary = Pick<
|
||||
Database["public"]["Tables"]["actions_log"]["Row"],
|
||||
"target_id" | "status" | "action" | "created_at" | "error_message"
|
||||
>;
|
||||
type HealthLogSummary = Pick<
|
||||
Database["public"]["Tables"]["actions_log"]["Row"],
|
||||
| "id"
|
||||
| "target_id"
|
||||
| "status"
|
||||
| "action"
|
||||
| "created_at"
|
||||
| "error_message"
|
||||
| "metadata"
|
||||
>;
|
||||
|
||||
function normalizeDomainLike(value: string) {
|
||||
return value.trim().toLowerCase().replace(/\.$/, "");
|
||||
@@ -49,6 +59,7 @@ export default async function DashboardPage() {
|
||||
{ data: domains },
|
||||
{ data: billing },
|
||||
{ data: autoSyncLogs },
|
||||
{ data: healthLogs },
|
||||
] = (await Promise.all([
|
||||
supabase
|
||||
.from("agencies")
|
||||
@@ -58,7 +69,7 @@ export default async function DashboardPage() {
|
||||
supabase
|
||||
.from("plesk_instances")
|
||||
.select(
|
||||
"id, name, base_url, auth_type, status, error_message, last_connected_at, last_sync_at, last_sync_status, last_sync_error, last_sync_subscriptions, last_sync_domains, auto_sync_enabled, auto_sync_frequency_minutes, last_auto_sync_at, next_auto_sync_at, auto_sync_lock_until",
|
||||
"id, name, base_url, auth_type, status, error_message, last_connected_at, last_sync_at, last_sync_status, last_sync_error, last_sync_subscriptions, last_sync_domains, auto_sync_enabled, auto_sync_frequency_minutes, last_auto_sync_at, next_auto_sync_at, auto_sync_lock_until, health_enabled, health_status, health_last_checked_at, health_last_ok_at, health_last_error, health_last_latency_ms, health_check_frequency_minutes, health_next_check_at, health_lock_until",
|
||||
)
|
||||
.eq("agency_id", context.agencyId)
|
||||
.order("created_at", { ascending: false }),
|
||||
@@ -89,6 +100,21 @@ export default async function DashboardPage() {
|
||||
.eq("action", "plesk_sync_auto")
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(200),
|
||||
supabase
|
||||
.from("actions_log")
|
||||
.select(
|
||||
"id, target_id, status, action, created_at, error_message, metadata",
|
||||
)
|
||||
.eq("agency_id", context.agencyId)
|
||||
.eq("target_type", "plesk_instance")
|
||||
.in("action", [
|
||||
"health_check_started",
|
||||
"health_check_ok",
|
||||
"health_check_degraded",
|
||||
"health_check_failed",
|
||||
])
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(400),
|
||||
])) as [
|
||||
{ data: AgencySummary | null },
|
||||
{
|
||||
@@ -110,6 +136,15 @@ export default async function DashboardPage() {
|
||||
last_auto_sync_at: string | null;
|
||||
next_auto_sync_at: string | null;
|
||||
auto_sync_lock_until: string | null;
|
||||
health_enabled: boolean;
|
||||
health_status: "ok" | "degraded" | "down" | "unknown";
|
||||
health_last_checked_at: string | null;
|
||||
health_last_ok_at: string | null;
|
||||
health_last_error: string | null;
|
||||
health_last_latency_ms: number | null;
|
||||
health_check_frequency_minutes: number;
|
||||
health_next_check_at: string | null;
|
||||
health_lock_until: string | null;
|
||||
}> | null;
|
||||
error: { message: string } | null;
|
||||
},
|
||||
@@ -138,6 +173,7 @@ export default async function DashboardPage() {
|
||||
},
|
||||
{ data: BillingSummary | null },
|
||||
{ data: ActionLogSummary[] | null },
|
||||
{ data: HealthLogSummary[] | null },
|
||||
];
|
||||
|
||||
const domainInstanceIds = Array.from(
|
||||
@@ -230,6 +266,33 @@ export default async function DashboardPage() {
|
||||
});
|
||||
}
|
||||
|
||||
const healthEventsByInstance = new Map<
|
||||
string,
|
||||
Array<{
|
||||
id: string;
|
||||
action: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
error_message: string | null;
|
||||
metadata: unknown;
|
||||
}>
|
||||
>();
|
||||
|
||||
for (const log of healthLogs ?? []) {
|
||||
if (!log.target_id) continue;
|
||||
const existing = healthEventsByInstance.get(log.target_id) ?? [];
|
||||
if (existing.length >= 10) continue;
|
||||
existing.push({
|
||||
id: log.id,
|
||||
action: log.action,
|
||||
status: log.status,
|
||||
created_at: log.created_at,
|
||||
error_message: log.error_message,
|
||||
metadata: log.metadata,
|
||||
});
|
||||
healthEventsByInstance.set(log.target_id, existing);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-6 py-8">
|
||||
<header className="flex items-center justify-between">
|
||||
@@ -312,6 +375,9 @@ export default async function DashboardPage() {
|
||||
};
|
||||
})}
|
||||
autoSyncByInstance={Object.fromEntries(autoSyncByInstance.entries())}
|
||||
healthEventsByInstance={Object.fromEntries(
|
||||
healthEventsByInstance.entries(),
|
||||
)}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user