From 17364e23d34d3b7619bc26d55a3f3670638b42bf Mon Sep 17 00:00:00 2001 From: litoral05 Date: Tue, 12 May 2026 11:59:54 +0100 Subject: [PATCH] Fixed responsiveness --- src-tauri/tauri.conf.json | 64 +- src/app/routes.tsx | 118 +-- src/components/activity/ActivityLogs.tsx | 566 +++++++------- src/components/dashboard/IpPoolChart.tsx | 60 +- src/components/dashboard/MetricCard.tsx | 17 +- .../dashboard/NetworkTrafficChart.tsx | 22 +- src/components/layout/AppShell.tsx | 2 +- src/components/layout/Sidebar.tsx | 44 +- src/components/layout/TopBar.tsx | 64 +- .../provisioning/ProvisioningWizard.tsx | 727 +++++++++--------- src/components/settings/BackendSettings.tsx | 317 ++++---- src/components/udp2raw/Udp2rawConfig.tsx | 405 +++++----- 12 files changed, 1240 insertions(+), 1166 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e0bfc01..763d8ac 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,35 +1,35 @@ { - "$schema": "https://schema.tauri.app/config/2", - "productName": "Litoral Regas VPN Orchestrator", - "version": "0.1.0", - "identifier": "com.litoralregas.vpnorchestrator", - "build": { - "beforeDevCommand": "npm run dev", - "devUrl": "http://localhost:1420", - "beforeBuildCommand": "npm run build", - "frontendDist": "../dist" - }, - "app": { - "windows": [ - { - "title": "Litoral Regas VPN Orchestrator", - "width": 1440, - "height": 980, - "minWidth": 1100, - "minHeight": 720, - "resizable": false, - "maximized": true - } - ], - "security": { - "csp": null - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/icon.ico" - ] + "$schema": "https://schema.tauri.app/config/2", + "productName": "Litoral Regas VPN Orchestrator", + "version": "0.1.0", + "identifier": "com.litoralregas.vpnorchestrator", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Litoral Regas VPN Orchestrator", + "width": 1440, + "height": 980, + "minWidth": 900, + "minHeight": 650, + "resizable": true, + "maximized": true + } + ], + "security": { + "csp": null } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/icon.ico" + ] + } } \ No newline at end of file diff --git a/src/app/routes.tsx b/src/app/routes.tsx index e49b15c..7a47708 100644 --- a/src/app/routes.tsx +++ b/src/app/routes.tsx @@ -101,18 +101,17 @@ export function DashboardRoute() { udp2rawHealthy; return ( -
+
{error && (
- Erro no backend do painel:{' '} - {error} + Erro no backend do painel: {error}
)} -
-
+
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
+
+
- -
-
-
-
- -
- -
-

- Estado da VPS -

- -

- Uptime e carga do sistema -

-
+
+ +
+
+
+
+
-

- {health?.systemUptime ?? 'Desconhecido'} -

+
+

+ Estado da VPS +

-

- Load: {health?.loadAverage ?? '—'} -

- -

- IP: {health?.publicIp ?? '—'} -

+

+ Uptime e carga do sistema +

+
-
-
- Memória - {health?.memoryUsagePercent ?? 0}% -
+

+ {health?.systemUptime ?? 'Desconhecido'} +

-
-
-
+

+ Load: {health?.loadAverage ?? '—'} +

+ +

+ IP: {health?.publicIp ?? '—'} +

+
+ +
+
+ Memória + {health?.memoryUsagePercent ?? 0}% +
+ +
+
- -
+
+ -
+
= [ - 'all', - 'info', - 'success', - 'warning', - 'error', + 'all', + 'info', + 'success', + 'warning', + 'error', ]; const sources: Array<'all' | ActivityLogSource> = [ - 'all', - 'desktop', - 'router', - 'backend', - 'vps', + 'all', + 'desktop', + 'router', + 'backend', + 'vps', ]; const levelLabels: Record<'all' | ActivityLogLevel, string> = { - all: 'Todos', - info: 'Info', - success: 'Sucesso', - warning: 'Aviso', - error: 'Erro', + all: 'Todos', + info: 'Info', + success: 'Sucesso', + warning: 'Aviso', + error: 'Erro', }; const sourceLabels: Record<'all' | ActivityLogSource, string> = { - all: 'Todas', - desktop: 'Desktop', - router: 'Router', - backend: 'Backend', - vps: 'VPS', + all: 'Todas', + desktop: 'Desktop', + router: 'Router', + backend: 'Backend', + vps: 'VPS', }; -function levelTone(level: ActivityLogLevel) { - if (level === 'success') return 'green'; - if (level === 'warning') return 'purple'; - if (level === 'error') return 'red'; +const scrollClass = + '[scrollbar-width:thin] [scrollbar-color:rgba(59,130,246,0.45)_transparent] [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-blue-500/30 hover:[&::-webkit-scrollbar-thumb]:bg-blue-500/50'; - return 'blue'; +function levelTone(level: ActivityLogLevel) { + if (level === 'success') return 'green'; + if (level === 'warning') return 'purple'; + if (level === 'error') return 'red'; + + return 'blue'; } export function ActivityLogs() { - const [logs, setLogs] = useState< - ActivityLogEntry[] - >([]); + const [logs, setLogs] = useState< + ActivityLogEntry[] + >([]); - const [levelFilter, setLevelFilter] = - useState<'all' | ActivityLogLevel>('all'); + const [levelFilter, setLevelFilter] = + useState<'all' | ActivityLogLevel>('all'); - const [sourceFilter, setSourceFilter] = - useState<'all' | ActivityLogSource>('all'); + const [sourceFilter, setSourceFilter] = + useState<'all' | ActivityLogSource>('all'); - const [query, setQuery] = useState(''); + const [query, setQuery] = useState(''); - function refreshLogs() { - setLogs(getActivityLogs()); - } + function refreshLogs() { + setLogs(getActivityLogs()); + } - useEffect(() => { - refreshLogs(); + useEffect(() => { + refreshLogs(); - window.addEventListener( - 'activity-log-added', - refreshLogs, - ); + window.addEventListener( + 'activity-log-added', + refreshLogs, + ); - return () => { - window.removeEventListener( - 'activity-log-added', - refreshLogs, - ); - }; - }, []); + return () => { + window.removeEventListener( + 'activity-log-added', + refreshLogs, + ); + }; + }, []); - const filteredLogs = useMemo(() => { - const normalizedQuery = - query.trim().toLowerCase(); + const filteredLogs = useMemo(() => { + const normalizedQuery = + query.trim().toLowerCase(); - return logs.filter((log) => { - const matchesLevel = - levelFilter === 'all' || - log.level === levelFilter; + return logs.filter((log) => { + const matchesLevel = + levelFilter === 'all' || + log.level === levelFilter; - const matchesSource = - sourceFilter === 'all' || - log.source === sourceFilter; + const matchesSource = + sourceFilter === 'all' || + log.source === sourceFilter; - const matchesQuery = - !normalizedQuery || - [ - log.action, - log.message, - log.routerIp, - log.vpnIp, - log.source, - log.level, - ] - .filter(Boolean) - .join(' ') - .toLowerCase() - .includes(normalizedQuery); + const matchesQuery = + !normalizedQuery || + [ + log.action, + log.message, + log.routerIp, + log.vpnIp, + log.source, + log.level, + ] + .filter(Boolean) + .join(' ') + .toLowerCase() + .includes(normalizedQuery); - return ( - matchesLevel && - matchesSource && - matchesQuery - ); - }); - }, [logs, levelFilter, sourceFilter, query]); + return ( + matchesLevel && + matchesSource && + matchesQuery + ); + }); + }, [logs, levelFilter, sourceFilter, query]); - return ( -
-
-
-

- Registos de Atividade -

+ return ( +
+
+
+

+ Registos de Atividade +

-

- Histórico local de auditoria de - provisionamento para técnicos. -

-
+

+ Histórico local de auditoria de provisionamento para técnicos. +

+
-
- +
+ - -
+ +
+
+ + +
+
+ + +
+ + + + setQuery(event.target.value) + } + placeholder="Pesquisar ação, IP VPN, IP router, mensagem..." + className="w-full min-w-0 bg-transparent py-3 text-sm text-white outline-none placeholder:text-slate-600" + /> +
+
+ +
+
+ + + - setQuery(event.target.value) - } - placeholder="Pesquisar ação, IP VPN, IP router, mensagem..." - className="w-full bg-transparent py-3 text-sm text-white outline-none placeholder:text-slate-600" - /> -
-
- -
- - - ({ - value: source, - label: sourceLabels[source], - }))} - /> -
-
-
- - -
-

- Eventos de Auditoria -

- - - {filteredLogs.length} apresentados /{' '} - {logs.length} total - -
- -
- - - - - - - - - - - - - - - - - - - {filteredLogs.map((log) => ( - - - - - - - - - - - - - - ))} - - {filteredLogs.length === 0 && ( - - - - )} - -
- Hora - - Nível - - Origem - - Ação - - Mensagem - - IP VPN -
- {new Date( - log.timestamp, - ).toLocaleString()} - - - {levelLabels[log.level]} - - - {sourceLabels[log.source]} - - {log.action} - - {log.message} - - {log.vpnIp ?? '—'} -
- Nenhum registo de atividade encontrado. -
-
-
+ { + const value = + event.target.value; + + setCustomIp(value); + setSelectedIp(value.trim()); + }} + onFocus={() => { + setSelectedIp(customIp.trim()); + }} + placeholder="Exemplo: 192.168.8.1" + className="w-full rounded-xl border border-white/10 bg-slate-950 px-3 py-2 font-mono text-sm text-white outline-none transition focus:border-blue-500/40 disabled:cursor-not-allowed disabled:opacity-60" + />
-
-
- +
+ - { - const value = - event.target.value; - - setCustomIp(value); - setSelectedIp(value.trim()); - }} - onFocus={() => { - setSelectedIp(customIp.trim()); - }} - placeholder="Exemplo: 192.168.8.1" - className="w-full rounded-xl border border-white/10 bg-slate-950 px-3 py-2 font-mono text-sm text-white outline-none transition focus:border-blue-500/40 disabled:cursor-not-allowed disabled:opacity-60" - /> -
- -
- - - - setCustomPassword( - event.target.value, - ) + + setCustomPassword( + event.target.value, + ) + } + onFocus={() => { + if (customIp.trim()) { + setSelectedIp( + customIp.trim(), + ); } - onFocus={() => { - if (customIp.trim()) { - setSelectedIp( - customIp.trim(), - ); - } - }} - placeholder="Vazio se não existir" - className="w-full rounded-xl border border-white/10 bg-slate-950 px-3 py-2 font-mono text-sm text-white outline-none transition focus:border-blue-500/40 disabled:cursor-not-allowed disabled:opacity-60" - /> -
+ }} + placeholder="Vazio se não existir" + className="w-full rounded-xl border border-white/10 bg-slate-950 px-3 py-2 font-mono text-sm text-white outline-none transition focus:border-blue-500/40 disabled:cursor-not-allowed disabled:opacity-60" + />
+
-
-
-
- -
- -
-

