diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-05-29 18:57:37 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-05-29 18:57:37 +0000 |
| commit | 6cc0bfd1d79074df790272b8091b1f0226d14283 (patch) | |
| tree | cab18b0f708cf3b0eaeeeb98e2cf81459365b33e /frontend/src/lib/components/ServiceCard.svelte | |
| download | yaum-6cc0bfd1d79074df790272b8091b1f0226d14283.tar.gz yaum-6cc0bfd1d79074df790272b8091b1f0226d14283.zip | |
feat: upload project
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'frontend/src/lib/components/ServiceCard.svelte')
| -rw-r--r-- | frontend/src/lib/components/ServiceCard.svelte | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/frontend/src/lib/components/ServiceCard.svelte b/frontend/src/lib/components/ServiceCard.svelte new file mode 100644 index 0000000..29305f2 --- /dev/null +++ b/frontend/src/lib/components/ServiceCard.svelte @@ -0,0 +1,90 @@ +<script lang="ts"> + import UptimeTimeline from './UptimeTimeline.svelte'; + import type { Service, Heartbeat } from '$lib/types'; + + let { + service, + heartbeats = [], + ondeleted + }: { + service: Service; + heartbeats: Heartbeat[]; + ondeleted: (id: number) => void; + } = $props(); + + let isUp = $derived(service?.last_heartbeat?.is_up ?? true); + let lastMs = $derived(service?.last_heartbeat?.response_time_ms ?? null); + let statusColor = $derived(isUp ? 'var(--green)' : 'var(--red)'); + let bgClass = $derived(isUp ? 'bg-emerald-500/10 text-emerald-500' : 'bg-rose-500/10 text-rose-500'); + let dotGlow = $derived(isUp ? '0 0 6px var(--green)' : '0 0 6px var(--red)'); + + let uptime30d = $derived.by(() => { + const now = Date.now(); + const cutoff30d = now - 30 * 24 * 60 * 60 * 1000; + const recent = heartbeats.filter((h) => new Date(h.tested_at).getTime() >= cutoff30d); + if (recent.length === 0) return null; + return (recent.filter((h) => h.is_up).length / recent.length) * 100; + }); + + let uptimeColor = $derived(uptime30d !== null && uptime30d < 99 ? 'var(--red)' : 'var(--green)'); +</script> + +{#if service} +<div + class="group relative overflow-hidden rounded-2xl border border-[var(--border-color)] bg-[var(--bg-card)] p-5 transition-all duration-300 hover:border-[var(--border-color)]/60 hover:shadow-2xl hover:shadow-black/30 hover:-translate-y-0.5" +> + <div + class="pointer-events-none absolute -inset-px rounded-2xl opacity-0 transition-opacity duration-500 group-hover:opacity-100" + style="background: radial-gradient(600px circle at 50% 50%, {statusColor}06 0%, transparent 60%)" + ></div> + + <div class="relative z-10"> + <div class="mb-3 flex items-start justify-between gap-2"> + <div class="min-w-0 flex-1"> + <h3 class="truncate text-sm font-semibold text-white">{service.name}</h3> + <p class="mt-0.5 truncate font-mono text-xs text-[var(--text-muted)]">{service.url}</p> + </div> + <button + onclick={() => ondeleted(service.id)} + class="shrink-0 rounded-lg p-1.5 text-[var(--text-muted)] opacity-0 transition-all duration-200 hover:bg-rose-500/10 hover:text-rose-500 group-hover:opacity-100" + title="Remover serviço" + > + <svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> + <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /> + </svg> + </button> + </div> + + <div class="mb-4 flex items-center gap-3"> + <span class="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1 text-xs font-semibold tracking-wide {bgClass}"> + <span class="h-1.5 w-1.5 rounded-full" style="background-color: {statusColor}; box-shadow: {dotGlow}"></span> + {isUp ? 'UP' : 'DOWN'} + </span> + + {#if lastMs !== null} + <span class="font-mono text-xs tabular-nums text-[var(--text-muted)]"> + {lastMs}<span class="text-[var(--text-secondary)]">ms</span> + </span> + {/if} + + {#if uptime30d !== null} + <span + class="ml-auto font-mono text-xs tabular-nums" + style="color: {uptimeColor}" + > + {uptime30d.toFixed(2)}% + </span> + {/if} + </div> + + <UptimeTimeline {heartbeats} /> + + <a + href="/status/{service.id}" + class="mt-3 block text-center text-[10px] font-medium uppercase tracking-widest text-[var(--text-muted)] transition-colors hover:text-[var(--text-secondary)]" + > + Detalhes → + </a> + </div> +</div> +{/if} |