/* eslint-disable react-refresh/only-export-components */ import { Box, Boxes, Braces, FolderSearch, HardDrive, LayoutDashboard, LayoutGrid, LayoutPanelLeft, MessagesSquare, Monitor, MousePointerClick, Network, Server, Settings, Terminal, Usb, User, Activity, TerminalSquare, Layers, // --- tmux-monitor --- Clock, Fingerprint, Hammer, Play, Plug, ScrollText, Sparkles, Presentation, Workflow, } from "lucide-react"; import { lazy, Suspense } from "react"; import { useTranslation } from "react-i18next"; import type { SerialHandle } from "@/features/serial/serial-types"; import type { TerminalHandle, TerminalHostConfig, } from "@/features/terminal/Terminal"; import type { GuacamoleAppHandle } from "@/features/guacamole/GuacamoleApp"; import { useIsMobile } from "@/hooks/use-mobile"; import type { Tab, TabType, Host } from "@/types/ui-types"; import { isQuickConnectHost, quickConnectGuacHost, } from "@/sidebar/quick-connect-host"; import type { SSHHost } from "@/types"; import { useTabsSafe } from "@/shell/TabContext"; import { markAdaptiveResourceUsed, runAdaptiveBackgroundTask, } from "@/lib/adaptive-resource-budget"; // Heavy tab surfaces — keep out of the AppShell critical path. const CommandHistoryProvider = lazy(() => import("@/features/terminal/command-history/CommandHistoryContext").then( (m) => ({ default: m.CommandHistoryProvider }), ), ); const loadTerminalFeature = () => import("@/features/terminal/Terminal").then((m) => ({ default: m.Terminal, })); const TerminalFeature = lazy(loadTerminalFeature); const CollabRoomTab = lazy(() => import("@/features/collab/CollabRoomTab").then((m) => ({ default: m.CollabRoomTab, })), ); const LocalTerminal = lazy(() => import("@/features/local-terminal/LocalTerminal").then((m) => ({ default: m.LocalTerminal, })), ); const MobileTerminalKeyboard = lazy(() => import("@/features/terminal/MobileTerminalKeyboard").then((m) => ({ default: m.MobileTerminalKeyboard, })), ); const loadFileManager = () => import("@/features/file-manager/FileManager").then((m) => ({ default: m.FileManager, })); const FileManager = lazy(loadFileManager); const loadDockerManager = () => import("@/features/docker/DockerManager").then((m) => ({ default: m.DockerManager, })); const DockerManager = lazy(loadDockerManager); const loadHostMetricsTab = () => import("@/features/host-metrics/HostMetricsTab").then((m) => ({ default: m.HostMetricsTab, })); const HostMetricsTab = lazy(loadHostMetricsTab); const loadProxmoxStatsTab = () => import("@/features/proxmox-stats/ProxmoxStatsTab").then((m) => ({ default: m.ProxmoxStatsTab, })); const ProxmoxStatsTab = lazy(loadProxmoxStatsTab); const loadTmuxMonitor = () => import("@/features/tmux-monitor/TmuxMonitor").then((m) => ({ default: m.TmuxMonitor, })); const TmuxMonitor = lazy(loadTmuxMonitor); const loadGuacamoleApp = () => import("@/features/guacamole/GuacamoleApp").then((m) => ({ default: m.default, })); const GuacamoleApp = lazy(loadGuacamoleApp); const DashboardTab = lazy(() => import("@/dashboard/DashboardTab").then((m) => ({ default: m.DashboardTab, })), ); const HomepageCanvas = lazy(() => import("@/features/homepage/HomepageCanvas").then((m) => ({ default: m.HomepageCanvas, })), ); const loadTunnelTab = () => import("@/features/tunnel/TunnelTab").then((m) => ({ default: m.TunnelTab, })); const TunnelTab = lazy(loadTunnelTab); const NetworkGraphCard = lazy(() => import("@/dashboard/cards/NetworkGraphCard").then((m) => ({ default: m.NetworkGraphCard, })), ); const Serial = lazy(() => import("@/features/serial/Serial").then((m) => ({ default: m.Serial, })), ); const FleetInventoryTab = lazy(() => import("@/sidebar/FleetInventoryTab").then((m) => ({ default: m.FleetInventoryTab, })), ); // Rail panels promoted to full tabs. const TermixIdPanel = lazy(() => import("@/sidebar/TermixIdPanel").then((m) => ({ default: m.TermixIdPanel })), ); const AlertsPanel = lazy(() => import("@/sidebar/AlertsPanel").then((m) => ({ default: m.AlertsPanel })), ); const SessionLogsPanel = lazy(() => import("@/sidebar/SessionLogsPanel").then((m) => ({ default: m.SessionLogsPanel, })), ); const SnippetsPanel = lazy(() => import("@/sidebar/SnippetsPanel").then((m) => ({ default: m.SnippetsPanel })), ); const MacrosPanel = lazy(() => import("@/sidebar/MacrosPanel").then((m) => ({ default: m.MacrosPanel })), ); const HistoryPanel = lazy(() => import("@/sidebar/HistoryPanel").then((m) => ({ default: m.HistoryPanel })), ); const SshToolsPanel = lazy(() => import("@/sidebar/SshToolsPanel").then((m) => ({ default: m.SshToolsPanel })), ); const AutomationsPanel = lazy(() => import("@/sidebar/AutomationsPanel").then((m) => ({ default: m.AutomationsPanel, })), ); const AiPanel = lazy(() => import("@/features/ai/AiPanel").then((m) => ({ default: m.AiPanel, })), ); const tabSurfaceLoaders: Partial Promise>> = { terminal: loadTerminalFeature, files: loadFileManager, docker: loadDockerManager, "host-metrics": loadHostMetricsTab, "proxmox-stats": loadProxmoxStatsTab, tmux_monitor: loadTmuxMonitor, tunnel: loadTunnelTab, rdp: loadGuacamoleApp, vnc: loadGuacamoleApp, telnet: loadGuacamoleApp, }; /** Download a likely next tab without starting a connection or mounting UI. */ export function preloadTabSurface(type: TabType): void { const loader = tabSurfaceLoaders[type]; if (loader) runAdaptiveBackgroundTask("module", `tab:${type}`, loader); } export function markTabSurfaceUsed(type: TabType): void { markAdaptiveResourceUsed("module", `tab:${type}`); } function hostToSSHHost(h: Host): SSHHost { return { id: parseInt(h.id, 10), name: h.name, ip: h.ip, port: h.port, username: h.username, folder: h.folder ?? "", tags: h.tags ?? [], pin: h.pin ?? false, authType: h.authType, password: h.password, hasPassword: h.hasPassword, key: h.key, keyPassword: h.keyPassword, hasKey: h.hasKey, hasKeyPassword: h.hasKeyPassword, keyType: h.keyType, credentialId: h.credentialId ? parseInt(h.credentialId, 10) : undefined, terminalConfig: h.terminalConfig, hasSudoPassword: h.hasSudoPassword, enableTerminal: h.enableTerminal ?? false, enableTunnel: h.enableTunnel ?? false, enableFileManager: h.enableFileManager ?? false, enableDocker: h.enableDocker ?? false, dockerConfig: h.dockerConfig ?? null, showTerminalInSidebar: true, showFileManagerInSidebar: true, showTunnelInSidebar: true, showDockerInSidebar: true, showServerStatsInSidebar: true, defaultPath: h.defaultPath ?? "", tunnelConnections: [], connectionType: "ssh", connectionOrigin: h.connectionOrigin ?? null, // Carries the host's identity to a delegated backend. Without it the // remote side resolves our local row id against its own table. syncId: h.syncId ?? null, createdAt: "", updatedAt: "", } as unknown as SSHHost; } function EmptyState({ icon: Icon, messageKey, }: { icon: React.ElementType; messageKey: string; }) { const { t } = useTranslation(); return (
{t(messageKey)}
); } function TabChunkFallback() { return (
); } function withTabSuspense(node: React.ReactNode) { return }>{node}; } /** * Host frame for rail panels opened as tabs. Panels expect a full-height flex * column like the sidebar gives them. The max width keeps forms readable on a * wide monitor instead of stretching them edge to edge. */ function PanelTabFrame({ children }: { children: React.ReactNode }) { return (
{children}
); } export function tabIcon(type: TabType) { switch (type) { case "dashboard": return ; case "terminal": return ; case "local-terminal": return ; case "rdp": return ; case "vnc": return ; case "telnet": return ; case "host-metrics": return ; case "proxmox-stats": return ; case "files": return ; case "host-manager": return ; case "user-profile": return ; case "admin-settings": return ; case "docker": return ; case "tunnel": return ; case "network_graph": return ; // --- tmux-monitor --- case "tmux_monitor": return ; case "serial": return ; case "homepage": return ; case "fleet-inventory": return ; case "collab": return ; case "termix-id": return ; case "alerts": return ; case "session-logs": return ; case "snippets": return ; case "macros": return ; case "history": return ; case "ssh-tools": return ; case "automations": return ; case "ai": return ; case "split-screen": return ; } } function TerminalTabContent({ tab, host, label, isVisible, isFocusedPane, onCloseTab, onRenameTab, onOpenFileInEditor, onOpenFileManager, onOpenTab, onSaveQuickConnect, }: { tab: Tab; host: Host; label: string; isVisible: boolean; isFocusedPane: boolean; onCloseTab?: (id: string) => void; onRenameTab?: (tabId: string, newLabel: string) => void; onOpenFileInEditor?: (filePath: string) => void; onOpenFileManager?: (path?: string) => void; onOpenTab?: (type: TabType) => void; onSaveQuickConnect?: () => Promise; }) { const { previewTerminalTheme } = useTabsSafe(); const isMobile = useIsMobile(); return withTabSuspense(
} hostConfig={ { ...hostToSSHHost(host), sshPort: host.sshPort ?? host.port, instanceId: tab.instanceId ?? tab.id, restoredSessionId: tab.restoredSessionId ?? null, joinSharedSessionId: tab.joinSharedSessionId ?? null, joinShareId: tab.joinShareId ?? null, } as TerminalHostConfig } isVisible={isVisible} initialPath={tab.initialFilePath} title={label} showTitle={false} splitScreen={false} onClose={() => onCloseTab?.(tab.id)} onTitleChange={ onRenameTab && host.terminalConfig?.useSSHTitle ? (title) => onRenameTab(tab.id, title) : undefined } previewTheme={previewTerminalTheme} onOpenFileInEditor={onOpenFileInEditor} onOpenFileManager={onOpenFileManager} isQuickConnect={host.id.startsWith("quick-connect-")} onSaveQuickConnect={onSaveQuickConnect} host={host} onOpenTab={onOpenTab} isFocusedPane={isFocusedPane} />
{isMobile && ( } /> )}
, ); } /** * Everything the promoted rail panels need from AppShell. Passed as one bag * rather than more positional params, which renderTabContent already has too * many of. */ export type PromotedPanelProps = { terminalTabs?: Tab[]; targetTerminalTabId?: string; storageMode?: "local" | "cloud"; }; export function renderTabContent( tab: Tab, onOpenSingletonTab?: (type: TabType) => void, onOpenTab?: (host: Host, type: TabType) => void, onCloseTab?: (id: string) => void, isVisible = true, onOpenFileInEditor?: (host: Host, filePath: string) => void, onOpenFileManager?: (host: Host, path?: string) => void, onOpenTerminalTab?: (host: Host, path?: string) => void, onRenameTab?: (tabId: string, newLabel: string) => void, onSaveQuickConnect?: (tab: Tab, host: Host) => Promise, isFocusedPane = true, panelProps?: PromotedPanelProps, ) { const { host, label } = tab; switch (tab.type) { case "dashboard": return withTabSuspense( , ); case "terminal": if (!host) return ( ); return ( onOpenFileInEditor(host, fp) : undefined } onOpenFileManager={ onOpenFileManager ? (p) => onOpenFileManager(host, p) : undefined } onOpenTab={onOpenTab ? (type) => onOpenTab(host, type) : undefined} onSaveQuickConnect={ onSaveQuickConnect ? () => onSaveQuickConnect(tab, host) : undefined } /> ); case "local-terminal": return withTabSuspense( , ); case "files": if (!host) return ( ); return withTabSuspense( onOpenTerminalTab(host, path) : undefined } />, ); case "docker": if (!host) return ; return withTabSuspense( , ); case "host-metrics": if (!host) return ( ); return withTabSuspense( , ); case "proxmox-stats": if (!host) return ( ); return withTabSuspense( , ); case "tunnel": return withTabSuspense( , ); case "rdp": case "vnc": case "telnet": if (!host) return ( ); return withTabSuspense( } hostId={host.id} tabId={tab.id} protocol={tab.type as "rdp" | "vnc" | "telnet"} isVisible={isVisible} quickConnectHost={ isQuickConnectHost(host) ? quickConnectGuacHost(host) : undefined } />, ); case "network_graph": return withTabSuspense( , ); // --- tmux-monitor --- case "tmux_monitor": return withTabSuspense( , ); case "serial": if (!tab.serialConfig) return ; return withTabSuspense( } config={tab.serialConfig} isVisible={isVisible} instanceId={tab.instanceId} />, ); case "homepage": return withTabSuspense(); case "fleet-inventory": return withTabSuspense( , ); case "collab": return withTabSuspense( , ); case "termix-id": return withTabSuspense( , ); case "alerts": return withTabSuspense( , ); case "session-logs": return withTabSuspense( , ); case "automations": return withTabSuspense( , ); case "ai": return withTabSuspense( , ); case "split-screen": return null; case "snippets": return withTabSuspense( , ); case "macros": return withTabSuspense( , ); case "history": return withTabSuspense( , ); case "ssh-tools": return withTabSuspense( , ); case "host-manager": case "user-profile": case "admin-settings": return null; } }