diff --git a/apps/web/package.json b/apps/web/package.json index f65d3bf..49a9403 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -40,7 +40,7 @@ "@sgse-app/backend": "*", "@tanstack/svelte-form": "^1.19.2", "@types/papaparse": "^5.3.14", - "better-auth": "^1.3.34", + "better-auth": "catalog:", "convex": "catalog:", "convex-svelte": "^0.0.11", "date-fns": "^4.1.0", diff --git a/apps/web/src/app.d.ts b/apps/web/src/app.d.ts index 24b0b67..6633a80 100644 --- a/apps/web/src/app.d.ts +++ b/apps/web/src/app.d.ts @@ -5,3 +5,5 @@ declare global { } } } + +export {}; diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts index bb90b27..08c379c 100644 --- a/apps/web/src/hooks.server.ts +++ b/apps/web/src/hooks.server.ts @@ -1,6 +1,6 @@ import type { Handle } from "@sveltejs/kit"; -import { getToken } from "@mmailaender/convex-better-auth-svelte/sveltekit"; import { createAuth } from "@sgse-app/backend/convex/auth"; +import { getToken } from "@mmailaender/convex-better-auth-svelte/sveltekit"; export const handle: Handle = async ({ event, resolve }) => { event.locals.token = await getToken(createAuth, event.cookies); diff --git a/apps/web/src/routes/(dashboard)/+layout.server.ts b/apps/web/src/routes/(dashboard)/+layout.server.ts new file mode 100644 index 0000000..12b79a6 --- /dev/null +++ b/apps/web/src/routes/(dashboard)/+layout.server.ts @@ -0,0 +1,9 @@ +import { api } from "@sgse-app/backend/convex/_generated/api"; +import { createConvexHttpClient } from "@mmailaender/convex-better-auth-svelte/sveltekit"; + +export const load = async ({ locals }) => { + const client = createConvexHttpClient({ token: locals.token }); + + const currentUser = await client.query(api.auth.getCurrentUser, {}); + return { currentUser }; +}; diff --git a/apps/web/src/routes/(dashboard)/+page.svelte b/apps/web/src/routes/(dashboard)/+page.svelte index c3eb11c..5819f43 100644 --- a/apps/web/src/routes/(dashboard)/+page.svelte +++ b/apps/web/src/routes/(dashboard)/+page.svelte @@ -5,55 +5,72 @@ import { page } from "$app/stores"; import { goto } from "$app/navigation"; import { UserPlus, Mail } from "lucide-svelte"; + import { useAuth } from "@mmailaender/convex-better-auth-svelte/svelte"; + + let { data } = $props(); + + const auth = useAuth(); + const isLoading = $derived(auth.isLoading && !data.currentUser); + const isAuthenticated = $derived(auth.isAuthenticated || !!data.currentUser); + + $inspect({ isLoading, isAuthenticated }); // Queries para dados do dashboard const statsQuery = useQuery(api.dashboard.getStats, {}); const activityQuery = useQuery(api.dashboard.getRecentActivity, {}); - + // Queries para monitoramento em tempo real const statusSistemaQuery = useQuery(api.monitoramento.getStatusSistema, {}); - const atividadeBDQuery = useQuery(api.monitoramento.getAtividadeBancoDados, {}); - const distribuicaoQuery = useQuery(api.monitoramento.getDistribuicaoRequisicoes, {}); + const atividadeBDQuery = useQuery( + api.monitoramento.getAtividadeBancoDados, + {}, + ); + const distribuicaoQuery = useQuery( + api.monitoramento.getDistribuicaoRequisicoes, + {}, + ); // Estado para animações let mounted = $state(false); let currentTime = $state(new Date()); let showAlert = $state(false); - let alertType = $state<"auth_required" | "access_denied" | "invalid_token" | null>(null); + let alertType = $state< + "auth_required" | "access_denied" | "invalid_token" | null + >(null); let redirectRoute = $state(""); // Forçar atualização das queries de monitoramento a cada 1 segundo let refreshKey = $state(0); - + onMount(() => { mounted = true; - + // Verificar se há mensagem de erro na URL const urlParams = new URLSearchParams(window.location.search); const error = urlParams.get("error"); const route = urlParams.get("route") || urlParams.get("redirect") || ""; - + if (error) { alertType = error as any; redirectRoute = route; showAlert = true; - + // Limpar URL const newUrl = window.location.pathname; window.history.replaceState({}, "", newUrl); - + // Auto-fechar após 10 segundos setTimeout(() => { showAlert = false; }, 10000); } - + // Atualizar relógio e forçar refresh das queries a cada segundo const interval = setInterval(() => { currentTime = new Date(); refreshKey = (refreshKey + 1) % 1000; // Incrementar para forçar re-render }, 1000); - + return () => clearInterval(interval); }); @@ -67,25 +84,25 @@ return { title: "Autenticação Necessária", message: `Para acessar "${redirectRoute}", você precisa fazer login no sistema.`, - icon: "🔐" + icon: "🔐", }; case "access_denied": return { title: "Acesso Negado", message: `Você não tem permissão para acessar "${redirectRoute}". Entre em contato com a equipe de TI para solicitar acesso.`, - icon: "⛔" + icon: "⛔", }; case "invalid_token": return { title: "Sessão Expirada", message: "Sua sessão expirou. Por favor, faça login novamente.", - icon: "⏰" + icon: "⏰", }; default: return { title: "Aviso", message: "Ocorreu um erro. Tente novamente.", - icon: "⚠️" + icon: "⚠️", }; } } @@ -114,7 +131,13 @@ {#if showAlert} {@const alertData = getAlertMessage()} -
+
{alertData.icon}
@@ -123,7 +146,11 @@ {#if alertType === "access_denied"} {/if}
- +
{/if} -
-
+
+

{getSaudacao()}! 👋 @@ -174,11 +209,15 @@ {:else if statsQuery.data}
-
+
-

Total de Funcionários

+

+ Total de Funcionários +

{formatNumber(statsQuery.data.totalFuncionarios)}

@@ -186,19 +225,34 @@ {statsQuery.data.funcionariosAtivos} ativos

-
- {calcPercentage(statsQuery.data.funcionariosAtivos, statsQuery.data.totalFuncionarios)}% +
+ {calcPercentage( + statsQuery.data.funcionariosAtivos, + statsQuery.data.totalFuncionarios, + )}%
-
+
-

Solicitações Pendentes

+

+ Solicitações Pendentes +

{formatNumber(statsQuery.data.solicitacoesPendentes)}

@@ -207,8 +261,19 @@

- - + +
@@ -216,21 +281,37 @@
-
+
-

Símbolos Cadastrados

+

+ Símbolos Cadastrados +

{formatNumber(statsQuery.data.totalSimbolos)}

- {statsQuery.data.cargoComissionado} CC / {statsQuery.data.funcaoGratificada} FG + {statsQuery.data.cargoComissionado} CC / {statsQuery.data + .funcaoGratificada} FG

- - + +
@@ -239,21 +320,39 @@ {#if activityQuery.data} -
+
-

Atividade (24h)

+

+ Atividade (24h) +

- {formatNumber(activityQuery.data.funcionariosCadastrados24h + activityQuery.data.solicitacoesAcesso24h)} + {formatNumber( + activityQuery.data.funcionariosCadastrados24h + + activityQuery.data.solicitacoesAcesso24h, + )}

{activityQuery.data.funcionariosCadastrados24h} cadastros

- - + +
@@ -267,23 +366,41 @@ {@const status = statusSistemaQuery.data} {@const atividade = atividadeBDQuery.data} {@const distribuicao = distribuicaoQuery.data} - +
- - + +
-

Monitoramento em Tempo Real

+

+ Monitoramento em Tempo Real +

- Atualizado a cada segundo • {new Date(status.ultimaAtualizacao).toLocaleTimeString('pt-BR')} + Atualizado a cada segundo • {new Date( + status.ultimaAtualizacao, + ).toLocaleTimeString("pt-BR")}

- - + + LIVE
@@ -291,17 +408,38 @@
-
+
-

Usuários Online

-

{status.usuariosOnline}

-

sessões ativas

+

+ Usuários Online +

+

+ {status.usuariosOnline} +

+

+ sessões ativas +

- - + +
@@ -309,17 +447,38 @@
-
+
-

Total Registros

-

{status.totalRegistros.toLocaleString('pt-BR')}

-

no banco de dados

+

+ Total Registros +

+

+ {status.totalRegistros.toLocaleString("pt-BR")} +

+

+ no banco de dados +

- - + +
@@ -327,17 +486,36 @@
-
+
-

Tempo Resposta

-

{status.tempoMedioResposta}ms

+

+ Tempo Resposta +

+

+ {status.tempoMedioResposta}ms +

média atual

- - + +
@@ -345,24 +523,42 @@
-
+
-

Uso do Sistema

+

+ Uso do Sistema +

CPU - {status.cpuUsada}% + {status.cpuUsada}%
- +
Memória - {status.memoriaUsada}% + {status.memoriaUsada}%
- +
@@ -375,8 +571,12 @@
-

Atividade do Banco de Dados

-

Entradas e saídas em tempo real (último minuto)

+

+ Atividade do Banco de Dados +

+

+ Entradas e saídas em tempo real (último minuto) +

@@ -386,7 +586,9 @@
-
+
{#each [10, 8, 6, 4, 2, 0] as val} {val} {/each} @@ -395,30 +597,43 @@
- {#each Array.from({length: 6}) as _, i} -
+ {#each Array.from({ length: 6 }) as _, i} +
{/each}
{#each atividade.historico as ponto, idx} - {@const maxAtividade = Math.max(...atividade.historico.map(p => Math.max(p.entradas, p.saidas)))} + {@const maxAtividade = Math.max( + ...atividade.historico.map((p) => + Math.max(p.entradas, p.saidas), + ), + )}
-
-
- + -
+
↑ {ponto.entradas} entradas
↓ {ponto.saidas} saídas
@@ -428,10 +643,14 @@
-
- +
+ -
+
-60s -30s agora @@ -439,13 +658,19 @@
-
+
-
+
Entradas no BD
-
+
Saídas do BD
@@ -456,21 +681,35 @@
-

Tipos de Operações

+

+ Tipos de Operações +

Queries (Leituras) - {distribuicao.queries} + {distribuicao.queries}
- +
Mutations (Escritas) - {distribuicao.mutations} + {distribuicao.mutations}
- +
@@ -478,21 +717,35 @@
-

Operações no Banco

+

+ Operações no Banco +

Leituras - {distribuicao.leituras} + {distribuicao.leituras}
- +
Escritas - {distribuicao.escritas} + {distribuicao.escritas}
- +
@@ -501,7 +754,6 @@
{/if} -
@@ -528,18 +780,27 @@ -
+
+
@@ -549,7 +810,8 @@ Versão: 1.0.0

- Última Atualização: {new Date().toLocaleDateString("pt-BR")} + Última Atualização: + {new Date().toLocaleDateString("pt-BR")}

Suporte: TI SGSE diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte index 9a5c7ba..86935d1 100644 --- a/apps/web/src/routes/+layout.svelte +++ b/apps/web/src/routes/+layout.svelte @@ -1,118 +1,12 @@

diff --git a/apps/web/src/routes/api/auth/[...all]/+server.ts b/apps/web/src/routes/api/auth/[...all]/+server.ts index 54f83c8..dd7705e 100644 --- a/apps/web/src/routes/api/auth/[...all]/+server.ts +++ b/apps/web/src/routes/api/auth/[...all]/+server.ts @@ -1,8 +1,3 @@ import { createSvelteKitHandler } from "@mmailaender/convex-better-auth-svelte/sveltekit"; -import { PUBLIC_CONVEX_URL } from "$env/static/public"; -// PUBLIC_CONVEX_SITE_URL é necessário para o Better Auth handler -// Se não estiver definido, usar PUBLIC_CONVEX_URL como fallback -export const { GET, POST } = createSvelteKitHandler({ - convexSiteUrl: PUBLIC_CONVEX_URL, -}); +export const { GET, POST } = createSvelteKitHandler(); diff --git a/bun.lock b/bun.lock index 79be3e8..fbde11b 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ "@sgse-app/backend": "*", "@tanstack/svelte-form": "^1.19.2", "@types/papaparse": "^5.3.14", - "better-auth": "^1.3.34", + "better-auth": "catalog:", "convex": "catalog:", "convex-svelte": "^0.0.11", "date-fns": "^4.1.0", @@ -67,7 +67,7 @@ "dependencies": { "@convex-dev/better-auth": "^0.9.7", "@dicebear/avataaars": "^9.2.4", - "better-auth": "1.3.27", + "better-auth": "catalog:", "convex": "catalog:", "nodemailer": "^7.0.10", }, @@ -85,6 +85,7 @@ }, }, "catalog": { + "better-auth": "1.3.27", "convex": "^1.28.0", "typescript": "^5.9.2", }, @@ -155,8 +156,6 @@ "@better-auth/core": ["@better-auth/core@1.3.27", "", { "dependencies": { "better-call": "1.0.19", "zod": "^4.1.5" } }, "sha512-3Sfdax6MQyronY+znx7bOsfQHI6m1SThvJWb0RDscFEAhfqLy95k1sl+/PgGyg0cwc2cUXoEiAOSqYdFYrg3vA=="], - "@better-auth/telemetry": ["@better-auth/telemetry@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18" } }, "sha512-aQZ3wN90YMqV49diWxAMe1k7s2qb55KCsedCZne5PlgCjU4s3YtnqyjC5FEpzw2KY8l8rvR7DMAsDl13NjObKA=="], - "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="], "@better-fetch/fetch": ["@better-fetch/fetch@1.1.18", "", {}, "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="], @@ -841,8 +840,6 @@ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@better-auth/telemetry/@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="], - "@convex-dev/better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@sveltejs/kit/@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -865,8 +862,6 @@ "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - "web/better-auth": ["better-auth@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/telemetry": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-LWA52SlvnUBJRbN8VLSTLILPomZY3zZAiLxVJCeSQ5uVmaIKkMBhERitkfJcXB9RJcfl4uP+3EqKkb6hX1/uiw=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -921,8 +916,6 @@ "convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], - "web/better-auth/@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], diff --git a/package.json b/package.json index 9088df1..55d7a8f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ ], "catalog": { "convex": "^1.28.0", - "typescript": "^5.9.2" + "typescript": "^5.9.2", + "better-auth": "1.3.27" } }, "scripts": { diff --git a/packages/backend/package.json b/packages/backend/package.json index 8a1d530..f268b6b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -22,7 +22,7 @@ "dependencies": { "@convex-dev/better-auth": "^0.9.7", "@dicebear/avataaars": "^9.2.4", - "better-auth": "1.3.27", + "better-auth": "catalog:", "convex": "catalog:", "nodemailer": "^7.0.10" }