"use client" import { useEffect, useState } from "react" import Link from "next/link" import TunnelConfig, { type TunnelConfig as TunnelConfigType, type Server } from "@/components/tunnel-config" import { authClient } from "@/lib/auth-client" const defaultConfig: TunnelConfigType = { type: "http", serverPort: 443, localPort: 8000, } const formatStartedAgo = (timestamp?: ApiTimestamp): string | undefined => { if (!timestamp) return undefined const startedMs = timestamp.seconds * 1000 + Math.floor(timestamp.nanos / 1_000_000) const diffSeconds = Math.max(0, Math.floor((Date.now() - startedMs) / 1000)) if (diffSeconds < 60) return `${diffSeconds}s ago` const diffMinutes = Math.floor(diffSeconds / 60) if (diffMinutes < 60) return `${diffMinutes}m ago` const diffHours = Math.floor(diffMinutes / 60) if (diffHours < 24) return `${diffHours}h ago` const diffDays = Math.floor(diffHours / 24) return `${diffDays}d ago` } const toActiveConnection = (session: ApiSession): ActiveConnection => { const startedAgo = formatStartedAgo(session.started_at) return { id: session.slug || `${session.node}-${session.started_at?.seconds ?? Date.now()}`, name: session.slug || session.node || "Unknown tunnel", status: session.active ? "connected" : "error", protocol: (session.forwarding_type || "http").toLowerCase(), serverLabel: session.node || "Unknown node", remote: session.slug ? `${session.slug}.${session.node}` : session.node || "—", startedAgo, latencyMs: null, dataInOut: undefined, } } type ApiTimestamp = { seconds: number nanos: number } type ApiSession = { node: string forwarding_type: "HTTP" | "HTTPS" | "TCP" | string slug: string user_id: string active: boolean started_at?: ApiTimestamp } type ApiSessionList = ApiSession[] type SessionResponse = Awaited> interface DashboardClientProps { initialActiveConnections: ApiSessionList } type ActiveConnectionStatus = "connected" | "pending" | "error" type ActiveConnection = { id: string name: string status: ActiveConnectionStatus protocol: string serverLabel: string remote: string localPort?: number serverPort?: number startedAgo?: string latencyMs?: number | null dataInOut?: string } export default function DashboardClient({ initialActiveConnections }: DashboardClientProps) { const [selectedServer, setSelectedServer] = useState(null) const [tunnelConfig, setTunnelConfig] = useState(defaultConfig) const [statusMessage, setStatusMessage] = useState(null) const [activeConnections, setActiveConnections] = useState( initialActiveConnections.map(toActiveConnection), ) const { data: cachedSession } = authClient.useSession() const [session, setSession] = useState(cachedSession ?? null) useEffect(() => { setActiveConnections(initialActiveConnections.map(toActiveConnection)) }, [initialActiveConnections]) useEffect(() => { if (!session && cachedSession) { setSession(cachedSession) } }, [cachedSession, session]) const stopConnection = (id: string) => { setActiveConnections((prev) => prev.filter((conn) => conn.id !== id)) setStatusMessage("Connection stopped") } return (

Active Forwarding

Live tunnels for this session.

{statusMessage && (
{statusMessage}
)}

Active Connections

Monitor and manage your running tunnels

View logs
{activeConnections.length === 0 ? (
No active connections yet. Configure a tunnel to see it here.
) : (
{activeConnections.map((connection) => { const metaParts: string[] = [] if (connection.localPort && connection.serverPort) { metaParts.push(`Local ${connection.localPort} → Server ${connection.serverPort}`) } if (connection.startedAgo) { metaParts.push(connection.startedAgo) } const metaText = metaParts.length > 0 ? metaParts.join(" · ") : "No session metadata yet" return (
{connection.name} {connection.status === "connected" ? "Connected" : connection.status === "pending" ? "Reconnecting" : "Error"}

{(connection.protocol || "http").toUpperCase()} · {connection.serverLabel}

{connection.remote || "—"}

{metaText}

Latency

{connection.latencyMs != null ? `${connection.latencyMs}ms` : "—"}

Data

{connection.dataInOut || "—"}

) })}
)}

Custom Tunnel Configuration

Pick a location, test latency, and shape your tunnel exactly how you need.

View docs
) }