diff --git a/src-tauri/resources/udp2raw/setup_udp2raw.sh b/src-tauri/resources/udp2raw/setup_udp2raw.sh new file mode 100644 index 0000000..be7d861 --- /dev/null +++ b/src-tauri/resources/udp2raw/setup_udp2raw.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +set -eu + +echo "======================================" +echo " UDP2RAW WireGuard Client Setup" +echo "======================================" + +VPS_HOST="146.59.230.190" +UDP2RAW_REMOTE_PORT="444" + +LOCAL_WG_PORT="4999" + +UDP2RAW_PASSWORD="test123" +RAW_MODE="faketcp" + +WG_MTU="1240" + +INIT_SCRIPT="/etc/init.d/udp2raw-wg" + +echo "" +echo "[1/10] Checking udp2raw binary..." + +if ! command -v udp2raw >/dev/null 2>&1; then + echo "ERROR: udp2raw binary is missing" + exit 10 +fi + +echo "udp2raw binary found:" +command -v udp2raw + +echo "" +echo "[2/10] Stopping existing service if present..." + +if [ -f "$INIT_SCRIPT" ]; then + /etc/init.d/udp2raw-wg stop || true +fi + +pkill -f "/usr/bin/udp2raw" || true + +sleep 1 + +echo "" +echo "[3/10] Writing init.d service..." + +cat > "$INIT_SCRIPT" </dev/null 2>&1; then + echo "ERROR: WireGuard peer section network.wgserver was not found" + exit 30 +fi + +if ! uci show network.wg0 >/dev/null 2>&1; then + echo "ERROR: WireGuard interface section network.wg0 was not found" + exit 31 +fi + +uci set network.wgserver.endpoint_host='127.0.0.1' +uci set network.wgserver.endpoint_port="${LOCAL_WG_PORT}" +uci set network.wg0.mtu="${WG_MTU}" + +uci commit network + +echo "" +echo "[7/10] Restarting WireGuard interface..." + +ifdown wg0 || true +sleep 2 +ifup wg0 || true +sleep 5 + +echo "" +echo "[8/10] Checking udp2raw process..." + +if pgrep -af "^/usr/bin/udp2raw" >/dev/null 2>&1; then + echo "udp2raw process running:" + pgrep -af "^/usr/bin/udp2raw" +else + echo "ERROR: udp2raw process not running" + exit 20 +fi + +echo "" +echo "[9/10] Checking local listener..." + +if netstat -ln 2>/dev/null | grep -q "127.0.0.1:${LOCAL_WG_PORT}"; then + echo "Local listener active on 127.0.0.1:${LOCAL_WG_PORT}" +else + echo "WARNING: Could not confirm local listener" +fi + +echo "" +echo "[10/10] Testing connectivity..." + +ping -c 2 -W 2 "${VPS_HOST}" || true + +echo "" +echo "WireGuard endpoint:" +uci get network.wgserver.endpoint_host +uci get network.wgserver.endpoint_port + +echo "" +echo "WireGuard MTU:" +uci get network.wg0.mtu || true + +echo "" +echo "WireGuard status:" +wg show wg0 || true + +echo "" +echo "======================================" +echo " UDP2RAW setup completed successfully" +echo "======================================" \ No newline at end of file diff --git a/src-tauri/resources/udp2raw/udp2raw b/src-tauri/resources/udp2raw/udp2raw new file mode 100644 index 0000000..46ad3f8 Binary files /dev/null and b/src-tauri/resources/udp2raw/udp2raw differ diff --git a/src-tauri/src/commands/router.rs b/src-tauri/src/commands/router.rs index 5dc3e33..70299ea 100644 --- a/src-tauri/src/commands/router.rs +++ b/src-tauri/src/commands/router.rs @@ -714,3 +714,257 @@ pub async fn upload_provisioning_bundle( Ok(format!("uploaded provision.sh and router.env to {}", ip)) } + +#[tauri::command] +pub async fn upload_udp2raw_setup_script(ip: String, password: String) -> Result { + if ip.trim().is_empty() { + return Err("router IP is required".into()); + } + + let local_script_path = "resources/udp2raw/setup_udp2raw.sh"; + let remote_script_path = "/tmp/setup_udp2raw.sh"; + + if password.trim().is_empty() { + let target = format!("root@{}:{}", ip, remote_script_path); + + let output = Command::new("scp") + .args([ + "-O", + "-o", + "BatchMode=yes", + "-o", + "ConnectTimeout=10", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=NUL", + local_script_path, + &target, + ]) + .output() + .map_err(|error| format!("failed to run scp for setup_udp2raw.sh: {}", error))?; + + if !output.status.success() { + return Err( + format!( + "failed to upload setup_udp2raw.sh:\n{}\n{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ) + ); + } + + run_system_ssh(&ip, "chmod +x /tmp/setup_udp2raw.sh")?; + + return Ok(format!("uploaded setup_udp2raw.sh to {}", ip)); + } + + let session = open_router_session(&ip, &password)?; + + scp_file_from_disk(&session, local_script_path, remote_script_path, 0o755)?; + + run_ssh_command(&session, "chmod +x /tmp/setup_udp2raw.sh")?; + + Ok(format!("uploaded setup_udp2raw.sh to {}", ip)) +} + +#[tauri::command] +pub async fn run_udp2raw_setup(ip: String, password: String) -> Result { + if ip.trim().is_empty() { + return Err("router IP is required".into()); + } + + let command = "sh /tmp/setup_udp2raw.sh"; + + if password.trim().is_empty() { + return run_system_ssh(&ip, command); + } + + let session = open_router_session(&ip, &password)?; + + run_ssh_command(&session, command) +} + +#[tauri::command] +pub async fn check_udp2raw_router_status(ip: String, password: String) -> Result { + if ip.trim().is_empty() { + return Err("router IP is required".into()); + } + + let command = + r#" + echo "== udp2raw binary ==" + if command -v udp2raw >/dev/null 2>&1; then + command -v udp2raw + else + echo "missing" + fi + + echo "" + echo "== init script ==" + if [ -x /etc/init.d/udp2raw-wg ]; then + echo "present" + else + echo "missing" + fi + + echo "" + echo "== process ==" + if pgrep -af "^/usr/bin/udp2raw" >/dev/null 2>&1; then + pgrep -af "^/usr/bin/udp2raw" + else + echo "not running" + fi + + echo "" + echo "== service status ==" + if [ -x /etc/init.d/udp2raw-wg ]; then + /etc/init.d/udp2raw-wg status || true + else + echo "service unavailable" + fi + + echo "" + echo "== WireGuard configured endpoint ==" + uci get network.wgserver.endpoint_host 2>/dev/null || true + uci get network.wgserver.endpoint_port 2>/dev/null || true + + echo "" + echo "== local listener ==" + netstat -ln 2>/dev/null | grep -E '127.0.0.1:4999|:4999' || echo "listener not confirmed" + + echo "" + echo "== WireGuard runtime endpoint ==" + wg show wg0 2>/dev/null | grep -A8 '^peer:' || echo "wg0 unavailable" + "#; + + if password.trim().is_empty() { + return run_system_ssh(&ip, command); + } + + let session = open_router_session(&ip, &password)?; + + run_ssh_command(&session, command) +} + +#[tauri::command] +pub async fn test_udp2raw_tunnel(ip: String, password: String) -> Result { + if ip.trim().is_empty() { + return Err("router IP is required".into()); + } + + let command = r#" + echo "== udp2raw process ==" + if pgrep -af "^/usr/bin/udp2raw" >/dev/null 2>&1; then + pgrep -af "^/usr/bin/udp2raw" + else + echo "ERROR: udp2raw is not running" + exit 20 + fi + + echo "" + echo "== WireGuard configured endpoint ==" + uci get network.wgserver.endpoint_host 2>/dev/null || true + uci get network.wgserver.endpoint_port 2>/dev/null || true + + echo "" + echo "== local listener ==" + netstat -ln 2>/dev/null | grep -E '127.0.0.1:4999|:4999' || echo "WARNING: listener not confirmed" + + echo "" + echo "== ping VPS public IP ==" + ping -c 2 -W 2 146.59.230.190 || true + + echo "" + echo "== WireGuard status ==" + wg show wg0 2>/dev/null || echo "wg0 not available" + + echo "" + echo "== route check ==" + ip route || true + + echo "" + echo "UDP2RAW tunnel test completed" + "#; + + if password.trim().is_empty() { + return run_system_ssh(&ip, command); + } + + let mut last_error = String::new(); + + for attempt in 1..=5 { + match open_router_session(&ip, &password) { + Ok(session) => { + return run_ssh_command(&session, command); + } + Err(error) => { + last_error = format!( + "SSH attempt {}/5 failed: {}", + attempt, + error + ); + + thread::sleep(Duration::from_secs(2)); + } + } + } + + Err(last_error) +} + +#[tauri::command] +pub async fn upload_udp2raw_binary(ip: String, password: String) -> Result { + if ip.trim().is_empty() { + return Err("router IP is required".into()); + } + + let local_binary_path = "resources/udp2raw/udp2raw"; + let remote_binary_path = "/usr/bin/udp2raw"; + + if password.trim().is_empty() { + let target = format!("root@{}:{}", ip, remote_binary_path); + + let output = Command::new("scp") + .args([ + "-O", + "-o", + "BatchMode=yes", + "-o", + "ConnectTimeout=10", + "-o", + "StrictHostKeyChecking=no", + "-o", + "UserKnownHostsFile=NUL", + local_binary_path, + &target, + ]) + .output() + .map_err(|error| { format!("failed to run scp for udp2raw binary: {}", error) })?; + + if !output.status.success() { + return Err( + format!( + "failed to upload udp2raw binary:\n{}\n{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ) + ); + } + + run_system_ssh( + &ip, + "chmod +x /usr/bin/udp2raw && /usr/bin/udp2raw --help >/dev/null 2>&1 || true" + )?; + + return Ok("uploaded udp2raw binary to /usr/bin/udp2raw".into()); + } + + let session = open_router_session(&ip, &password)?; + + scp_file_from_disk(&session, local_binary_path, remote_binary_path, 0o755)?; + + run_ssh_command(&session, "chmod +x /usr/bin/udp2raw && ls -l /usr/bin/udp2raw")?; + + Ok("uploaded udp2raw binary to /usr/bin/udp2raw".into()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 816714f..cf0ba95 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -15,39 +15,48 @@ use commands::{ reconnect_router_after_flash, verify_router, wait_for_ssh, - check_router_after_flash - }, - ssh::{ - inspect_router_with_password, - probe_router_ssh, - remove_known_host, + check_router_after_flash, + upload_udp2raw_setup_script, + run_udp2raw_setup, + test_udp2raw_tunnel, + check_udp2raw_router_status, + upload_udp2raw_binary }, + ssh::{ inspect_router_with_password, probe_router_ssh, remove_known_host }, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() + tauri::Builder + ::default() .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) - .invoke_handler(tauri::generate_handler![ - read_text_file, - ping_host, - remove_known_host, - probe_router_ssh, - inspect_router_with_password, - detect_router, - upload_firmware, - upload_firmware_to_router, - flash_router, - flash_router_sysupgrade, - reconnect_router_after_flash, - wait_for_ssh, - upload_provisioning_bundle, - run_provisioning, - capture_wireguard_public_key, - verify_router, - check_router_after_flash - ]) + .invoke_handler( + tauri::generate_handler![ + read_text_file, + ping_host, + remove_known_host, + probe_router_ssh, + inspect_router_with_password, + detect_router, + upload_firmware, + upload_firmware_to_router, + flash_router, + flash_router_sysupgrade, + reconnect_router_after_flash, + wait_for_ssh, + upload_provisioning_bundle, + run_provisioning, + capture_wireguard_public_key, + verify_router, + check_router_after_flash, + upload_udp2raw_setup_script, + run_udp2raw_setup, + test_udp2raw_tunnel, + check_udp2raw_router_status, + upload_udp2raw_binary + ] + ) .run(tauri::generate_context!()) .expect("error while running tauri application"); -} \ No newline at end of file +} diff --git a/src/app/routes.tsx b/src/app/routes.tsx index 54a301f..e49b15c 100644 --- a/src/app/routes.tsx +++ b/src/app/routes.tsx @@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from 'react'; import { Clock, Database, + HardDrive, + RadioTower, Server, ShieldCheck, } from 'lucide-react'; @@ -15,6 +17,7 @@ import { ProvisioningWizard } from '@/components/provisioning/ProvisioningWizard import { BackendSettings } from '@/components/settings/BackendSettings'; import { ActivityLogs } from '@/components/activity/ActivityLogs'; import { Card } from '@/components/ui/Card'; +import { Udp2rawConfig } from '@/components/udp2raw/Udp2rawConfig'; import { vpnApi } from '@/services/vpnApi'; import { vpsApi } from '@/services/vpsApi'; @@ -50,7 +53,6 @@ export function DashboardRoute() { } catch (err) { setHealth(null); setUsedIps(null); - setError(String(err)); } } @@ -88,74 +90,159 @@ export function DashboardRoute() { const vpnHealthy = health?.wireGuardRunning === true; + const udp2rawHealthy = + health?.udp2rawActive === true; + const dashboardReady = !error && Boolean(health) && backendHealthy && - vpnHealthy; + vpnHealthy && + udp2rawHealthy; return (
{error && ( -
+
Erro no backend do painel:{' '} {error}
)} -
- } - /> +
+
+ } + tone={vpnHealthy ? 'green' : 'red'} + /> +
- } - /> +
+ } + tone={udp2rawHealthy ? 'purple' : 'red'} + /> +
- } - /> +
+ } + tone="blue" + /> +
- } - /> +
+ } + tone={ + (health?.diskUsagePercent ?? 0) > 85 + ? 'red' + : 'blue' + } + /> +
+ +
+ } + tone={backendHealthy ? 'green' : 'red'} + /> +
-
- +
+
-
+
+ + + +
+
+
+
+ +
+ +
+

