aboutsummaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/routes/(admin)/admin/+page.svelte67
-rw-r--r--frontend/src/routes/status/[id]/+page.svelte44
-rw-r--r--frontend/src/routes/status/[id]/+page.ts9
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
};
};