aboutsummaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/package-lock.json11
-rw-r--r--frontend/package.json1
-rw-r--r--frontend/src/hooks.server.js5
-rw-r--r--frontend/src/routes/(admin)/admin/+page.server.js8
-rw-r--r--frontend/src/routes/(admin)/admin/+page.svelte49
-rw-r--r--frontend/src/routes/+layout.svelte2
-rw-r--r--frontend/src/routes/login/+page.server.js7
-rw-r--r--frontend/src/routes/login/+page.svelte61
-rw-r--r--frontend/src/routes/status/[id]/+page.ts8
-rw-r--r--frontend/svelte.config.js6
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'
+ })
}
};