- Alvo de Deteção -

- -

- root@{selectedIp || '—'} -

-
- - - {statusLabel(status)} - +
+
+
+
-
-
- +
+

+ Alvo de Deteção +

- +

+ root@{selectedIp || '—'} +

+
+ + + {statusLabel(status)} +
+ +
+ + + +
-
- -
+ +
+
@@ -1382,80 +1358,76 @@ export function ProvisioningWizard() {
-
- {workflowState.map((step) => ( - - ))} -
+ + {isActionRunning + ? 'A trabalhar...' + : isReconnecting + ? 'A reconectar...' + : 'Continuar'} + +
-
- -
-
-
- -
- -
-
- -
- -
-

- Informação do Router -

- -

- Saída de ubus system board. -

-
-
- -
-              {routerInfo || 'Ainda não foi capturada informação do router.'}
-            
-
-
+
+ {workflowState.map((step) => ( + + ))} +
+
- -

- Registo Técnico -

+
+ +
+
+ +
-
-          {log.join('\n') || 'Ainda não há atividade de provisionamento.'}
-        
- +
+

+ Informação do Router +

+ +

+ Saída de ubus system board. +

+
+
+ +
+            {routerInfo || 'Ainda não foi capturada informação do router.'}
+          
+
+ + +

+ Registo Técnico +

+ +
+            {log.join('\n') || 'Ainda não há atividade de provisionamento.'}
+          
+
+
{confirmFlashOpen && ( void; +}) { + return ( + + ); +} + function RouterEnvModal({ routerEnv, setRouterEnv, @@ -1541,8 +1548,8 @@ function RouterEnvModal({ } return ( -
-
+
+
@@ -1559,7 +1566,7 @@ function RouterEnvModal({
-
+
-
+

LAN_IP=198.51.100.1

LAN_NETMASK=255.255.255.0

WG_CIDR=32

@@ -1664,7 +1671,7 @@ function ConfirmFlashModal({ onConfirm: () => void; }) { return ( -
+
@@ -1717,7 +1724,7 @@ function FlashOverlay({ flashProgress: number; }) { return ( -
+
@@ -1770,7 +1777,7 @@ function ProvisionOverlay({ provisionProgress: number; }) { return ( -
+
@@ -1831,7 +1838,7 @@ function StopProvisioningModal({ canConfirm: boolean; }) { return ( -
+
@@ -1899,23 +1906,25 @@ function WorkflowStepCard({ return (
@@ -1946,7 +1955,7 @@ function SetupCompleteModal({ onClose: () => void; }) { return ( -
+
diff --git a/src/components/settings/BackendSettings.tsx b/src/components/settings/BackendSettings.tsx index ed35737..19e4f74 100644 --- a/src/components/settings/BackendSettings.tsx +++ b/src/components/settings/BackendSettings.tsx @@ -11,7 +11,6 @@ import { } from 'lucide-react'; import { Badge } from '@/components/ui/Badge'; -import { Button } from '@/components/ui/Button'; import { Card } from '@/components/ui/Card'; import { @@ -50,16 +49,15 @@ export function BackendSettings() { } return ( -
-
-
-

+
+
+
+

Posto de Trabalho

-

- Configuração local da consola técnica - para provisionamento de routers. +

+ Configuração local da consola técnica para provisionamento de routers.

@@ -68,100 +66,84 @@ export function BackendSettings() {
-
- + {saved && ( +
+
+ + Configuração do posto de trabalho guardada. +
+
+ )} + +
+
-
+

Ligação ao Backend

- Endpoint API usado para atribuição - de IP VPN e registo de peers. + Endpoint API usado para atribuição de IP VPN e registo de peers.

-
-
- +
+ } + placeholder="http://localhost:8080" + onChange={(value) => + setSettings({ + ...settings, + backendUrl: value, + }) + } + /> -
- - - - setSettings({ - ...settings, - backendUrl: event.target.value, - }) - } - className="w-full rounded-2xl border border-white/10 bg-slate-950/80 py-4 pl-11 pr-4 font-mono text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-500/50 focus:bg-slate-950" - placeholder="http://localhost:8080" - /> -
-
- -
- - -
- - - - setSettings({ - ...settings, - apiKey: event.target.value, - }) - } - className="w-full rounded-2xl border border-white/10 bg-slate-950/80 py-4 pl-11 pr-4 font-mono text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-500/50 focus:bg-slate-950" - placeholder="dev-api-key" - /> -
-
+ } + placeholder="dev-api-key" + onChange={(value) => + setSettings({ + ...settings, + apiKey: value, + }) + } + />
-
-
-
-

- Alvo Ligado -

+
+
+
+
+

+ Alvo Ligado +

-

- {backendHost} -

+

+ {backendHost} +

+
+ + + Pronto +
- - - Pronto -
-
-
-
-
-
- -
- {[ - ['VPS', '146.59.230.190:444'], - ['Local', '127.0.0.1:4999'], - ['Modo', 'faketcp'], - ['Serviço', 'udp2raw-wg'], - ].map(([label, value]) => ( -
+ setShowPassword((current) => !current) + } + className="absolute right-3 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-xl text-slate-500 transition hover:bg-white/5 hover:text-blue-300" > -

- {label} -

- -

- {value} -

-
- ))} + {showPassword ? ( + + ) : ( + + )} + +
- +
- -
-
-

- Fluxo de Instalação -

+
+ {[ + ['VPS', '146.59.230.190:444'], + ['Local', '127.0.0.1:4999'], + ['Modo', 'faketcp'], + ['Serviço', 'udp2raw-wg'], + ].map(([label, value]) => ( +
+

+ {label} +

-

- Execute passo a passo ou corra o fluxo completo. +

+ {value}

+ ))} +
+ - + +
+
+

+ Fluxo de Instalação +

+ +

+ Execute passo a passo ou corra o fluxo completo. +

-
-
- Progresso - {completedSteps.length}/{steps.length} -
- -
-
-
-
- -
- {steps.map((step, index) => { - const done = completedSteps.includes(step.id); - const active = runningStep === step.id; - const failed = failedStep === step.id; - const Icon = step.icon; + + Executar Tudo + +
- return ( - - ); - })} -
- -
- -
-
-
- -
+
+
+

+ {step.title} +

-
-

- Log UDP2RAW -

+ + {String(index + 1).padStart(2, '0')} + +
-

- Saída dos comandos executados no router. -

-
-
- - +

+ {step.description} +

+
+
+ + ); + })}
- -
-            {log.join('\n\n') || 'Ainda não há atividade UDP2RAW.'}
-          
+ + +
+
+
+ +
+ +
+

+ Log UDP2RAW +

+ +

+ Saída dos comandos executados no router. +

+
+
+ + +
+ +
+          {log.join('\n\n') || 'Ainda não há atividade UDP2RAW.'}
+        
+
); } \ No newline at end of file