diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-05-29 20:41:33 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-05-29 20:41:33 +0000 |
| commit | 9218d22954d61296a32c49373ab0f17f0be59cfe (patch) | |
| tree | 595b94ce7c2b5eed9760f79e7781653d6b554d18 /frontend/src/routes | |
| parent | d186a989b6808ff39fcd43a9f0b478aebc4aa346 (diff) | |
| download | yaum-9218d22954d61296a32c49373ab0f17f0be59cfe.tar.gz yaum-9218d22954d61296a32c49373ab0f17f0be59cfe.zip | |
feat: add SSL info tracking and server metrics collection
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'frontend/src/routes')
| -rw-r--r-- | frontend/src/routes/(admin)/admin/+page.svelte | 67 | ||||
| -rw-r--r-- | frontend/src/routes/status/[id]/+page.svelte | 44 | ||||
| -rw-r--r-- | frontend/src/routes/status/[id]/+page.ts | 9 |
3 files changed, 117 insertions, 3 deletions
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} diff --git a/frontend/src/routes/status/[id]/+page.svelte b/frontend/src/routes/status/[id]/+page.svelte index 0701eeb..d47797d 100644 --- a/frontend/src/routes/status/[id]/+page.svelte +++ b/frontend/src/routes/status/[id]/+page.svelte @@ -126,6 +126,50 @@ {/if} </div> + <!-- SSL Card --> + {#if data.sslInfo} + {@const ssl = data.sslInfo} + {@const isExpiring = ssl.days_remaining < 7} + <div + class="mb-6 overflow-hidden rounded-xl border p-5 transition-all" + style="border-color: {isExpiring ? 'rgba(250,204,21,0.3)' : 'rgba(34,240,106,0.2)'}; background-color: {isExpiring ? 'rgba(250,204,21,0.04)' : 'rgba(34,240,106,0.03)'}" + > + <div class="flex items-start gap-4"> + <div + class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg" + style="background-color: {isExpiring ? 'rgba(250,204,21,0.12)' : 'rgba(34,240,106,0.1)'}" + > + <svg class="h-5 w-5" style="color: {isExpiring ? '#facc15' : 'var(--green)'}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"> + <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /> + </svg> + </div> + <div class="min-w-0 flex-1"> + <div class="flex items-center justify-between gap-2"> + <h2 class="text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]"> + Segurança SSL/TLS + </h2> + <span + class="inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-[10px] font-semibold" + style="background-color: {isExpiring ? 'rgba(250,204,21,0.12)' : 'rgba(34,240,106,0.1)'}; color: {isExpiring ? '#facc15' : 'var(--green)'}" + > + {ssl.days_remaining >= 0 ? 'Válido' : 'Expirado'} + </span> + </div> + <p class="mt-2 text-sm font-medium text-white"> + {ssl.issuer} + </p> + <p class="mt-0.5 text-xs" style="color: {isExpiring ? '#facc15' : 'var(--text-muted)'}"> + {ssl.days_remaining >= 0 + ? isExpiring + ? '⚠ Certificado expira em ' + ssl.days_remaining + ' dia' + (ssl.days_remaining === 1 ? '' : 's') + : 'Certificado válido por mais ' + ssl.days_remaining + ' dias' + : '⚠ Certificado expirado há ' + Math.abs(ssl.days_remaining) + ' dias'} + </p> + </div> + </div> + </div> + {/if} + <div class="grid gap-6 lg:grid-cols-5"> <!-- SLA Breakdown --> {#if stats} diff --git a/frontend/src/routes/status/[id]/+page.ts b/frontend/src/routes/status/[id]/+page.ts index 1b91c67..13a72b1 100644 --- a/frontend/src/routes/status/[id]/+page.ts +++ b/frontend/src/routes/status/[id]/+page.ts @@ -3,10 +3,11 @@ import type { PageLoad } from './$types'; export const load: PageLoad = async ({ params, fetch }) => { const id = params.id; - const [servicesRes, historyRes, statsRes] = await Promise.all([ + const [servicesRes, historyRes, statsRes, sslRes] = await Promise.all([ fetch(`http://localhost:8080/api/services`).catch(() => null), fetch(`http://localhost:8080/api/services/${id}/history`).catch(() => null), - fetch(`http://localhost:8080/api/services/${id}/stats`).catch(() => null) + fetch(`http://localhost:8080/api/services/${id}/stats`).catch(() => null), + fetch(`http://localhost:8080/api/services/${id}/ssl`).catch(() => null) ]); const services = servicesRes?.ok ? await servicesRes.json() : []; @@ -14,10 +15,12 @@ export const load: PageLoad = async ({ params, fetch }) => { const historyJson = historyRes?.ok ? await historyRes.json() : []; const history = Array.isArray(historyJson) ? historyJson : (historyJson.data ?? []); const stats = statsRes?.ok ? await statsRes.json() : null; + const sslInfo = sslRes?.ok ? await sslRes.json() : null; return { service: svc ?? null, history, - stats + stats, + sslInfo }; }; |