feat(openwrt): build router detection and provisioning readiness flow
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
import { useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Router,
|
||||
Search,
|
||||
ShieldCheck,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
|
||||
type DetectionResult = {
|
||||
detected: boolean;
|
||||
ip: string | null;
|
||||
method: string;
|
||||
ssh_reachable: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type CompatibilityResult = {
|
||||
compatible: boolean;
|
||||
version: string | null;
|
||||
target: string | null;
|
||||
arch: string | null;
|
||||
reason: string | null;
|
||||
};
|
||||
|
||||
type ReadinessResult = {
|
||||
internet_ok: boolean;
|
||||
dns_ok: boolean;
|
||||
opkg_ok: boolean;
|
||||
openvpn_installed: boolean;
|
||||
free_space: string | null;
|
||||
free_memory: string | null;
|
||||
};
|
||||
|
||||
type SshResult = {
|
||||
connected: boolean;
|
||||
host: string;
|
||||
hostname: string | null;
|
||||
openwrt_release: string | null;
|
||||
compatibility: CompatibilityResult | null;
|
||||
readiness: ReadinessResult | null;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function OpenWrtConfigPage() {
|
||||
const [detecting, setDetecting] = useState(false);
|
||||
const [connecting, setConnecting] = useState(false);
|
||||
const [detection, setDetection] = useState<DetectionResult | null>(null);
|
||||
const [sshResult, setSshResult] = useState<SshResult | null>(null);
|
||||
|
||||
const [host, setHost] = useState("");
|
||||
const [username, setUsername] = useState("root");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const compatibility = sshResult?.compatibility;
|
||||
|
||||
async function detectRouter() {
|
||||
try {
|
||||
setDetecting(true);
|
||||
setSshResult(null);
|
||||
|
||||
const result = await invoke<DetectionResult>("detect_openwrt_router");
|
||||
|
||||
setDetection(result);
|
||||
|
||||
if (result.ip) {
|
||||
setHost(result.ip);
|
||||
}
|
||||
} finally {
|
||||
setDetecting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectSsh() {
|
||||
try {
|
||||
setConnecting(true);
|
||||
|
||||
const result = await invoke<SshResult>("test_openwrt_ssh", {
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
|
||||
setSshResult(result);
|
||||
} finally {
|
||||
setConnecting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="page-stack">
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1>Configuração OpenWRT</h1>
|
||||
<p>Detetar router local, testar SSH e validar compatibilidade</p>
|
||||
</div>
|
||||
|
||||
<button className="primary" onClick={detectRouter} disabled={detecting}>
|
||||
<Search size={16} />
|
||||
{detecting ? "A detetar..." : "Detetar Router"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="openwrt-grid">
|
||||
<div className="dashboard-card">
|
||||
<div className="card-header">
|
||||
<div>
|
||||
<h2>Deteção do Router</h2>
|
||||
<p>Procura pelo gateway local ou pelo IP padrão OpenWRT</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="status-note">
|
||||
<Router size={24} />
|
||||
|
||||
<p>
|
||||
{detection
|
||||
? detection.message
|
||||
: "Ainda não foi executada nenhuma deteção."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{detection?.ip && (
|
||||
<div className="detail-list">
|
||||
<DetailRow label="IP detetado" value={detection.ip} />
|
||||
<DetailRow label="Método" value={detection.method} />
|
||||
<DetailRow
|
||||
label="SSH"
|
||||
value={detection.ssh_reachable ? "Disponível" : "Indisponível"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="dashboard-card">
|
||||
<div className="card-header">
|
||||
<div>
|
||||
<h2>Ligação SSH</h2>
|
||||
<p>Introduza as credenciais do router OpenWRT</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="openwrt-form">
|
||||
<label>
|
||||
IP do Router
|
||||
<input value={host} onChange={(e) => setHost(e.target.value)} />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Utilizador
|
||||
<input
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Password SSH"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
className="primary"
|
||||
onClick={connectSsh}
|
||||
disabled={connecting || !host || !username}
|
||||
>
|
||||
<ShieldCheck size={16} />
|
||||
{connecting ? "A ligar..." : "Ligar ao Router"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sshResult && (
|
||||
<div className="dashboard-card wide-panel">
|
||||
<div className="card-header">
|
||||
<div>
|
||||
<h2>Resultado da Ligação</h2>
|
||||
<p>Estado da sessão SSH com o router</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="detail-list">
|
||||
<DetailRow
|
||||
label="Estado"
|
||||
value={sshResult.connected ? "Ligado" : "Falhou"}
|
||||
/>
|
||||
|
||||
<DetailRow label="Host" value={sshResult.host} />
|
||||
<DetailRow label="Hostname" value={sshResult.hostname || "-"} />
|
||||
</div>
|
||||
|
||||
{!sshResult.connected && (
|
||||
<div className="status-note">
|
||||
<XCircle size={24} />
|
||||
<p>{sshResult.message}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sshResult?.connected && compatibility && (
|
||||
<div className="dashboard-card wide-panel">
|
||||
<div className="card-header">
|
||||
<div>
|
||||
<h2>Compatibilidade OpenWRT</h2>
|
||||
<p>Validação da versão e arquitetura do firmware</p>
|
||||
</div>
|
||||
|
||||
<span
|
||||
className={`badge ${compatibility.compatible ? "success" : "failed"
|
||||
}`}
|
||||
>
|
||||
{compatibility.compatible ? "Compatível" : "Não compatível"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`status-note ${compatibility.compatible ? "compatible" : "unsupported"
|
||||
}`}
|
||||
>
|
||||
{compatibility.compatible ? (
|
||||
<CheckCircle2 size={24} />
|
||||
) : (
|
||||
<AlertTriangle size={24} />
|
||||
)}
|
||||
|
||||
<p>
|
||||
{compatibility.compatible
|
||||
? "Este router cumpre os requisitos mínimos para configuração OpenVPN."
|
||||
: compatibility.reason ||
|
||||
"Este router não cumpre os requisitos mínimos."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="detail-list">
|
||||
<DetailRow
|
||||
label="Versão OpenWRT"
|
||||
value={compatibility.version || "-"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="Target"
|
||||
value={compatibility.target || "-"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="Arquitetura"
|
||||
value={compatibility.arch || "-"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="Versão mínima"
|
||||
value="21.02.2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sshResult.openwrt_release && (
|
||||
<div className="log-box">
|
||||
<pre>{sshResult.openwrt_release}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sshResult?.connected && sshResult.readiness && (
|
||||
<div className="dashboard-card wide-panel">
|
||||
<div className="card-header">
|
||||
<div>
|
||||
<h2>Estado do Router</h2>
|
||||
<p>Verificações antes da configuração OpenVPN</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="detail-list">
|
||||
<DetailRow
|
||||
label="Internet"
|
||||
value={sshResult.readiness.internet_ok ? "Disponível" : "Indisponível"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="DNS"
|
||||
value={sshResult.readiness.dns_ok ? "Disponível" : "Indisponível"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="OPKG"
|
||||
value={sshResult.readiness.opkg_ok ? "Disponível" : "Indisponível"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="OpenVPN"
|
||||
value={sshResult.readiness.openvpn_installed ? "Instalado" : "Não instalado"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="Espaço livre"
|
||||
value={sshResult.readiness.free_space || "-"}
|
||||
/>
|
||||
|
||||
<DetailRow
|
||||
label="Memória livre"
|
||||
value={sshResult.readiness.free_memory || "-"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="detail-row">
|
||||
<span>{label}</span>
|
||||
<strong>{value}</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user