// Interactive view of a tmux pane. Embeds the real SSH terminal (through the // terminal.view component ssh-terminal registers) and attaches to the // selected session via the native tmux_attach flow, so the pane is fully // usable: typing, mouse scrolling and tmux copy-mode all behave exactly like // a normal terminal tab. import { useRef, useState } from "react"; import { useTranslation } from "@termix/plugin-sdk/frontend"; import { Activity, Cpu, MemoryStick, RotateCw, SquareSplitHorizontal, SquareSplitVertical, Trash2, X, } from "lucide-react"; import { PluginComponent } from "@termix/plugin-sdk/ui"; import type { PluginHostRecord, TabHandle } from "@termix/plugin-sdk/frontend"; import type { TmuxPaneMetrics } from "./api"; import { formatMem } from "./format"; import type { SelectedPane } from "./types"; interface PanePreviewProps { host: PluginHostRecord; pane: SelectedPane; metrics?: TmuxPaneMetrics; /** Imperative handle of the embedded terminal, used by the parent to nudge * a refit/redraw after layout-changing tmux actions. */ terminalRef?: React.RefObject; /** Split the window containing this pane ("h" = new pane to the right, * "v" = below — tmux -h/-v semantics). */ onSplit: (direction: "h" | "v") => void; /** Ask for confirmation and kill this pane. */ onKillPane: () => void; onClose: () => void; } function newInstanceId(): string { return typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; } export function PanePreview({ host, pane, metrics, terminalRef, onSplit, onKillPane, onClose, }: PanePreviewProps) { const { t } = useTranslation(); // One PTY per mount; the parent remounts this component (via key) when the // host or session changes. Pane switches within a session go through the // focus endpoint instead, so the connection is reused. const instanceIdRef = useRef(newInstanceId()); // Bumping this remounts the embedded terminal with a fresh PTY — the rescue // hatch when the attached client's rendering gets out of sync. const [attachNonce, setAttachNonce] = useState(0); function reattach() { instanceIdRef.current = newInstanceId(); setAttachNonce((n) => n + 1); } // The host's own authType travels in the spread, so a Tailscale host still // gets the Tailscale-aware PTY path. The SSH port may be non-standard. const resolvedPort = host.sshPort ?? host.port; const terminalHostConfig = { ...host, port: resolvedPort, sshPort: resolvedPort, instanceId: instanceIdRef.current, }; return ( <>
{pane.sessionName} {pane.paneId} {metrics && ( <> {metrics.cpuPercent.toFixed(1)}% {formatMem(metrics.memRssKb)} {metrics.gpuMemMb > 0 && ( {metrics.gpuMemMb} MB GPU )} {metrics.topCommand && ( {metrics.topCommand} ({metrics.processCount}) )} )}
); }