diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..f4768e7 Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..4c4470e Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..4637a05 Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..3b5fa92 Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..e97e3dd Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..e684fd5 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..30b6834 Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..04050fa Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..04caa00 Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..41dd264 Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..5ca81c6 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..ad71fad Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..689f9d3 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..74e640e Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..aee128e Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ab4f6fc Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..ad98604 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..0f7e448 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2ad437b Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..f45077a Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..d04b807 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7de6c89 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..907b3b6 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..1cdb994 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6dc5816 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..137fdbe Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..832081d Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b396ce8 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..7299407 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..1e4fe4f Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index cc07adc..71a1258 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..4a31231 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..5b94ee6 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..e614abe Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..e614abe Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..be40fca Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..de17c69 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..d8b0590 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..d8b0590 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..984afb1 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..e614abe Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..488371b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..488371b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..0021852 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..0009ac3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..0021852 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..136fb3a Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..e609e79 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..56e8a31 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..abd4291 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src/app/App.tsx b/src/app/App.tsx index cd6d005..e917707 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,15 +1,43 @@ import { useState } from 'react'; +import { LoginScreen } from '@/components/login/LoginScreen'; import { AppShell } from '@/components/layout/AppShell'; import { RouteView } from './routes'; +const AUTH_KEY = 'lr-openwrt-tool.authenticated'; + export default function App() { - const [active, setActive] = useState('Dashboard'); + const [authenticated, setAuthenticated] = + useState(() => localStorage.getItem(AUTH_KEY) === 'true'); + + const [active, setActive] = + useState('Painel'); + + function handleLogin(keepLoggedIn: boolean) { + setAuthenticated(true); + + if (keepLoggedIn) { + localStorage.setItem(AUTH_KEY, 'true'); + } else { + localStorage.removeItem(AUTH_KEY); + } + } + + function handleLogout() { + localStorage.removeItem(AUTH_KEY); + setAuthenticated(false); + setActive('Painel'); + } + + if (!authenticated) { + return ; + } return ( diff --git a/src/app/routes.tsx b/src/app/routes.tsx index b9546d1..54a301f 100644 --- a/src/app/routes.tsx +++ b/src/app/routes.tsx @@ -100,52 +100,52 @@ export function DashboardRoute() { {error && (
- Dashboard backend error:{' '} + Erro no backend do painel:{' '} {error}
)}
} /> } /> } /> } />
@@ -178,8 +178,8 @@ export function PlaceholderRoute({

- Screen scaffold ready for production - implementation. + Estrutura do ecrã pronta para + implementação em produção.

); @@ -190,25 +190,25 @@ export function RouteView({ }: { active: string; }) { - if (active === 'Dashboard') { + if (active === 'Painel') { return ; } - if (active === 'Provisioning') { + if (active === 'Provisionamento') { return ; } - if (active === 'UDP2RAW Config') { + if (active === 'Configuração UDP2RAW') { return ( - + ); } - if (active === 'Activity Logs') { + if (active === 'Registos de Atividade') { return ; } - if (active === 'Workstation') { + if (active === 'Posto de Trabalho') { return ; } diff --git a/src/assets/logo-icon.png b/src/assets/logo-icon.png new file mode 100644 index 0000000..c4c7eab Binary files /dev/null and b/src/assets/logo-icon.png differ diff --git a/src/components/activity/ActivityLogs.tsx b/src/components/activity/ActivityLogs.tsx index ca024d2..f907fd7 100644 --- a/src/components/activity/ActivityLogs.tsx +++ b/src/components/activity/ActivityLogs.tsx @@ -38,6 +38,22 @@ const sources: Array<'all' | ActivityLogSource> = [ 'vps', ]; +const levelLabels: Record<'all' | ActivityLogLevel, string> = { + 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', +}; + function levelTone(level: ActivityLogLevel) { if (level === 'success') return 'green'; if (level === 'warning') return 'purple'; @@ -120,12 +136,12 @@ export function ActivityLogs() {

- Activity Logs + Registos de Atividade

- Local provisioning audit trail for - technicians. + Histórico local de auditoria de + provisionamento para técnicos.

@@ -138,7 +154,7 @@ export function ActivityLogs() { }} > - Export JSON + Exportar JSON
@@ -159,7 +175,7 @@ export function ActivityLogs() {
@@ -173,7 +189,7 @@ export function ActivityLogs() { onChange={(event) => setQuery(event.target.value) } - placeholder="Search action, VPN IP, router IP, message..." + 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" />
@@ -181,7 +197,7 @@ export function ActivityLogs() {
({ value: source, - label: source, + label: sourceLabels[source], }))} />
@@ -214,11 +230,11 @@ export function ActivityLogs() {

- Audit Events + Eventos de Auditoria

- {filteredLogs.length} shown /{' '} + {filteredLogs.length} apresentados /{' '} {logs.length} total
@@ -235,27 +251,27 @@ export function ActivityLogs() { - Time + Hora - Level + Nível - Source + Origem - Action + Ação - Message + Mensagem - VPN IP + IP VPN @@ -274,12 +290,12 @@ export function ActivityLogs() { - {log.level} + {levelLabels[log.level]} - {log.source} + {sourceLabels[log.source]} @@ -302,7 +318,7 @@ export function ActivityLogs() { colSpan={6} className="px-4 py-10 text-center text-slate-500" > - No activity logs found. + Nenhum registo de atividade encontrado. )} diff --git a/src/components/dashboard/IpPoolChart.tsx b/src/components/dashboard/IpPoolChart.tsx index 5452ea8..60bfe1c 100644 --- a/src/components/dashboard/IpPoolChart.tsx +++ b/src/components/dashboard/IpPoolChart.tsx @@ -28,11 +28,11 @@ export function IpPoolChart({ const data = [ { - name: 'Used', + name: 'Usados', value: used, }, { - name: 'Available', + name: 'Disponíveis', value: available, }, ]; @@ -41,11 +41,11 @@ export function IpPoolChart({

- IP Pool Usage + Uso da Pool IP

- WireGuard allocation capacity + Capacidade de atribuição WireGuard

@@ -82,7 +82,7 @@ export function IpPoolChart({

- pool used + pool usada

@@ -90,7 +90,7 @@ export function IpPoolChart({

- Used IPs + IPs Usados

@@ -100,7 +100,7 @@ export function IpPoolChart({

- Available IPs + IPs Disponíveis

@@ -110,7 +110,7 @@ export function IpPoolChart({

- Capacity + Capacidade {used} / {total}
diff --git a/src/components/dashboard/NetworkTrafficChart.tsx b/src/components/dashboard/NetworkTrafficChart.tsx index a930c04..54cb410 100644 --- a/src/components/dashboard/NetworkTrafficChart.tsx +++ b/src/components/dashboard/NetworkTrafficChart.tsx @@ -84,16 +84,16 @@ export function NetworkTrafficChart() {

- Network Traffic + Tráfego de Rede

- Live wg0 RX/TX throughput + Débito RX/TX wg0 em direto

- 3s refresh + Atualização 3s
diff --git a/src/components/dashboard/ProvisioningWorkflow.tsx b/src/components/dashboard/ProvisioningWorkflow.tsx deleted file mode 100644 index 7243151..0000000 --- a/src/components/dashboard/ProvisioningWorkflow.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - CheckCircle, - Cpu, - FileUp, - KeyRound, - Network, - Plug, - RefreshCw, - ShieldCheck, -} from 'lucide-react'; - -import { Card } from '@/components/ui/Card'; - -const steps = [ - ['Connect Router', Plug], - ['Upload Firmware', FileUp], - ['Flash Firmware', Cpu], - ['Wait & Reconnect', RefreshCw], - ['Provision Router', Network], - ['Get Public Key', KeyRound], - ['Register Peer', ShieldCheck], - ['Verify Connection', CheckCircle], -] as const; - -export function ProvisioningWorkflow() { - return ( - -
-
-

- Provisioning Workflow -

- -

- OpenWrt 23.05 production - provisioning pipeline -

-
- - - Technician Mode - -
- -
- {steps.map(([step, Icon], index) => ( -
-
- - Step {index + 1} - - - -
- -

- {step} -

- - {index !== steps.length - 1 && ( -
- )} -
- ))} -
- - ); -} \ No newline at end of file diff --git a/src/components/dashboard/RecentActivityPanel.tsx b/src/components/dashboard/RecentActivityPanel.tsx deleted file mode 100644 index 68c4984..0000000 --- a/src/components/dashboard/RecentActivityPanel.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Card } from '@/components/ui/Card'; - -const rows = [ - 'Peer added 198.19.254.3', - 'Peer updated 198.19.254.2', - 'Backup created wg0.conf.bak', - 'Health check all systems operational', -]; - -export function RecentActivityPanel() { - return ( - -
-

- Recent Activity -

- - - Live Feed - -
- -
- {rows.map((row, index) => ( -
- - {row} - - - - {index - ? `${index * 15}m ago` - : '2m ago'} - -
- ))} -
-
- ); -} \ No newline at end of file diff --git a/src/components/dashboard/ServicesHealthPanel.tsx b/src/components/dashboard/ServicesHealthPanel.tsx deleted file mode 100644 index 5f02735..0000000 --- a/src/components/dashboard/ServicesHealthPanel.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Card } from '@/components/ui/Card'; -import { Badge } from '@/components/ui/Badge'; -import { StatusDot } from '@/components/ui/StatusDot'; - -const services = [ - 'WireGuard wg0', - 'DNAT rules', - 'IP forwarding', - 'Persistent keepalive', - 'Backup system', -]; - -export function ServicesHealthPanel() { - return ( - -
-

- Services Health -

- - - fw4 / nftables - -
- -
- {services.map((service) => ( -
-
- - -
-

- {service} -

- -

- Running baseline check -

-
-
- - - Running - -
- ))} -
-
- ); -} \ No newline at end of file diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index a81ce28..9e26d8e 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -5,16 +5,22 @@ import { Sidebar } from './Sidebar'; type AppShellProps = PropsWithChildren<{ active: string; onSelect: (value: string) => void; + onLogout: () => void; }>; export function AppShell({ active, onSelect, + onLogout, children, }: AppShellProps) { return (
- +
{children} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index bcf8151..bfd8515 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,42 +1,49 @@ import { FileClock, Gauge, + LogOut, RadioTower, Settings, Shield, Wrench, } from 'lucide-react'; +import logoIcon from '@/assets/logo-icon.png'; + const items = [ - ['Dashboard', Gauge], - ['UDP2RAW Config', RadioTower], - ['Provisioning', Wrench], - ['Activity Logs', FileClock], - ['Workstation', Settings], + ['Painel', Gauge], + ['Configuração UDP2RAW', RadioTower], + ['Provisionamento', Wrench], + ['Registos de Atividade', FileClock], + ['Posto de Trabalho', Settings], ] as const; type SidebarProps = { active: string; onSelect: (value: string) => void; + onLogout: () => void; }; export function Sidebar({ active, onSelect, + onLogout, }: SidebarProps) { return ( ); 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({

- Dashboard + Painel

+ Provisionamento de routers de produção OpenWrt 23.05 WireGuard - production router provisioning

@@ -27,7 +27,7 @@ export function TopBar({ - Last updated:{' '} + Última atualização:{' '} {new Date().toLocaleTimeString()}
@@ -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 +

+
+
+ +
+
+
+ + +
+ + + + setUsername(event.target.value) + } + autoComplete="username" + className="w-full rounded-2xl border border-white/10 bg-black/25 py-4 pl-11 pr-4 text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-400/60 focus:bg-black/35 focus:shadow-[0_0_0_4px_rgba(59,130,246,0.12)]" + placeholder="Introduza o utilizador" + /> +
+
+ +
+ + +
+ + + + setPassword(event.target.value) + } + autoComplete="current-password" + className="w-full rounded-2xl border border-white/10 bg-black/25 py-4 pl-11 pr-12 text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-400/60 focus:bg-black/35 focus:shadow-[0_0_0_4px_rgba(59,130,246,0.12)]" + placeholder="Introduza a palavra-passe" + /> + + +
+
+
+ + {error && ( +
+ {error} +
+ )} + + + + +
+ +
+ 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() {

- Custom Router Access + Acesso Personalizado ao Router

- Use this for non-standard router IPs - or passwords. + Use para IPs de router ou palavras-passe não padrão.

@@ -1253,7 +1262,7 @@ export function ProvisioningWizard() {
{ setSelectedIp(customIp.trim()); }} - placeholder="Example: 192.168.8.1" + 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" />
@@ -1311,7 +1320,7 @@ export function ProvisioningWizard() {

- Detection Target + Alvo de Deteção

@@ -1336,7 +1345,7 @@ export function ProvisioningWizard() { } > - Detect Router + Detetar Router

@@ -1364,11 +1373,11 @@ export function ProvisioningWizard() {

- Provisioning Flow + Fluxo de Provisionamento

- Full router onboarding sequence. + Sequência completa de preparação do router.

@@ -1400,10 +1409,10 @@ export function ProvisioningWizard() { > {isActionRunning - ? 'Working...' + ? 'A trabalhar...' : isReconnecting - ? 'Reconnecting...' - : 'Continue Provisioning'} + ? 'A reconectar...' + : 'Continuar Provisionamento'}
@@ -1418,11 +1427,11 @@ export function ProvisioningWizard() {

- Router Info + Informação do Router

- ubus system board output. + Saída de ubus system board.

@@ -1430,7 +1439,7 @@ export function ProvisioningWizard() {
-              {routerInfo || 'No router information captured yet.'}
+              {routerInfo || 'Ainda não foi capturada informação do router.'}
             
@@ -1438,13 +1447,13 @@ export function ProvisioningWizard() {

- Technician Log + Registo Técnico

-          {log.join('\n') || 'No provisioning activity yet.'}
+          {log.join('\n') || 'Ainda não há atividade de provisionamento.'}
         
@@ -1541,18 +1550,18 @@ function RouterEnvModal({

- Router Environment + Ambiente do Router

- Values used to generate router.env before upload. + Valores usados para gerar router.env antes do envio.

updateField('routerId', value)} placeholder="203" @@ -1566,14 +1575,14 @@ function RouterEnvModal({ /> updateField('wgIp', value)} placeholder="198.19.1.203" /> updateField('rootPassword', value)} placeholder="litoralr" @@ -1583,7 +1592,7 @@ function RouterEnvModal({

- Static router.env values + Valores estáticos de router.env

@@ -1602,12 +1611,12 @@ function RouterEnvModal({
@@ -1664,33 +1673,33 @@ function ConfirmFlashModal({

- Confirm Firmware Flash + Confirmar Gravação do Firmware

- This will run{' '} + Isto irá executar{' '} sysupgrade -n /tmp/firmware.bin {' '} - on router{' '} + no router{' '} {selectedIp} - . The SSH session will drop and the router may be unreachable for several minutes. + . A sessão SSH será interrompida e o router poderá ficar inacessível durante vários minutos.

- Do not unplug router power. Do not close this application. + Não desligue a alimentação do router. Não feche esta aplicação.
@@ -1717,17 +1726,17 @@ function FlashOverlay({

- Flashing router firmware + A gravar firmware do router

- The router is applying firmware and rebooting. The SSH session will be unavailable during this window. + O router está a aplicar o firmware e a reiniciar. A sessão SSH ficará indisponível temporariamente.

- Protected wait window + Janela de espera protegida @@ -1744,7 +1753,7 @@ function FlashOverlay({
- Do not unplug power. Do not close this app. Wait for this screen to finish before touching the Ethernet cable. + Não desligue a alimentação. Não feche esta aplicação. Aguarde até este ecrã terminar antes de mexer no cabo Ethernet.
@@ -1770,18 +1779,18 @@ function ProvisionOverlay({

- Applying router provisioning + A aplicar provisionamento do router

- The router is changing LAN, firewall, WireGuard, LuCI, - and root password settings. SSH or Ethernet may drop during this step. + O router está a alterar definições de LAN, firewall, WireGuard, LuCI + e palavra-passe root. O SSH ou Ethernet poderá cair durante este passo.

- Protected provisioning window + Janela de provisionamento protegida @@ -1798,8 +1807,8 @@ function ProvisionOverlay({
- Do not unplug power. When this timer finishes, unplug and replug Ethernet, - then continue to verify WireGuard and register the VPS peer. + Não desligue a alimentação. Quando este temporizador terminar, desligue e volte a ligar o cabo Ethernet, + depois continue para verificar o WireGuard e registar o peer VPS.
@@ -1831,19 +1840,19 @@ function StopProvisioningModal({

- Stop Provisioning? + Parar Provisionamento?

- Stopping in the middle of provisioning can leave the router in a partial or broken state. Only continue if you know the current router state is safe. + Parar a meio do provisionamento pode deixar o router num estado parcial ou avariado. Continue apenas se souber que o estado atual do router é seguro.

- Type{' '} + Escreva{' '} {stopPhrase} {' '} - to confirm. + para confirmar.

@@ -1946,11 +1955,11 @@ function SetupCompleteModal({

- Router setup completed + Configuração do router concluída

- Router provisioning finished successfully and the VPS WireGuard peer was registered. + O provisionamento do router terminou com sucesso e o peer WireGuard da VPS foi registado.

@@ -1961,7 +1970,7 @@ function SetupCompleteModal({
diff --git a/src/components/settings/BackendSettings.tsx b/src/components/settings/BackendSettings.tsx index 1424360..ed35737 100644 --- a/src/components/settings/BackendSettings.tsx +++ b/src/components/settings/BackendSettings.tsx @@ -36,7 +36,7 @@ export function BackendSettings() { try { return new URL(settings.backendUrl).host; } catch { - return 'Invalid backend URL'; + return 'URL backend inválido'; } }, [settings.backendUrl]); @@ -54,17 +54,17 @@ export function BackendSettings() {

- Workstation + Posto de Trabalho

- Local technician console configuration - for router provisioning. + Configuração local da consola técnica + para provisionamento de routers.

- {saved ? 'Saved' : 'Local Config'} + {saved ? 'Guardado' : 'Configuração Local'}
@@ -77,12 +77,12 @@ export function BackendSettings() {

- Backend Connectivity + Ligação ao Backend

- API endpoint used for VPN IP - assignment and peer registration. + Endpoint API usado para atribuição + de IP VPN e registo de peers.

@@ -90,7 +90,7 @@ export function BackendSettings() {
@@ -115,7 +115,7 @@ export function BackendSettings() {
@@ -143,7 +143,7 @@ export function BackendSettings() {

- Connected Target + Alvo Ligado

@@ -152,7 +152,7 @@ export function BackendSettings() {

- Ready + Pronto
@@ -169,7 +169,7 @@ export function BackendSettings() { /> - Save Workstation Config + Guardar Configuração
@@ -184,38 +184,38 @@ export function BackendSettings() {

- Provisioning Baseline + Base de Provisionamento

- Read-only production values. + Valores de produção apenas leitura.

@@ -229,16 +229,16 @@ export function BackendSettings() {

- Production Profile + Perfil de Produção

- OpenWrt 23.05 baseline, - ZBT-WE826 16M target, - fw4/nftables firewall, - LuCI over WireGuard, stable - LAN topology and automated VPS - peer registration. + Base OpenWrt 23.05, + alvo ZBT-WE826 16M, + firewall fw4/nftables, + LuCI sobre WireGuard, topologia + LAN estável e registo automático + de peers VPS.

@@ -262,7 +262,7 @@ export function BackendSettings() {
- Workstation configuration saved. + Configuração do posto de trabalho guardada.
)} diff --git a/src/components/vpn/IpManagementPanel.tsx b/src/components/vpn/IpManagementPanel.tsx deleted file mode 100644 index 468939e..0000000 --- a/src/components/vpn/IpManagementPanel.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useState } from 'react'; - -import { Card } from '@/components/ui/Card'; -import { Button } from '@/components/ui/Button'; - -import { vpnApi } from '@/services/vpnApi'; - -export function IpManagementPanel() { - const [ip, setIp] = useState(''); - - async function handleReserveIp() { - try { - const response = - await vpnApi.availableIp(); - - setIp(response.vpnIp); - } catch (error) { - console.error( - 'Failed to reserve IP:', - error, - ); - } - } - - return ( - -

- IP Management -

- -

- Overlay route: 198.19.0.0/16 -

- -
- -
- - {ip && ( -
-

- Assigned candidate: -

- -

- {ip} -

-
- )} -
- ); -} \ No newline at end of file diff --git a/src/components/vpn/VpnPeersTable.tsx b/src/components/vpn/VpnPeersTable.tsx deleted file mode 100644 index 67cedf4..0000000 --- a/src/components/vpn/VpnPeersTable.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Card } from '@/components/ui/Card'; - -const peers = [ - [ - '198.19.254.1', - 'Q2o9...h8jK', - '2m ago', - '1.2 MB', - ], - [ - '198.19.254.2', - 'P9xL...a7Bc', - '3m ago', - '532 KB', - ], - [ - '198.19.254.3', - 'J4kM...z9Xp', - '1m ago', - '2.1 MB', - ], -]; - -export function VpnPeersTable() { - return ( - -
-

- VPN Peers -

- - - 3 Active - -
- -
- - - - - - - - - - - - - - - {peers.map((peer) => ( - - - - - - - - - - ))} - -
- VPN IP - - Public Key - - Last Handshake - - Transfer -
- ● {peer[0]} - - {peer[1]} - - {peer[2]} - - {peer[3]} -
-
-
- ); -} \ No newline at end of file diff --git a/src/services/loginApi.ts b/src/services/loginApi.ts new file mode 100644 index 0000000..274f7b2 --- /dev/null +++ b/src/services/loginApi.ts @@ -0,0 +1,17 @@ +import { apiRequest } from '@/services/apiClient'; + +type LoginResponse = { + authenticated: boolean; +}; + +export const loginApi = { + login(username: string, password: string) { + return apiRequest('/api/login', { + method: 'POST', + body: JSON.stringify({ + username, + password, + }), + }); + }, +}; \ No newline at end of file diff --git a/src/styles/globals.css b/src/styles/globals.css index f40fb8f..aa28398 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -20,4 +20,14 @@ body, body { overflow: hidden; +} + +input[type='password']::-ms-reveal, +input[type='password']::-ms-clear { + display: none; +} + +input[type='password']::-webkit-credentials-auto-fill-button, +input[type='password']::-webkit-password-reveal-button { + display: none !important; } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 68b970a..1509932 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { - "compilerOptions": { + "compilerOptions": { + "ignoreDeprecations": "6.0", "target": "ES2020", "useDefineForClassFields": true, "lib": [