+
-
-
+
+
Litoral Regas
-
+
VPN Orchestrator
@@ -51,11 +58,10 @@ export function Sidebar({
key={label}
type="button"
onClick={() => onSelect(label)}
- className={`flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left text-sm font-medium transition ${
- isActive
- ? 'bg-blue-500/15 text-blue-200'
- : 'text-slate-300 hover:bg-white/5 hover:text-white'
- }`}
+ className={`flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left text-sm font-medium transition ${isActive
+ ? 'bg-blue-500/15 text-blue-200'
+ : 'text-slate-300 hover:bg-white/5 hover:text-white'
+ }`}
>
{label}
@@ -64,19 +70,15 @@ export function Sidebar({
})}
-
-
-
- Backend connected
-
-
-
- localhost:8080
-
-
-
- Version 1.0.0
-
+
+
+
+ Terminar Sessão
+
);
diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx
index 14cf69c..b767be7 100644
--- a/src/components/layout/TopBar.tsx
+++ b/src/components/layout/TopBar.tsx
@@ -13,12 +13,12 @@ export function TopBar({
@@ -36,8 +36,8 @@ export function TopBar({
tone={healthy ? 'green' : 'red'}
>
{healthy
- ? 'All Systems Operational'
- : 'System Issues Detected'}
+ ? 'Todos os sistemas operacionais'
+ : 'Problemas detetados no sistema'}
diff --git a/src/components/login/LoginScreen.tsx b/src/components/login/LoginScreen.tsx
new file mode 100644
index 0000000..c43c27c
--- /dev/null
+++ b/src/components/login/LoginScreen.tsx
@@ -0,0 +1,181 @@
+import { useState } from 'react';
+import {
+ Eye,
+ EyeOff,
+ LockKeyhole,
+ Shield,
+ User,
+} from 'lucide-react';
+
+import { Button } from '@/components/ui/Button';
+import { loginApi } from '@/services/loginApi';
+
+type LoginScreenProps = {
+ onLogin: (keepLoggedIn: boolean) => void;
+};
+
+export function LoginScreen({
+ onLogin,
+}: LoginScreenProps) {
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const [keepLoggedIn, setKeepLoggedIn] = useState(false);
+ const [showPassword, setShowPassword] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
+
+ async function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+
+ if (submitting) {
+ return;
+ }
+
+ setSubmitting(true);
+ setError('');
+
+ try {
+ const response = await loginApi.login(
+ username.trim(),
+ password,
+ );
+
+ if (response.authenticated) {
+ onLogin(keepLoggedIn);
+ return;
+ }
+
+ setError('Credenciais inválidas.');
+ } catch {
+ setError('Credenciais inválidas.');
+ } finally {
+ setSubmitting(false);
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Litoral Regas
+
+
+
+ VPN Orchestrator
+
+
+
+
+
+
+
+ Acesso restrito a técnicos autorizados
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/provisioning/ProvisioningStepCard.tsx b/src/components/provisioning/ProvisioningStepCard.tsx
deleted file mode 100644
index 04a6377..0000000
--- a/src/components/provisioning/ProvisioningStepCard.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Card } from '@/components/ui/Card';
-
-import type { ProvisioningState } from '@/types/provisioning';
-
-type ProvisioningStepCardProps = {
- state: ProvisioningState;
- active: boolean;
-};
-
-export function ProvisioningStepCard({
- state,
- active,
-}: ProvisioningStepCardProps) {
- return (
-
-
- {state.replace(/_/g, ' ')}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/provisioning/ProvisioningWizard.tsx b/src/components/provisioning/ProvisioningWizard.tsx
index dc78f46..2f56648 100644
--- a/src/components/provisioning/ProvisioningWizard.tsx
+++ b/src/components/provisioning/ProvisioningWizard.tsx
@@ -71,12 +71,12 @@ type RouterEnvForm = {
const routerPresets = [
{
ip: '192.168.1.1',
- label: 'Factory/default',
+ label: 'Fábrica/predefinido',
password: '',
},
{
ip: '198.51.100.1',
- label: 'Provisioned LAN',
+ label: 'LAN provisionada',
password: 'litoralr',
},
];
@@ -84,7 +84,7 @@ const routerPresets = [
const FLASH_SECONDS = 300;
const PROVISION_SECONDS = 90;
-const stopPhrase = 'STOP PROVISIONING';
+const stopPhrase = 'PARAR PROVISIONAMENTO';
const workflowSteps: Array<{
id: WorkflowStep;
@@ -94,50 +94,50 @@ const workflowSteps: Array<{
}> = [
{
id: 'DETECT_ROUTER',
- title: 'Detect Router',
- description: 'Ping and inspect router over SSH.',
+ title: 'Detetar Router',
+ description: 'Ping e inspeção do router via SSH.',
icon: Search,
},
{
id: 'UPLOAD_FIRMWARE',
- title: 'Upload Firmware',
- description: 'Copy firmware image to /tmp.',
+ title: 'Enviar Firmware',
+ description: 'Copiar imagem de firmware para /tmp.',
icon: UploadCloud,
},
{
id: 'FLASH_FIRMWARE',
- title: 'Flash Firmware',
- description: 'Run sysupgrade -n /tmp/firmware.bin.',
+ title: 'Gravar Firmware',
+ description: 'Executar sysupgrade -n /tmp/firmware.bin.',
icon: Cpu,
},
{
id: 'WAIT_REBOOT',
- title: 'Wait for Reboot',
- description: 'Block actions while router restarts.',
+ title: 'Aguardar Reinício',
+ description: 'Bloquear ações enquanto o router reinicia.',
icon: RefreshCw,
},
{
id: 'RECONNECT_ROUTER',
- title: 'Reconnect Router',
- description: 'Reconnect Ethernet and wait for SSH.',
+ title: 'Reconectar Router',
+ description: 'Reconectar Ethernet e aguardar SSH.',
icon: PlugZap,
},
{
id: 'UPLOAD_PROVISIONING',
- title: 'Upload Bundle',
- description: 'Copy router.env and provision.sh.',
+ title: 'Enviar Pacote',
+ description: 'Copiar router.env e provision.sh.',
icon: FileUp,
},
{
id: 'RUN_PROVISIONING',
- title: 'Run Provisioning',
- description: 'Execute router-side setup script.',
+ title: 'Executar Provisionamento',
+ description: 'Executar script de configuração no router.',
icon: Terminal,
},
{
id: 'REGISTER_PEER',
- title: 'Register VPS Peer',
- description: 'Apply WireGuard peer on VPS.',
+ title: 'Registar Peer VPS',
+ description: 'Aplicar peer WireGuard na VPS.',
icon: Network,
}
];
@@ -171,7 +171,20 @@ function statusTone(status: DetectionStatus) {
}
function statusLabel(status: DetectionStatus) {
- return status.replace(/_/g, ' ');
+ const labels: Record
= {
+ idle: 'inativo',
+ checking: 'a verificar',
+ reachable: 'acessível',
+ ssh_ok: 'ssh ok',
+ auth_required: 'autenticação necessária',
+ stale_host_key: 'chave SSH antiga',
+ failed: 'falhou',
+ flashing: 'a gravar firmware',
+ waiting_reboot: 'a aguardar reinício',
+ reconnecting: 'a reconectar',
+ };
+
+ return labels[status];
}
function formatSeconds(totalSeconds: number) {
@@ -272,7 +285,6 @@ export function ProvisioningWizard() {
const [provisionSecondsRemaining, setProvisionSecondsRemaining] =
useState(PROVISION_SECONDS);
-
const [isActionRunning, setIsActionRunning] =
useState(false);
@@ -404,7 +416,7 @@ export function ProvisioningWizard() {
if (shouldLog) {
addLog(
- 'Provisioning flow was reset.',
+ 'O fluxo de provisionamento foi reposto.',
'warning',
);
}
@@ -414,7 +426,7 @@ export function ProvisioningWizard() {
resetProvisioning(false);
setLog((currentLog) => [
- `${new Date().toLocaleTimeString()} Router setup completed successfully. Ready for next router.`,
+ `${new Date().toLocaleTimeString()} Configuração do router concluída com sucesso. Pronto para o próximo router.`,
...currentLog,
]);
}
@@ -451,7 +463,7 @@ export function ProvisioningWizard() {
setActiveStep('RECONNECT_ROUTER');
addLog(
- 'Flash wait window completed. Reconnect or replug Ethernet if needed, then continue with reconnect checks.',
+ 'Janela de espera do flash concluída. Volte a ligar o cabo Ethernet se necessário e continue com a verificação de reconexão.',
'success',
);
}
@@ -488,7 +500,7 @@ export function ProvisioningWizard() {
setActiveStep('REGISTER_PEER');
addLog(
- 'Provisioning wait completed. Replug Ethernet now, wait a few seconds, then continue to verify WireGuard and register peer.',
+ 'Tempo de provisionamento concluído. Desligue e volte a ligar o Ethernet, aguarde alguns segundos e continue para verificar o WireGuard e registar o peer.',
'warning',
);
@@ -507,7 +519,7 @@ export function ProvisioningWizard() {
async function detectRouter() {
if (controlsLocked) {
addLog(
- 'Router detection is locked while provisioning is active.',
+ 'A deteção do router está bloqueada enquanto o provisionamento está ativo.',
'warning',
);
return;
@@ -515,7 +527,7 @@ export function ProvisioningWizard() {
if (!selectedIp.trim()) {
addLog(
- 'Router detection blocked: no IP selected',
+ 'Deteção do router bloqueada: nenhum IP selecionado',
'warning',
);
return;
@@ -526,7 +538,7 @@ export function ProvisioningWizard() {
setRouterInfo('');
addLog(
- `Starting safe router detection at ${selectedIp}`,
+ `A iniciar deteção segura do router em ${selectedIp}`,
);
try {
@@ -535,7 +547,7 @@ export function ProvisioningWizard() {
});
addLog(
- `Router responded at ${selectedIp}`,
+ `Router respondeu em ${selectedIp}`,
'success',
);
@@ -558,7 +570,7 @@ export function ProvisioningWizard() {
);
addLog(
- `SSH key authentication succeeded at ${selectedIp}`,
+ `Autenticação SSH por chave concluída com sucesso em ${selectedIp}`,
'success',
);
@@ -574,7 +586,7 @@ export function ProvisioningWizard() {
setStatus('stale_host_key');
addLog(
- `Stale SSH host key detected for ${selectedIp}`,
+ `Detetada chave SSH antiga para ${selectedIp}`,
'warning',
);
@@ -587,7 +599,7 @@ export function ProvisioningWizard() {
)
) {
addLog(
- 'Router requires password authentication. Attempting automatic inspection...',
+ 'O router requer autenticação por palavra-passe. A tentar inspeção automática...',
'warning',
);
@@ -614,7 +626,7 @@ export function ProvisioningWizard() {
);
addLog(
- `Password SSH inspection succeeded at ${selectedIp}`,
+ `Inspeção SSH por palavra-passe concluída com sucesso em ${selectedIp}`,
'success',
);
@@ -623,7 +635,7 @@ export function ProvisioningWizard() {
setStatus('auth_required');
addLog(
- `Password authentication failed: ${String(
+ `Falha na autenticação por palavra-passe: ${String(
passwordError,
)}`,
'error',
@@ -641,7 +653,7 @@ export function ProvisioningWizard() {
setStatus('failed');
addLog(
- `Router detection failed: ${message}`,
+ `Falha na deteção do router: ${message}`,
'error',
);
}
@@ -650,7 +662,7 @@ export function ProvisioningWizard() {
async function fixKnownHost() {
if (controlsLocked) {
addLog(
- 'Known host cleanup is locked while provisioning is active.',
+ 'A limpeza de known_hosts está bloqueada enquanto o provisionamento está ativo.',
'warning',
);
return;
@@ -658,7 +670,7 @@ export function ProvisioningWizard() {
if (!selectedIp.trim()) {
addLog(
- 'Cannot remove known_hosts entry: no IP selected',
+ 'Não foi possível remover a entrada known_hosts: nenhum IP selecionado',
'warning',
);
return;
@@ -666,7 +678,7 @@ export function ProvisioningWizard() {
try {
addLog(
- `Removing known_hosts entry for ${selectedIp}`,
+ `A remover entrada known_hosts para ${selectedIp}`,
);
const result = await invoke(
@@ -703,7 +715,7 @@ export function ProvisioningWizard() {
if (activeStep === 'UPLOAD_FIRMWARE') {
try {
addLog(
- `Uploading firmware to ${selectedIp}:/tmp/firmware.bin`,
+ `A enviar firmware para ${selectedIp}:/tmp/firmware.bin`,
);
const result = await invoke(
@@ -728,7 +740,7 @@ export function ProvisioningWizard() {
return;
} catch (error) {
addLog(
- `Firmware upload failed: ${String(error)}`,
+ `Falha no envio do firmware: ${String(error)}`,
'error',
);
@@ -750,13 +762,13 @@ export function ProvisioningWizard() {
setStatus('reconnecting');
addLog(
- `Checking router SSH after flash at ${selectedIp}`,
+ `A verificar SSH do router após flash em ${selectedIp}`,
'warning',
);
for (let attempt = 1; attempt <= 30; attempt += 1) {
try {
- addLog(`Reconnect attempt ${attempt}/30`);
+ addLog(`Tentativa de reconexão ${attempt}/30`);
const info = await invoke(
'check_router_after_flash',
@@ -769,7 +781,7 @@ export function ProvisioningWizard() {
setRouterInfo(info);
addLog(
- `Router SSH reconnected after flash at ${selectedIp}`,
+ `SSH do router reconectado após flash em ${selectedIp}`,
'success',
);
@@ -787,7 +799,7 @@ export function ProvisioningWizard() {
return;
} catch (error) {
addLog(
- `Reconnect attempt ${attempt}/30 failed: ${String(error)}`,
+ `Tentativa de reconexão ${attempt}/30 falhou: ${String(error)}`,
'warning',
);
@@ -801,7 +813,7 @@ export function ProvisioningWizard() {
setIsReconnecting(false);
addLog(
- 'Router reconnect failed after 30 attempts. Replug Ethernet and retry.',
+ 'Falha na reconexão do router após 30 tentativas. Volte a ligar o Ethernet e tente novamente.',
'error',
);
@@ -819,7 +831,7 @@ export function ProvisioningWizard() {
try {
addLog(
- `Running provisioning script on ${selectedIp}`,
+ `A executar script de provisionamento em ${selectedIp}`,
'warning',
);
@@ -847,7 +859,7 @@ export function ProvisioningWizard() {
if (expectedProvisionDisconnect) {
addLog(
- 'Provisioning script reached network/service restart and SSH disconnected as expected.',
+ 'O script de provisionamento atingiu o reinício de rede/serviços e o SSH desligou-se como esperado.',
'warning',
);
@@ -856,7 +868,7 @@ export function ProvisioningWizard() {
setProvisionOverlayOpen(false);
addLog(
- `Provisioning script failed: ${message}`,
+ `Falha no script de provisionamento: ${message}`,
'error',
);
@@ -879,7 +891,7 @@ export function ProvisioningWizard() {
if (activeStep === 'REGISTER_PEER') {
try {
addLog(
- `Capturing router WireGuard public key from ${routerPresets[1].ip}`,
+ `A capturar chave pública WireGuard do router em ${routerPresets[1].ip}`,
'warning',
);
@@ -892,7 +904,7 @@ export function ProvisioningWizard() {
);
addLog(
- `Captured router public key ${publicKey.slice(0, 12)}...`,
+ `Chave pública do router capturada ${publicKey.slice(0, 12)}...`,
'success',
);
@@ -902,7 +914,7 @@ export function ProvisioningWizard() {
});
addLog(
- `Registered VPS peer ${routerEnv.wgIp}`,
+ `Peer VPS ${routerEnv.wgIp} registado`,
'success',
);
@@ -919,7 +931,7 @@ export function ProvisioningWizard() {
return;
} catch (error) {
addLog(
- `Peer registration failed: ${String(error)}`,
+ `Falha no registo do peer: ${String(error)}`,
'error',
);
@@ -928,7 +940,7 @@ export function ProvisioningWizard() {
}
addLog(
- `Step ${activeStep} is not wired yet.`,
+ `O passo ${activeStep} ainda não está ligado.`,
'warning',
);
}
@@ -940,7 +952,7 @@ export function ProvisioningWizard() {
!routerEnv.wgIp.trim()
) {
addLog(
- 'router.env is missing router ID, hostname, or WireGuard IP.',
+ 'router.env não contém o ID do router, hostname ou IP WireGuard.',
'error',
);
return;
@@ -950,7 +962,7 @@ export function ProvisioningWizard() {
const envContent = buildRouterEnv(routerEnv);
addLog(
- `Uploading router.env and provision.sh to ${selectedIp}`,
+ `A enviar router.env e provision.sh para ${selectedIp}`,
'warning',
);
@@ -977,7 +989,7 @@ export function ProvisioningWizard() {
setActiveStep('RUN_PROVISIONING');
} catch (error) {
addLog(
- `Provisioning bundle upload failed: ${String(error)}`,
+ `Falha no envio do pacote de provisionamento: ${String(error)}`,
'error',
);
}
@@ -991,7 +1003,7 @@ export function ProvisioningWizard() {
flashCompletionHandledRef.current = false;
addLog(
- 'Starting firmware flash with sysupgrade -n /tmp/firmware.bin',
+ 'A iniciar gravação do firmware com sysupgrade -n /tmp/firmware.bin',
'warning',
);
@@ -1005,7 +1017,7 @@ export function ProvisioningWizard() {
);
addLog(
- 'Flash command submitted. SSH session may disconnect; entering protected wait window.',
+ 'Comando de flash submetido. A sessão SSH pode desligar; a entrar na janela de espera protegida.',
'success',
);
} catch (error) {
@@ -1023,14 +1035,14 @@ export function ProvisioningWizard() {
loweredMessage.includes('sysupgrade')
) {
addLog(
- 'Router accepted sysupgrade and disconnected as expected during flash.',
+ 'O router aceitou o sysupgrade e desligou-se como esperado durante o flash.',
'warning',
);
} else {
setStatus('failed');
addLog(
- `Flash command failed before reboot: ${message}`,
+ `Falha no comando de flash antes do reinício: ${message}`,
'error',
);
@@ -1045,7 +1057,7 @@ export function ProvisioningWizard() {
async function prepareRouterEnv() {
try {
addLog(
- 'Requesting next available WireGuard IP from backend...',
+ 'A pedir ao backend o próximo IP WireGuard disponível...',
);
const response = await vpnApi.availableIp();
@@ -1063,14 +1075,14 @@ export function ProvisioningWizard() {
}));
addLog(
- `Reserved next available WireGuard IP candidate: ${vpnIp}`,
+ `Candidato a próximo IP WireGuard disponível reservado: ${vpnIp}`,
'success',
);
setEnvModalOpen(true);
} catch (error) {
addLog(
- `Failed to get available WireGuard IP: ${String(error)}`,
+ `Falha ao obter IP WireGuard disponível: ${String(error)}`,
'error',
);
}
@@ -1081,12 +1093,11 @@ export function ProvisioningWizard() {
- Provisioning
+ Provisionamento
- Guided router provisioning from
- detection to VPS peer registration.
+ Provisionamento guiado do router, desde a deteção até ao registo do peer na VPS.
@@ -1102,7 +1113,7 @@ export function ProvisioningWizard() {
isActionRunning
}
>
- Stop Provisioning
+ Parar Provisionamento
)}
@@ -1124,12 +1135,11 @@ export function ProvisioningWizard() {
- Router Connection
+ Ligação ao Router
- Select target and validate SSH
- before provisioning.
+ Selecione o alvo e valide o SSH antes do provisionamento.
@@ -1147,10 +1157,10 @@ export function ProvisioningWizard() {
}`}
>
- Full Provisioning
+ Provisionamento Completo
- Flash firmware, reconnect, then provision.
+ Gravar firmware, reconectar e depois provisionar.
@@ -1166,10 +1176,10 @@ export function ProvisioningWizard() {
}`}
>
- Provision Only
+ Apenas Provisionar
- Router is already flashed and stable.
+ O router já tem firmware gravado e está estável.
@@ -1213,9 +1223,9 @@ export function ProvisioningWizard() {
- Password:{' '}
+ Palavra-passe:{' '}
- {preset.password || 'empty'}
+ {preset.password || 'vazia'}
@@ -1240,12 +1250,11 @@ export function ProvisioningWizard() {