diff options
Diffstat (limited to 'frontend/src/routes')
| -rw-r--r-- | frontend/src/routes/+page.svelte | 82 |
1 files changed, 78 insertions, 4 deletions
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 172e6b8..6a57fd4 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { onMount } from 'svelte'; + import { onMount, tick } from 'svelte'; import { deleteService } from '$lib/api'; import ServiceCard from '$lib/components/ServiceCard.svelte'; import AddServiceModal from '$lib/components/AddServiceModal.svelte'; @@ -18,6 +18,12 @@ let error = $state(''); let showModal = $state(false); + let isKiosk = $state(false); + let kioskFloating = $state(false); + let kioskTimer: ReturnType<typeof setTimeout> | undefined; + let wakeLock: WakeLockSentinel | null = $state(null); + let pulseId = $state(0); + $effect(() => { if (!initialized && data) { services = [...data.services]; @@ -78,20 +84,60 @@ } } + function toggleKiosk() { + isKiosk = !isKiosk; + requestAnimationFrame(() => { + if (isKiosk) { + document.documentElement.classList.add('kiosk-mode'); + requestWakeLock(); + } else { + document.documentElement.classList.remove('kiosk-mode'); + releaseWakeLock(); + kioskFloating = false; + } + }); + } + + async function requestWakeLock() { + try { + if ('wakeLock' in navigator) { + wakeLock = await navigator.wakeLock.request('screen'); + wakeLock.addEventListener('release', () => { wakeLock = null; }); + } + } catch { /* wake lock not available */ } + } + + function releaseWakeLock() { + if (wakeLock) { + wakeLock.release(); + wakeLock = null; + } + } + + function handleKioskMouseMove() { + if (!isKiosk) return; + kioskFloating = true; + clearTimeout(kioskTimer); + kioskTimer = setTimeout(() => { kioskFloating = false; }, 3000); + } + onMount(() => { const unsub = subscribeHeartbeats((hb) => { let wasUp: boolean | undefined; + let changed = false; services = services.map((s) => { if (s.id !== hb.service_id) return s; if (s.last_heartbeat) { wasUp = s.last_heartbeat.is_up; } + changed = true; return { ...s, last_heartbeat: hb, heartbeats: [...s.heartbeats, hb].slice(-30) }; }); + if (changed) pulseId += 1; const svc = services.find((s) => s.id === hb.service_id); if (!svc) return; @@ -114,11 +160,38 @@ <title>YAUM — Yet Another Uptime Monitor</title> </svelte:head> -<div class="mb-8"> - <h1 class="text-2xl font-semibold tracking-tight text-white">Dashboard</h1> - <p class="mt-1 text-sm text-[var(--text-muted)]">Status dos serviços monitorados</p> +<div class="mb-8 flex items-start justify-between" class:kiosk-hidden={isKiosk}> + <div> + <h1 class="text-2xl font-semibold tracking-tight text-white">Dashboard</h1> + <p class="mt-1 text-sm text-[var(--text-muted)]">Status dos serviços monitorados</p> + </div> + <button + onclick={toggleKiosk} + class="kiosk-hidden shrink-0 rounded-xl border border-[var(--border-color)] bg-[var(--bg-card)] px-3.5 py-2 text-xs font-medium text-[var(--text-muted)] transition-all hover:border-[var(--green)]/30 hover:text-[var(--green)]" + title="Modo TV — tela cheia para monitores de parede" + > + <svg class="-ml-0.5 mr-1.5 inline h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"> + <path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25A2.25 2.25 0 015.25 3h13.5A2.25 2.25 0 0121 5.25z" /> + </svg> + Modo TV + </button> </div> +<!-- Floating exit button (kiosk only) --> +{#if isKiosk} + <button + onclick={toggleKiosk} + class="fixed bottom-6 right-6 z-50 rounded-xl border border-white/10 bg-black/60 px-4 py-2.5 text-xs font-medium text-white/70 backdrop-blur-sm transition-all duration-300 hover:bg-black/80 hover:text-white" + class:opacity-0={!kioskFloating} + class:opacity-100={kioskFloating} + class:pointer-events-none={!kioskFloating} + > + Sair do Modo TV + </button> +{/if} + +<!-- eslint-disable-next-line svelte/no-reactive-reassign --> +<div onmousemove={handleKioskMouseMove} role="region" class:kiosk-active={isKiosk} class:pulse={pulseId > 0} data-pulse={pulseId}> {#if error} <div class="rounded-2xl border border-[var(--red)]/20 bg-[var(--red)]/5 p-8 text-center"> <svg class="mx-auto mb-3 h-8 w-8 text-[var(--red)]/60" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"> @@ -164,6 +237,7 @@ <ActivityLog entries={logEntries} /> </div> {/if} +</div> <AddServiceModal show={showModal} |