+ Estado da VPS +

+ +

+ Uptime e carga do sistema +

+
+
+ +

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

+ +

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

+ +

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

+
+ +
+
+ Memória + {health?.memoryUsagePercent ?? 0}% +
+ +
+
+
+
+
+ +
+ +
- ); + return ; } if (active === 'Registos de Atividade') { diff --git a/src/components/dashboard/MetricCard.tsx b/src/components/dashboard/MetricCard.tsx index 8d11c91..e0c7e35 100644 --- a/src/components/dashboard/MetricCard.tsx +++ b/src/components/dashboard/MetricCard.tsx @@ -2,11 +2,51 @@ import { ReactNode } from 'react'; import { Card } from '@/components/ui/Card'; +type MetricCardTone = + | 'blue' + | 'green' + | 'purple' + | 'red'; + type MetricCardProps = { title: string; value: string; subtitle: string; icon: ReactNode; + tone?: MetricCardTone; +}; + +const toneStyles: Record< + MetricCardTone, + { + iconBg: string; + iconText: string; + glow: string; + } +> = { + blue: { + iconBg: 'bg-blue-500/10', + iconText: 'text-blue-300', + glow: 'from-blue-500/20', + }, + + green: { + iconBg: 'bg-emerald-500/10', + iconText: 'text-emerald-300', + glow: 'from-emerald-500/20', + }, + + purple: { + iconBg: 'bg-violet-500/10', + iconText: 'text-violet-300', + glow: 'from-violet-500/20', + }, + + red: { + iconBg: 'bg-red-500/10', + iconText: 'text-red-300', + glow: 'from-red-500/20', + }, }; export function MetricCard({ @@ -14,24 +54,35 @@ export function MetricCard({ value, subtitle, icon, + tone = 'blue', }: MetricCardProps) { + const style = toneStyles[tone]; + return ( - -
-
+ +
+ +
+ +
+
{icon}
-
-

+

+

{title}

-

+

{value}

-

+

{subtitle}

diff --git a/src/components/dashboard/NetworkTrafficChart.tsx b/src/components/dashboard/NetworkTrafficChart.tsx index 54cb410..2f484ff 100644 --- a/src/components/dashboard/NetworkTrafficChart.tsx +++ b/src/components/dashboard/NetworkTrafficChart.tsx @@ -14,6 +14,17 @@ import { Card } from '@/components/ui/Card'; import { vpsApi } from '@/services/vpsApi'; +type TrafficMode = + | 'wireguard' + | 'udp2raw'; + +type NetworkTrafficChartProps = { + title?: string; + subtitle?: string; + mode?: TrafficMode; + compact?: boolean; +}; + type TrafficPoint = { time: string; downloadMbps: number; @@ -22,7 +33,12 @@ type TrafficPoint = { const MAX_POINTS = 24; -export function NetworkTrafficChart() { +export function NetworkTrafficChart({ + title = 'Tráfego de Rede', + subtitle = 'Débito RX/TX em direto', + mode = 'wireguard', + compact = false, +}: NetworkTrafficChartProps) { const [points, setPoints] = useState< TrafficPoint[] >([]); @@ -35,7 +51,9 @@ export function NetworkTrafficChart() { async function loadTraffic() { try { const response = - await vpsApi.networkTraffic(); + mode === 'udp2raw' + ? await vpsApi.udp2rawTraffic() + : await vpsApi.networkTraffic(); if (!mounted) { return; @@ -66,6 +84,9 @@ export function NetworkTrafficChart() { } } + setPoints([]); + setError(''); + loadTraffic(); const intervalId = window.setInterval( @@ -77,23 +98,28 @@ export function NetworkTrafficChart() { mounted = false; window.clearInterval(intervalId); }; - }, []); + }, [mode]); + + const latestPoint = + points[points.length - 1]; return ( - -
-
-

- Tráfego de Rede + +
+ +
+
+

+ {title}

-

- Débito RX/TX wg0 em direto +

+ {subtitle}

- - Atualização 3s + + 3s
@@ -103,12 +129,59 @@ export function NetworkTrafficChart() {
)} -
+ {compact && latestPoint && ( +
+
+

+ Entrada +

+ +

+ {latestPoint.downloadMbps.toFixed(3)} Mbps +

+
+ +
+

+ Saída +

+ +

+ {latestPoint.uploadMbps.toFixed(3)} Mbps +

+
+
+ )} + +
- + `${value} Mbps` } @@ -147,7 +222,11 @@ export function NetworkTrafficChart() { +
-

- Painel -

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

- Provisionamento de routers de produção - OpenWrt 23.05 WireGuard -

+
+

+ Painel +

+ +

+ Provisionamento de routers OpenWrt 23.05 com WireGuard e UDP2RAW +

+
+
-
-
- +
+
+ - Última atualização:{' '} - {new Date().toLocaleTimeString()} + Atualizado às{' '} + + {new Date().toLocaleTimeString()} +
- {healthy - ? 'Todos os sistemas operacionais' - : 'Problemas detetados no sistema'} - + ? 'Sistemas operacionais' + : 'Atenção necessária'} +
); diff --git a/src/components/udp2raw/Udp2rawConfig.tsx b/src/components/udp2raw/Udp2rawConfig.tsx new file mode 100644 index 0000000..01c96af --- /dev/null +++ b/src/components/udp2raw/Udp2rawConfig.tsx @@ -0,0 +1,424 @@ +import { useMemo, useState } from 'react'; +import { invoke } from '@tauri-apps/api/core'; +import { + Activity, + CheckCircle2, + Cpu, + Network, + Play, + RadioTower, + Search, + Terminal, + Eye, + EyeOff, + UploadCloud, +} from 'lucide-react'; + +import { Badge } from '@/components/ui/Badge'; +import { Button } from '@/components/ui/Button'; +import { Card } from '@/components/ui/Card'; + +type Udp2rawStep = { + id: string; + title: string; + description: string; + command: string; + logLabel: string; + icon: typeof Search; +}; + +const steps: Udp2rawStep[] = [ + { + id: 'status', + title: 'Verificar Estado', + description: 'Confirma binário, serviço, processo e porta local.', + command: 'check_udp2raw_router_status', + logLabel: 'A verificar estado UDP2RAW no router...', + icon: Search, + }, + { + id: 'binary', + title: 'Enviar Binário', + description: 'Copia o binário udp2raw para /usr/bin/udp2raw.', + command: 'upload_udp2raw_binary', + logLabel: 'A enviar binário UDP2RAW para o router...', + icon: Cpu, + }, + { + id: 'script', + title: 'Enviar Script', + description: 'Copia o script de instalação para /tmp.', + command: 'upload_udp2raw_setup_script', + logLabel: 'A enviar script UDP2RAW para o router...', + icon: UploadCloud, + }, + { + id: 'setup', + title: 'Executar Setup', + description: 'Cria o serviço init.d e arranca o túnel.', + command: 'run_udp2raw_setup', + logLabel: 'A executar configuração UDP2RAW...', + icon: Play, + }, + { + id: 'test', + title: 'Testar Túnel', + description: 'Valida processo, listener, ping e estado WireGuard.', + command: 'test_udp2raw_tunnel', + logLabel: 'A testar túnel UDP2RAW...', + icon: CheckCircle2, + }, +]; + +const scrollClass = + '[scrollbar-width:thin] [scrollbar-color:rgba(59,130,246,0.45)_transparent] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-blue-500/30 hover:[&::-webkit-scrollbar-thumb]:bg-blue-500/50'; + +export function Udp2rawConfig() { + const [routerIp, setRouterIp] = useState('198.51.100.1'); + const [password, setPassword] = useState('litoralr'); + const [runningStep, setRunningStep] = useState(null); + const [completedSteps, setCompletedSteps] = useState([]); + const [failedStep, setFailedStep] = useState(null); + const [log, setLog] = useState([]); + const [showPassword, setShowPassword] = + useState(false); + const running = Boolean(runningStep); + + const statusTone = useMemo(() => { + if (failedStep) return 'red'; + if (completedSteps.includes('test')) return 'green'; + if (running) return 'purple'; + return 'blue'; + }, [completedSteps, failedStep, running]); + + const statusLabel = useMemo(() => { + if (failedStep) return 'erro'; + if (completedSteps.includes('test')) return 'validado'; + if (running) return 'a executar'; + return 'pronto'; + }, [completedSteps, failedStep, running]); + + const progress = + (completedSteps.length / steps.length) * 100; + + function addLog(message: string) { + setLog((current) => [ + `${new Date().toLocaleTimeString()} ${message}`, + ...current, + ]); + } + + async function runStep(step: Udp2rawStep) { + if (running) { + return; + } + + setRunningStep(step.id); + setFailedStep(null); + + try { + addLog(step.logLabel); + + const result = await invoke(step.command, { + ip: routerIp, + password, + }); + + addLog(result || 'Concluído.'); + + setCompletedSteps((current) => [ + ...new Set([ + ...current, + step.id, + ]), + ]); + } catch (error) { + setFailedStep(step.id); + addLog(`ERRO: ${String(error)}`); + } finally { + setRunningStep(null); + } + } + + async function runFullFlow() { + if (running) { + return; + } + + setFailedStep(null); + + for (const step of steps) { + try { + await runStep(step); + } catch { + return; + } + } + } + + function clearLog() { + setLog([]); + setCompletedSteps([]); + setFailedStep(null); + } + + return ( +
+
+
+
+
+ +
+ +
+
+ +
+

+ Configuração UDP2RAW +

+ +

+ Instalação, arranque e validação do túnel UDP2RAW no router. +

+
+
+ + + {statusLabel} + +
+ +
+
+ +
+ +
+
+ +
+ +
+

+ Router Alvo +

+ +

+ Defina o router onde o cliente UDP2RAW será configurado. +

+
+
+ +
+
+ + + + setRouterIp(event.target.value) + } + className="w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 font-mono text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-500/50 focus:bg-black/30 focus:shadow-[0_0_0_4px_rgba(59,130,246,0.10)] disabled:cursor-not-allowed disabled:opacity-60" + /> +
+ +
+ + +
+ + setPassword(event.target.value) + } + placeholder="Vazio se não existir" + className="w-full rounded-2xl border border-white/10 bg-black/20 px-4 py-3 pr-14 font-mono text-sm text-white outline-none transition placeholder:text-slate-600 focus:border-blue-500/50 focus:bg-black/30 focus:shadow-[0_0_0_4px_rgba(59,130,246,0.10)] disabled:cursor-not-allowed disabled:opacity-60" + /> + + +
+
+
+ +
+ {[ + ['VPS', '146.59.230.190:444'], + ['Local', '127.0.0.1:4999'], + ['Modo', 'faketcp'], + ['Serviço', 'udp2raw-wg'], + ].map(([label, value]) => ( +
+

+ {label} +

+ +

+ {value} +

+
+ ))} +
+ + + +
+
+

+ Fluxo de Instalação +

+ +

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

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

+ Log UDP2RAW +

+ +

+ Saída dos comandos executados no router. +

+
+
+ + +
+ +
+            {log.join('\n\n') || 'Ainda não há atividade UDP2RAW.'}
+          
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/services/vpsApi.ts b/src/services/vpsApi.ts index 1164695..a53e98b 100644 --- a/src/services/vpsApi.ts +++ b/src/services/vpsApi.ts @@ -16,6 +16,11 @@ export const vpsApi = { '/api/vps/network-traffic', ), + udp2rawTraffic: () => + apiRequest( + '/api/vps/udp2raw-traffic', + ), + rollbackLastBackup: () => apiRequest<{ restored: boolean }>( '/api/vps/wireguard/rollback-last-backup',