Add OpenWRT SSH provisioning and readiness workflow
This commit is contained in:
@@ -6,7 +6,8 @@ pub fn run() {
|
|||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
openwrt::detect_openwrt_router,
|
openwrt::detect_openwrt_router,
|
||||||
openwrt::test_openwrt_ssh
|
openwrt::test_openwrt_ssh,
|
||||||
|
openwrt::prepare_openwrt_router
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -43,6 +43,15 @@ pub struct OpenWrtReadiness {
|
|||||||
free_memory: Option<String>,
|
free_memory: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct OpenWrtPrepareResult {
|
||||||
|
success: bool,
|
||||||
|
hostname_updated: bool,
|
||||||
|
password_updated: bool,
|
||||||
|
message: String,
|
||||||
|
logs: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn detect_openwrt_router() -> OpenWrtDetectionResult {
|
pub fn detect_openwrt_router() -> OpenWrtDetectionResult {
|
||||||
let candidates = vec![detect_default_gateway(), Some("192.168.1.1".to_string())];
|
let candidates = vec![detect_default_gateway(), Some("192.168.1.1".to_string())];
|
||||||
@@ -169,6 +178,127 @@ pub fn test_openwrt_ssh(host: String, username: String, password: String) -> Ope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn prepare_openwrt_router(
|
||||||
|
host: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
target_hostname: String,
|
||||||
|
new_password: String
|
||||||
|
) -> OpenWrtPrepareResult {
|
||||||
|
if target_hostname.trim().is_empty() {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: "Hostname inválido.".to_string(),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_password.trim().is_empty() {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: "Password root inválida.".to_string(),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if password.trim().is_empty() {
|
||||||
|
return prepare_openwrt_router_with_system_ssh(
|
||||||
|
host,
|
||||||
|
username,
|
||||||
|
target_hostname,
|
||||||
|
new_password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = format!("{}:22", host);
|
||||||
|
|
||||||
|
let socket = match address.parse::<SocketAddr>() {
|
||||||
|
Ok(socket) => socket,
|
||||||
|
Err(err) => {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: format!("Endereço inválido: {}", err),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tcp = match TcpStream::connect_timeout(&socket, Duration::from_secs(5)) {
|
||||||
|
Ok(stream) => stream,
|
||||||
|
Err(err) => {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: format!("Falha ao ligar por TCP/SSH: {}", err),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut session = match Session::new() {
|
||||||
|
Ok(session) => session,
|
||||||
|
Err(err) => {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: format!("Falha ao criar sessão SSH: {}", err),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
session.set_tcp_stream(tcp);
|
||||||
|
|
||||||
|
if let Err(err) = session.handshake() {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: format!("Falha no handshake SSH: {}", err),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = session.userauth_password(&username, &password) {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: format!("Falha na autenticação SSH: {}", err),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = build_prepare_command(&target_hostname, &new_password);
|
||||||
|
|
||||||
|
let logs = run_ssh_command(&session, &command).unwrap_or_default();
|
||||||
|
|
||||||
|
let hostname_updated = logs.contains("HOSTNAME_UPDATED");
|
||||||
|
let password_updated = logs.contains("PASSWORD_UPDATED");
|
||||||
|
let success = hostname_updated && password_updated;
|
||||||
|
|
||||||
|
OpenWrtPrepareResult {
|
||||||
|
success,
|
||||||
|
hostname_updated,
|
||||||
|
password_updated,
|
||||||
|
message: if success {
|
||||||
|
"Router preparado com sucesso.".to_string()
|
||||||
|
} else {
|
||||||
|
"Preparação do router incompleta. Verifique os logs.".to_string()
|
||||||
|
},
|
||||||
|
logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tcp_port_open(host: &str, port: u16) -> bool {
|
fn tcp_port_open(host: &str, port: u16) -> bool {
|
||||||
let address = format!("{}:{}", host, port);
|
let address = format!("{}:{}", host, port);
|
||||||
|
|
||||||
@@ -436,3 +566,82 @@ fn extract_prefixed_value(content: &str, prefix: &str) -> Option<String> {
|
|||||||
.map(|line| line.trim_start_matches(prefix).trim().to_string())
|
.map(|line| line.trim_start_matches(prefix).trim().to_string())
|
||||||
.filter(|value| !value.is_empty())
|
.filter(|value| !value.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_openwrt_router_with_system_ssh(
|
||||||
|
host: String,
|
||||||
|
username: String,
|
||||||
|
target_hostname: String,
|
||||||
|
new_password: String
|
||||||
|
) -> OpenWrtPrepareResult {
|
||||||
|
let target = format!("{}@{}", username, host);
|
||||||
|
let command = build_prepare_command(&target_hostname, &new_password);
|
||||||
|
|
||||||
|
let output = Command::new("ssh")
|
||||||
|
.args([
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=NUL",
|
||||||
|
"-o",
|
||||||
|
"BatchMode=yes",
|
||||||
|
"-o",
|
||||||
|
"ConnectTimeout=5",
|
||||||
|
&target,
|
||||||
|
&command,
|
||||||
|
])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let output = match output {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(err) => {
|
||||||
|
return OpenWrtPrepareResult {
|
||||||
|
success: false,
|
||||||
|
hostname_updated: false,
|
||||||
|
password_updated: false,
|
||||||
|
message: format!("Falha ao executar ssh local: {}", err),
|
||||||
|
logs: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
|
let logs = format!("{}\n{}", stdout, stderr);
|
||||||
|
|
||||||
|
let hostname_updated = logs.contains("HOSTNAME_UPDATED");
|
||||||
|
let password_updated = logs.contains("PASSWORD_UPDATED");
|
||||||
|
let success = output.status.success() && hostname_updated && password_updated;
|
||||||
|
|
||||||
|
OpenWrtPrepareResult {
|
||||||
|
success,
|
||||||
|
hostname_updated,
|
||||||
|
password_updated,
|
||||||
|
message: if success {
|
||||||
|
"Router preparado com sucesso.".to_string()
|
||||||
|
} else {
|
||||||
|
"Preparação do router incompleta. Verifique os logs.".to_string()
|
||||||
|
},
|
||||||
|
logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_prepare_command(target_hostname: &str, new_password: &str) -> String {
|
||||||
|
let safe_hostname = shell_escape_single_quotes(target_hostname);
|
||||||
|
let safe_password = shell_escape_single_quotes(new_password);
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"set -e; \
|
||||||
|
uci set system.@system[0].hostname='{hostname}'; \
|
||||||
|
uci commit system; \
|
||||||
|
/etc/init.d/system reload >/dev/null 2>&1 || true; \
|
||||||
|
echo HOSTNAME_UPDATED; \
|
||||||
|
printf '%s\\n%s\\n' '{password}' '{password}' | passwd root; \
|
||||||
|
echo PASSWORD_UPDATED",
|
||||||
|
hostname = safe_hostname,
|
||||||
|
password = safe_password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shell_escape_single_quotes(value: &str) -> String {
|
||||||
|
value.replace('\'', "'\\''")
|
||||||
|
}
|
||||||
|
|||||||
+76
-1
@@ -1381,7 +1381,7 @@ td .small-action {
|
|||||||
.openwrt-form {
|
.openwrt-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr auto;
|
grid-template-columns: 1fr 1fr 1fr auto;
|
||||||
align-items: end;
|
align-items: start;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1423,3 +1423,78 @@ td .small-action {
|
|||||||
.openwrt-grid .card-header {
|
.openwrt-grid .card-header {
|
||||||
margin-bottom: 22px;
|
margin-bottom: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.openwrt-prepare-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-prepare-form label {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-prepare-form input,
|
||||||
|
.openwrt-prepare-form select {
|
||||||
|
height: 44px;
|
||||||
|
border: 1px solid #edf1f7;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0 14px;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #111827;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-prepare-form input:read-only {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-form .form-error {
|
||||||
|
color: #dc2626;
|
||||||
|
margin-top: 4px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-button-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-form label,
|
||||||
|
.openwrt-prepare-form label {
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-form .form-error,
|
||||||
|
.openwrt-prepare-form .form-error {
|
||||||
|
color: #dc2626 !important;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-top: 4px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openwrt-form input:focus,
|
||||||
|
.openwrt-prepare-form input:focus,
|
||||||
|
.openwrt-prepare-form .custom-select-button:focus {
|
||||||
|
outline: 0 !important;
|
||||||
|
background: white;
|
||||||
|
border-color: #5da8ff !important;
|
||||||
|
box-shadow: 0 0 0 4px rgba(93, 168, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-note + .log-box {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-list + .status-note {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
+1
-1
@@ -408,7 +408,7 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{page === "openwrt" && (
|
{page === "openwrt" && (
|
||||||
<OpenWrtConfigPage />
|
<OpenWrtConfigPage routers={routers} />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
+245
-12
@@ -1,5 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { ChevronDown } from "lucide-react";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import type { RouterItem } from "../types/router";
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
@@ -17,6 +19,14 @@ type DetectionResult = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PrepareResult = {
|
||||||
|
success: boolean;
|
||||||
|
hostname_updated: boolean;
|
||||||
|
password_updated: boolean;
|
||||||
|
message: string;
|
||||||
|
logs: string;
|
||||||
|
};
|
||||||
|
|
||||||
type CompatibilityResult = {
|
type CompatibilityResult = {
|
||||||
compatible: boolean;
|
compatible: boolean;
|
||||||
version: string | null;
|
version: string | null;
|
||||||
@@ -44,7 +54,11 @@ type SshResult = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function OpenWrtConfigPage() {
|
type Props = {
|
||||||
|
routers: RouterItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function OpenWrtConfigPage({ routers }: Props) {
|
||||||
const [detecting, setDetecting] = useState(false);
|
const [detecting, setDetecting] = useState(false);
|
||||||
const [connecting, setConnecting] = useState(false);
|
const [connecting, setConnecting] = useState(false);
|
||||||
const [detection, setDetection] = useState<DetectionResult | null>(null);
|
const [detection, setDetection] = useState<DetectionResult | null>(null);
|
||||||
@@ -53,6 +67,13 @@ export function OpenWrtConfigPage() {
|
|||||||
const [host, setHost] = useState("");
|
const [host, setHost] = useState("");
|
||||||
const [username, setUsername] = useState("root");
|
const [username, setUsername] = useState("root");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
const [selectedRouterId, setSelectedRouterId] = useState("");
|
||||||
|
const [newRootPassword, setNewRootPassword] = useState("");
|
||||||
|
const [confirmRootPassword, setConfirmRootPassword] = useState("");
|
||||||
|
const [routerDropdownOpen, setRouterDropdownOpen] = useState(false);
|
||||||
|
|
||||||
|
const [preparing, setPreparing] = useState(false);
|
||||||
|
const [prepareResult, setPrepareResult] = useState<PrepareResult | null>(null);
|
||||||
|
|
||||||
const compatibility = sshResult?.compatibility;
|
const compatibility = sshResult?.compatibility;
|
||||||
|
|
||||||
@@ -73,6 +94,46 @@ export function OpenWrtConfigPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sshAuthFailed =
|
||||||
|
sshResult &&
|
||||||
|
!sshResult.connected &&
|
||||||
|
sshResult.message.toLowerCase().includes("permission denied");
|
||||||
|
|
||||||
|
async function prepareRouter() {
|
||||||
|
if (!selectedRouter || !targetHostname) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setPreparing(true);
|
||||||
|
setPrepareResult(null);
|
||||||
|
|
||||||
|
const result = await invoke<PrepareResult>("prepare_openwrt_router", {
|
||||||
|
host,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
targetHostname,
|
||||||
|
newPassword: newRootPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
setPrepareResult(result);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
setPassword(newRootPassword);
|
||||||
|
setNewRootPassword("");
|
||||||
|
setConfirmRootPassword("");
|
||||||
|
|
||||||
|
const refreshed = await invoke<SshResult>("test_openwrt_ssh", {
|
||||||
|
host,
|
||||||
|
username,
|
||||||
|
password: newRootPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSshResult(refreshed);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setPreparing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function connectSsh() {
|
async function connectSsh() {
|
||||||
try {
|
try {
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
@@ -89,6 +150,14 @@ export function OpenWrtConfigPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedRouter = routers.find((router) => router.id === selectedRouterId);
|
||||||
|
|
||||||
|
const vpnIpParts = selectedRouter?.vpnIp?.split(".") ?? [];
|
||||||
|
|
||||||
|
const targetHostname = selectedRouter?.vpnIp
|
||||||
|
? `Litoral_Regas_${vpnIpParts[vpnIpParts.length - 1]}`
|
||||||
|
: "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="page-stack">
|
<section className="page-stack">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
@@ -161,19 +230,31 @@ export function OpenWrtConfigPage() {
|
|||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setPassword(e.target.value);
|
||||||
|
|
||||||
|
if (sshResult && !sshResult.connected) {
|
||||||
|
setSshResult(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="Password SSH"
|
placeholder="Password SSH"
|
||||||
/>
|
/>
|
||||||
|
{sshAuthFailed && (
|
||||||
|
<span className="form-error">
|
||||||
|
Password SSH inválida ou em falta. Introduza uma password válida.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
<div className="openwrt-button-wrap">
|
||||||
<button
|
<button
|
||||||
className="primary"
|
className="primary"
|
||||||
onClick={connectSsh}
|
onClick={connectSsh}
|
||||||
disabled={connecting || !host || !username}
|
disabled={connecting || !host || !username}
|
||||||
>
|
>
|
||||||
<ShieldCheck size={16} />
|
<ShieldCheck size={16} />
|
||||||
{connecting ? "A ligar..." : "Ligar ao Router"}
|
{connecting ? "A ligar..." : "Ligar ao Router"}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -199,7 +280,11 @@ export function OpenWrtConfigPage() {
|
|||||||
{!sshResult.connected && (
|
{!sshResult.connected && (
|
||||||
<div className="status-note">
|
<div className="status-note">
|
||||||
<XCircle size={24} />
|
<XCircle size={24} />
|
||||||
<p>{sshResult.message}</p>
|
<p>
|
||||||
|
{sshAuthFailed
|
||||||
|
? "Não foi possível autenticar. Verifique a password SSH."
|
||||||
|
: sshResult.message}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -311,8 +396,156 @@ export function OpenWrtConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{sshResult?.connected && sshResult.readiness && (
|
||||||
|
<div className="dashboard-card wide-panel">
|
||||||
|
<div className="card-header">
|
||||||
|
<div>
|
||||||
|
<h2>Preparação do Router</h2>
|
||||||
|
<p>Associe este router físico a um router da plataforma</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="openwrt-prepare-form">
|
||||||
|
<label>
|
||||||
|
Router da Plataforma
|
||||||
|
<div className="custom-select">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="custom-select-button"
|
||||||
|
onClick={() => setRouterDropdownOpen((prev) => !prev)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{selectedRouter
|
||||||
|
? `${selectedRouter.name} - ${selectedRouter.vpnIp}`
|
||||||
|
: "Selecionar router..."}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ChevronDown
|
||||||
|
size={16}
|
||||||
|
className={`chevron ${routerDropdownOpen ? "open" : ""}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{routerDropdownOpen && (
|
||||||
|
<div className="custom-select-menu">
|
||||||
|
{routers.map((router) => (
|
||||||
|
<button
|
||||||
|
key={router.id}
|
||||||
|
type="button"
|
||||||
|
className="custom-select-option"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedRouterId(router.id);
|
||||||
|
setRouterDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{router.name} - {router.vpnIp}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Hostname Gerado
|
||||||
|
<input value={targetHostname || "-"} readOnly />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Nova Password Root
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={newRootPassword}
|
||||||
|
onChange={(e) => setNewRootPassword(e.target.value)}
|
||||||
|
placeholder="Nova password"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Confirmar Password Root
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={confirmRootPassword}
|
||||||
|
onChange={(e) => setConfirmRootPassword(e.target.value)}
|
||||||
|
placeholder="Confirmar password"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{confirmRootPassword &&
|
||||||
|
newRootPassword !== confirmRootPassword && (
|
||||||
|
<span className="form-error">
|
||||||
|
As passwords não coincidem.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedRouter && (
|
||||||
|
<div className="detail-list">
|
||||||
|
<DetailRow label="Router selecionado" value={selectedRouter.name} />
|
||||||
|
<DetailRow label="IP VPN" value={selectedRouter.vpnIp || "-"} />
|
||||||
|
<DetailRow label="Hostname final" value={targetHostname} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button
|
||||||
|
className="primary"
|
||||||
|
onClick={prepareRouter}
|
||||||
|
disabled={
|
||||||
|
preparing ||
|
||||||
|
!selectedRouter ||
|
||||||
|
!targetHostname ||
|
||||||
|
!newRootPassword ||
|
||||||
|
newRootPassword !== confirmRootPassword
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{preparing ? "A preparar..." : "Preparar Router"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{prepareResult && (
|
||||||
|
<div className="dashboard-card wide-panel">
|
||||||
|
<div className="card-header">
|
||||||
|
<div>
|
||||||
|
<h2>Resultado da Preparação</h2>
|
||||||
|
<p>Estado da configuração inicial do router</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className={`badge ${prepareResult.success ? "success" : "failed"}`}>
|
||||||
|
{prepareResult.success ? "Sucesso" : "Falhou"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="detail-list">
|
||||||
|
<DetailRow
|
||||||
|
label="Hostname"
|
||||||
|
value={prepareResult.hostname_updated ? "Atualizado" : "Falhou"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DetailRow
|
||||||
|
label="Password Root"
|
||||||
|
value={prepareResult.password_updated ? "Atualizada" : "Falhou"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="status-note">
|
||||||
|
{prepareResult.success ? <CheckCircle2 size={24} /> : <XCircle size={24} />}
|
||||||
|
<p>{prepareResult.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{prepareResult.logs && (
|
||||||
|
<div className="log-box">
|
||||||
|
<pre>{prepareResult.logs}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user