From 9218d22954d61296a32c49373ab0f17f0be59cfe Mon Sep 17 00:00:00 2001
From: zwlucas <lucas.fariamo08@gmail.com>
Date: Fri, 29 May 2026 17:41:33 -0300
Subject: feat: add SSL info tracking and server metrics collection

Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
---
 frontend/src/routes/(admin)/admin/+page.svelte | 67 ++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

(limited to 'frontend/src/routes/(admin)/admin/+page.svelte')

diff --git a/frontend/src/routes/(admin)/admin/+page.svelte b/frontend/src/routes/(admin)/admin/+page.svelte
index 5be9154..7fa361d 100644
--- a/frontend/src/routes/(admin)/admin/+page.svelte
+++ b/frontend/src/routes/(admin)/admin/+page.svelte
@@ -14,6 +14,7 @@
 			services = data.services ?? [];
 			initialized = true;
 			fetchMaintenances();
+			fetchServerStats();
 		}
 	});
 	let editingSvc: any = $state(null);
@@ -29,6 +30,26 @@
 	let mtnSaving = $state(false);
 	let mtnFormError = $state('');
 
+	// server health state
+	let serverStats: { cpu_percent: number; memory_percent: number; disk_percent: number } | null = $state(null);
+	let serverStatsLoading = $state(true);
+	let serverStatsError = $state('');
+
+	async function fetchServerStats() {
+		serverStatsLoading = true;
+		serverStatsError = '';
+		try {
+			const res = await fetch(`${API}/admin/server-stats`, { headers: { Authorization: `Bearer ${token}` } });
+			if (await handleUnauthorized(res)) return;
+			if (!res.ok) throw new Error('Erro ao carregar');
+			serverStats = await res.json();
+		} catch {
+			serverStatsError = 'Falha ao carregar métricas do servidor';
+		} finally {
+			serverStatsLoading = false;
+		}
+	}
+
 	function apiHeaders() {
 		return { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` };
 	}
@@ -265,6 +286,52 @@
 	</a>
 </div>
 
+<!-- Saúde do Sistema -->
+<div class="mb-8">
+	<h2 class="mb-4 text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
+		Saúde do Sistema
+	</h2>
+	{#if serverStatsLoading}
+		<div class="grid grid-cols-3 gap-4">
+			{#each ['CPU', 'RAM', 'Disco'] as label}
+				<div class="rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] p-4">
+					<p class="text-[10px] font-medium uppercase tracking-wider text-[var(--text-muted)]">{label}</p>
+					<div class="mt-3 h-2 rounded-full bg-[var(--border-color)]"><div class="h-full w-1/3 rounded-full bg-[var(--border-color)]"></div></div>
+					<p class="mt-2 text-right font-mono text-xs text-[var(--text-muted)]">…</p>
+				</div>
+			{/each}
+		</div>
+	{:else if serverStats}
+		<div class="grid grid-cols-3 gap-4">
+			{#each [
+				{ label: 'CPU', pct: serverStats.cpu_percent },
+				{ label: 'RAM', pct: serverStats.memory_percent },
+				{ label: 'Disco', pct: serverStats.disk_percent },
+			] as item}
+				{@const color = item.pct >= 90 ? 'var(--red)' : item.pct >= 70 ? 'var(--amber)' : 'var(--green)'}
+				<div class="rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] p-4">
+					<div class="flex items-center justify-between">
+						<p class="text-[10px] font-medium uppercase tracking-wider text-[var(--text-muted)]">{item.label}</p>
+						<span class="font-mono text-lg font-bold tabular-nums" style="color: {color}">
+							{item.pct.toFixed(1)}<span class="text-xs font-normal text-[var(--text-muted)]">%</span>
+						</span>
+					</div>
+					<div class="mt-3 h-2 overflow-hidden rounded-full bg-[var(--border-color)]">
+						<div
+							class="h-full rounded-full transition-all duration-500"
+							style="width: {Math.min(item.pct, 100)}%; background-color: {color};"
+						></div>
+					</div>
+				</div>
+			{/each}
+		</div>
+	{:else if serverStatsError}
+		<div class="rounded-xl border border-[var(--red)]/20 bg-[var(--red)]/5 p-4 text-center">
+			<p class="text-xs text-[var(--text-muted)]">{serverStatsError}</p>
+		</div>
+	{/if}
+</div>
+
 <div class="mb-8">
 	<button
 		onclick={openCreate}
-- 
cgit v1.2.3

