import { useCallback, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { api } from '../../api/client'; import type { Agent, AgentCapabilities } from '../../types'; import { canRunAggressiveAction, aggressiveActionHint } from '../../help/aggressiveActions'; import './ProtocolTunnelPanel.css'; export interface TunnelStatusView { cloudflared_running?: boolean; cloudflared_url?: string; cloudflared_pid?: number; wireguard_active?: boolean; wireguard_detail?: string; ssh_forwards?: Array<{ local_port: number; remote_host: string; remote_port: number; ssh_user?: string; jump_host?: string; pid: number; running?: boolean; }>; } function parseTunnelStatus(message: string): TunnelStatusView | null { const start = message.indexOf('{'); if (start < 0) return null; try { return JSON.parse(message.slice(start)) as TunnelStatusView; } catch { return null; } } interface Props { agentId: string; agentName: string; online: boolean; caps?: AgentCapabilities | null; platform?: string; compact?: boolean; /** WS command_result messages — panel listens for tunnel_status */ lastTunnelStatusMessage?: string; onDispatch: (action: string, args?: Record) => void | Promise; busy?: string | null; } export default function ProtocolTunnelPanel({ agentId, agentName, online, caps, platform, compact = false, lastTunnelStatusMessage, onDispatch, busy, }: Props) { const [expanded, setExpanded] = useState(!compact); const [cfURL, setCfURL] = useState(''); const [localPort, setLocalPort] = useState('2222'); const [targetHostPort, setTargetHostPort] = useState('192.168.1.10:22'); const [sshUser, setSshUser] = useState(''); const [status, setStatus] = useState(null); const [statusRaw, setStatusRaw] = useState(''); useEffect(() => { api.getConfig().then((cfg) => { const fromTunnel = cfg.tunnel_defaults?.cloudflared_target_url?.trim(); const fromPublic = cfg.server?.public_url?.trim(); setCfURL(fromTunnel || fromPublic || ''); }).catch(() => {}); }, []); useEffect(() => { if (lastTunnelStatusMessage) { const parsed = parseTunnelStatus(lastTunnelStatusMessage); if (parsed) setStatus(parsed); setStatusRaw(lastTunnelStatusMessage); } }, [lastTunnelStatusMessage]); const tunnelAllowed = canRunAggressiveAction('start_tunnel', caps, platform); const tunnelHint = aggressiveActionHint('start_tunnel', caps, platform); const refreshStatus = useCallback(() => { if (!online || !agentId) return; void onDispatch('tunnel_status'); }, [online, agentId, onDispatch]); useEffect(() => { if (expanded && online) refreshStatus(); }, [expanded, online, refreshStatus]); const disabled = !online || !!busy; return (
{expanded && (

Encapsulates traffic for ops on your fleet — reach internal hosts and expose agent LAN services. Not for third-party evasion or hiding infrastructure.

Cloudflare Tunnel

Agent dials out to your control URL (no inbound port).

WireGuard (Path Tracer)

Multi-hop mesh VPN for owned nodes — configure sessions on the dashboard.

Open Path Tracer →

SSH Local Forward

Windows agent opens 127.0.0.1:local → LAN target via OpenSSH/plink (admin reach-through).

{(status || statusRaw) && (

tunnel_status — {agentName}

{status ? (
  • Cloudflared:{' '} {status.cloudflared_running ? `running (pid ${status.cloudflared_pid}) → ${status.cloudflared_url ?? ''}` : 'stopped'}
  • WireGuard:{' '} {status.wireguard_active ? 'active' : 'inactive'}
  • SSH forwards:{' '} {status.ssh_forwards?.length ? status.ssh_forwards .map( (f) => `127.0.0.1:${f.local_port} → ${f.remote_host}:${f.remote_port} (pid ${f.pid})` ) .join('; ') : 'none'}
) : (
{statusRaw.slice(0, 2000)}
)}
)}
)}
); } export { parseTunnelStatus };