diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-05-29 21:24:59 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-05-29 21:24:59 +0000 |
| commit | bf8b1d0d49703e82cf8162399405984bd0b3fe88 (patch) | |
| tree | af4ed5fb7feae49cb3ebe1c8c3f87831e53b8273 /frontend | |
| parent | 17b159c563278c3b3b8b3884be37f8d5025ce0c7 (diff) | |
| download | yaum-bf8b1d0d49703e82cf8162399405984bd0b3fe88.tar.gz yaum-bf8b1d0d49703e82cf8162399405984bd0b3fe88.zip | |
feat: implement static file serving and Docker support, update frontend build process
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/package-lock.json | 11 | ||||
| -rw-r--r-- | frontend/package.json | 1 | ||||
| -rw-r--r-- | frontend/src/hooks.server.js | 5 | ||||
| -rw-r--r-- | frontend/src/routes/(admin)/admin/+page.server.js | 8 | ||||
| -rw-r--r-- | frontend/src/routes/(admin)/admin/+page.svelte | 49 | ||||
| -rw-r--r-- | frontend/src/routes/+layout.svelte | 2 | ||||
| -rw-r--r-- | frontend/src/routes/login/+page.server.js | 7 | ||||
| -rw-r--r-- | frontend/src/routes/login/+page.svelte | 61 | ||||
| -rw-r--r-- | frontend/src/routes/status/[id]/+page.ts | 8 | ||||
| -rw-r--r-- | frontend/svelte.config.js | 6 |
10 files changed, 128 insertions, 30 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 09bad19..2198ee5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "devDependencies": { "@sveltejs/adapter-auto": "^3.3.1", + "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.61.1", "@sveltejs/vite-plugin-svelte": "^4.0.4", "autoprefixer": "^10.5.0", @@ -937,6 +938,16 @@ "@sveltejs/kit": "^2.0.0" } }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, "node_modules/@sveltejs/kit": { "version": "2.61.1", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.61.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 557023a..a47f219 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ }, "devDependencies": { "@sveltejs/adapter-auto": "^3.3.1", + "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.61.1", "@sveltejs/vite-plugin-svelte": "^4.0.4", "autoprefixer": "^10.5.0", diff --git a/frontend/src/hooks.server.js b/frontend/src/hooks.server.js index 39c89e9..0c53a3d 100644 --- a/frontend/src/hooks.server.js +++ b/frontend/src/hooks.server.js @@ -1,7 +1,8 @@ import { redirect } from '@sveltejs/kit'; -const API = 'http://localhost:8080/api'; +import { building } from '$app/environment'; +const API_BASE = building ? 'http://localhost:8080/api' : '/api'; const PUBLIC_PREFIXES = ['/_app', '/api']; export async function handle({ event, resolve }) { @@ -18,7 +19,7 @@ export async function handle({ event, resolve }) { if (!token) { throw redirect(302, '/login'); } - const res = await fetch(`${API}/auth/verify`, { + const res = await fetch(`${API_BASE}/auth/verify`, { headers: { Authorization: `Bearer ${token}` } }); if (!res.ok) { diff --git a/frontend/src/routes/(admin)/admin/+page.server.js b/frontend/src/routes/(admin)/admin/+page.server.js index bdf498b..f0a6f44 100644 --- a/frontend/src/routes/(admin)/admin/+page.server.js +++ b/frontend/src/routes/(admin)/admin/+page.server.js @@ -1,11 +1,13 @@ -const API = 'http://localhost:8080/api'; +import { building } from '$app/environment'; + +const API_BASE = building ? 'http://localhost:8080/api' : '/api'; export async function load({ locals }) { const token = locals.token; const headers = token ? { Authorization: `Bearer ${token}` } : {}; - const res = await fetch(`${API}/services`, { headers }); + const res = await fetch(`${API_BASE}/services`, { headers }); if (!res.ok) { return { services: [], error: 'Falha ao carregar serviços', token: token ?? '' }; } @@ -14,7 +16,7 @@ export async function load({ locals }) { const servicesWithStats = await Promise.all( services.map(async (svc) => { try { - const statsRes = await fetch(`${API}/services/${svc.id}/stats`, { headers }); + const statsRes = await fetch(`${API_BASE}/services/${svc.id}/stats`, { headers }); if (statsRes.ok) { svc.stats = await statsRes.json(); } diff --git a/frontend/src/routes/(admin)/admin/+page.svelte b/frontend/src/routes/(admin)/admin/+page.svelte index 7fa361d..9a48ba0 100644 --- a/frontend/src/routes/(admin)/admin/+page.svelte +++ b/frontend/src/routes/(admin)/admin/+page.svelte @@ -1,5 +1,7 @@ <script lang="ts"> - const API = 'http://localhost:8080/api'; + import { onMount } from 'svelte'; + + const API = '/api'; let { data } = $props(); @@ -8,15 +10,42 @@ let services = $state<any[]>([]); let showForm = $state(false); - $effect(() => { - if (!initialized && data) { - token = data.token; - services = data.services ?? []; - initialized = true; - fetchMaintenances(); - fetchServerStats(); + function getCookie(name: string) { + return document.cookie.split('; ').find(r => r.startsWith(name + '='))?.split('=')[1] ?? ''; + } + + async function bootstrap() { + if (initialized) return; + initialized = true; + token = data?.token || getCookie('auth_token') || ''; + if (data?.services && data.services.length > 0) { + services = data.services; + } else { + await fetchServices(); } - }); + fetchMaintenances(); + fetchServerStats(); + } + + async function fetchServices() { + try { + const res = await fetch(`${API}/services`, { headers: { Authorization: `Bearer ${token}` } }); + if (await handleUnauthorized(res)) return; + if (res.ok) { + const svcs = await res.json(); + const withStats = await Promise.all( + svcs.map(async (svc: any) => { + try { + const sr = await fetch(`${API}/services/${svc.id}/stats`, { headers: { Authorization: `Bearer ${token}` } }); + if (sr.ok) svc.stats = await sr.json(); + } catch { /* ignore */ } + return svc; + }) + ); + services = withStats; + } + } catch { /* ignore */ } + } let editingSvc: any = $state(null); let saving = $state(false); let formError = $state(''); @@ -24,6 +53,8 @@ let toggling = $state<Record<number, boolean>>({}); // maintenance state + onMount(bootstrap); + let maintenances = $state<any[]>([]); let showMtnForm = $state(false); let editingMtn: any = $state(null); diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 9ed74ce..8dbfcda 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -8,7 +8,7 @@ onMount(async () => { try { - const res = await fetch('http://localhost:8080/api/active-maintenance'); + const res = await fetch('/api/active-maintenance'); if (res.ok) { const data = await res.json(); maintenance = data.maintenance ?? null; diff --git a/frontend/src/routes/login/+page.server.js b/frontend/src/routes/login/+page.server.js index 4272ef7..a0e9f7f 100644 --- a/frontend/src/routes/login/+page.server.js +++ b/frontend/src/routes/login/+page.server.js @@ -1,11 +1,12 @@ import { redirect } from '@sveltejs/kit'; +import { building } from '$app/environment'; -const API = 'http://localhost:8080/api'; +const API_BASE = building ? 'http://localhost:8080/api' : '/api'; export async function load({ cookies }) { const token = cookies.get('auth_token'); if (token) { - const res = await fetch(`${API}/auth/verify`, { + const res = await fetch(`${API_BASE}/auth/verify`, { headers: { Authorization: `Bearer ${token}` } }); if (res.ok) { @@ -25,7 +26,7 @@ export const actions = { return { error: 'Preencha todos os campos' }; } - const res = await fetch(`${API}/auth/login`, { + const res = await fetch(`${API_BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index a472c89..2b12a75 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -1,5 +1,53 @@ <script lang="ts"> - let { form }: { form?: { error?: string } } = $props(); + import { onMount } from 'svelte'; + import { goto } from '$app/navigation'; + + let errorMsg = $state(''); + let loading = $state(false); + + async function handleSubmit(e: Event) { + e.preventDefault(); + loading = true; + errorMsg = ''; + const formData = new FormData(e.target as HTMLFormElement); + try { + const res = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: formData.get('username'), + password: formData.get('password') + }) + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + errorMsg = err.error || 'Credenciais inválidas'; + return; + } + const { token } = await res.json(); + document.cookie = `auth_token=${token}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Lax`; + goto('/admin'); + } catch { + errorMsg = 'Erro de conexão com o servidor'; + } finally { + loading = false; + } + } + + onMount(async () => { + const hasToken = document.cookie.split(';').some(c => c.trim().startsWith('auth_token=')); + if (!hasToken) return; + try { + const res = await fetch('/api/auth/verify', { + headers: { Authorization: `Bearer ${getCookie('auth_token')}` } + }); + if (res.ok) goto('/admin'); + } catch { /* ignore */ } + }); + + function getCookie(name: string) { + return document.cookie.split('; ').find(r => r.startsWith(name + '='))?.split('=')[1] ?? ''; + } </script> <svelte:head> @@ -14,7 +62,7 @@ </div> <div class="rounded-2xl border border-[var(--border-color)] bg-[var(--bg-card)] p-6"> - <form method="POST" class="space-y-4"> + <form onsubmit={handleSubmit} class="space-y-4"> <div> <label for="username" class="mb-1.5 block text-xs font-medium text-[var(--text-secondary)]">Usuário</label> <input @@ -41,15 +89,16 @@ /> </div> - {#if form?.error} - <p class="rounded-lg bg-[var(--red)]/5 px-3 py-2 text-xs text-[var(--red)]">{form.error}</p> + {#if errorMsg} + <p class="rounded-lg bg-[var(--red)]/5 px-3 py-2 text-xs text-[var(--red)]">{errorMsg}</p> {/if} <button type="submit" - class="w-full rounded-xl border border-[var(--green)]/50 bg-[var(--green)]/10 px-4 py-2.5 text-sm font-medium text-[var(--green)] transition-all hover:bg-[var(--green)]/20" + disabled={loading} + class="w-full rounded-xl border border-[var(--green)]/50 bg-[var(--green)]/10 px-4 py-2.5 text-sm font-medium text-[var(--green)] transition-all hover:bg-[var(--green)]/20 disabled:opacity-50" > - Entrar + {loading ? 'Entrando…' : 'Entrar'} </button> </form> </div> diff --git a/frontend/src/routes/status/[id]/+page.ts b/frontend/src/routes/status/[id]/+page.ts index 13a72b1..6e77c6e 100644 --- a/frontend/src/routes/status/[id]/+page.ts +++ b/frontend/src/routes/status/[id]/+page.ts @@ -4,10 +4,10 @@ export const load: PageLoad = async ({ params, fetch }) => { const id = params.id; 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}/ssl`).catch(() => null) + fetch(`/api/services`).catch(() => null), + fetch(`/api/services/${id}/history`).catch(() => null), + fetch(`/api/services/${id}/stats`).catch(() => null), + fetch(`/api/services/${id}/ssl`).catch(() => null) ]); const services = servicesRes?.ok ? await servicesRes.json() : []; diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js index fcf06bd..0d48f92 100644 --- a/frontend/svelte.config.js +++ b/frontend/svelte.config.js @@ -1,11 +1,13 @@ -import adapter from '@sveltejs/adapter-auto'; +import adapterStatic from '@sveltejs/adapter-static'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), kit: { - adapter: adapter() + adapter: adapterStatic({ + fallback: '200.html' + }) } }; |