import React from "react"; import { useSidebar } from "@/components/ui/sidebar.tsx"; import { Status, StatusIndicator } from "@/components/ui/shadcn-io/status"; import { Separator } from "@/components/ui/separator.tsx"; import { Button } from "@/components/ui/button.tsx"; import { Tunnel } from "@/ui/desktop/apps/tunnel/Tunnel.tsx"; import { getServerStatusById, getServerMetricsById, executeSnippet, type ServerMetrics, } from "@/ui/main-axios.ts"; import { useTabs } from "@/ui/desktop/navigation/tabs/TabContext.tsx"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { type WidgetType, type StatsConfig, DEFAULT_STATS_CONFIG, } from "@/types/stats-widgets"; import { CpuWidget, MemoryWidget, DiskWidget, NetworkWidget, UptimeWidget, ProcessesWidget, SystemWidget, LoginStatsWidget, } from "./widgets"; import { SimpleLoader } from "@/ui/desktop/navigation/animations/SimpleLoader.tsx"; interface QuickAction { name: string; snippetId: number; } interface HostConfig { id: number; name: string; ip: string; username: string; folder?: string; enableFileManager?: boolean; tunnelConnections?: unknown[]; quickActions?: QuickAction[]; statsConfig?: string | StatsConfig; [key: string]: unknown; } interface TabData { id: number; type: string; title?: string; hostConfig?: HostConfig; [key: string]: unknown; } interface ServerProps { hostConfig?: HostConfig; title?: string; isVisible?: boolean; isTopbarOpen?: boolean; embedded?: boolean; } export function Server({ hostConfig, title, isVisible = true, isTopbarOpen = true, embedded = false, }: ServerProps): React.ReactElement { const { t } = useTranslation(); const { state: sidebarState } = useSidebar(); const { addTab, tabs } = useTabs() as { addTab: (tab: { type: string; [key: string]: unknown }) => number; tabs: TabData[]; }; const [serverStatus, setServerStatus] = React.useState<"online" | "offline">( "offline", ); const [metrics, setMetrics] = React.useState(null); const [metricsHistory, setMetricsHistory] = React.useState( [], ); const [currentHostConfig, setCurrentHostConfig] = React.useState(hostConfig); const [isLoadingMetrics, setIsLoadingMetrics] = React.useState(false); const [isRefreshing, setIsRefreshing] = React.useState(false); const [showStatsUI, setShowStatsUI] = React.useState(true); const [executingActions, setExecutingActions] = React.useState>( new Set(), ); const statsConfig = React.useMemo((): StatsConfig => { if (!currentHostConfig?.statsConfig) { return DEFAULT_STATS_CONFIG; } try { const parsed = typeof currentHostConfig.statsConfig === "string" ? JSON.parse(currentHostConfig.statsConfig) : currentHostConfig.statsConfig; return { ...DEFAULT_STATS_CONFIG, ...parsed }; } catch (error) { console.error("Failed to parse statsConfig:", error); return DEFAULT_STATS_CONFIG; } }, [currentHostConfig?.statsConfig]); const enabledWidgets = statsConfig.enabledWidgets; const statusCheckEnabled = statsConfig.statusCheckEnabled !== false; const metricsEnabled = statsConfig.metricsEnabled !== false; React.useEffect(() => { if (hostConfig?.id !== currentHostConfig?.id) { // Reset state when switching to a different host setServerStatus("offline"); setMetrics(null); setMetricsHistory([]); setShowStatsUI(true); } setCurrentHostConfig(hostConfig); }, [hostConfig?.id]); const renderWidget = (widgetType: WidgetType) => { switch (widgetType) { case "cpu": return ; case "memory": return ( ); case "disk": return ; case "network": return ( ); case "uptime": return ( ); case "processes": return ( ); case "system": return ( ); case "login_stats": return ( ); default: return null; } }; React.useEffect(() => { const fetchLatestHostConfig = async () => { if (hostConfig?.id) { try { const { getSSHHosts } = await import("@/ui/main-axios.ts"); const hosts = await getSSHHosts(); const updatedHost = hosts.find((h) => h.id === hostConfig.id); if (updatedHost) { setCurrentHostConfig(updatedHost); } } catch { toast.error(t("serverStats.failedToFetchHostConfig")); } } }; fetchLatestHostConfig(); const handleHostsChanged = async () => { if (hostConfig?.id) { try { const { getSSHHosts } = await import("@/ui/main-axios.ts"); const hosts = await getSSHHosts(); const updatedHost = hosts.find((h) => h.id === hostConfig.id); if (updatedHost) { setCurrentHostConfig(updatedHost); } } catch { toast.error(t("serverStats.failedToFetchHostConfig")); } } }; window.addEventListener("ssh-hosts:changed", handleHostsChanged); return () => window.removeEventListener("ssh-hosts:changed", handleHostsChanged); }, [hostConfig?.id]); React.useEffect(() => { if (!statusCheckEnabled || !currentHostConfig?.id || !isVisible) { setServerStatus("offline"); return; } let cancelled = false; let intervalId: number | undefined; const fetchStatus = async () => { try { const res = await getServerStatusById(currentHostConfig?.id); if (!cancelled) { setServerStatus(res?.status === "online" ? "online" : "offline"); } } catch (error: unknown) { if (!cancelled) { const err = error as { response?: { status?: number }; }; if (err?.response?.status === 503) { setServerStatus("offline"); } else if (err?.response?.status === 504) { setServerStatus("offline"); } else if (err?.response?.status === 404) { setServerStatus("offline"); } else { setServerStatus("offline"); } } } }; fetchStatus(); intervalId = window.setInterval(fetchStatus, 10000); return () => { cancelled = true; if (intervalId) window.clearInterval(intervalId); }; }, [currentHostConfig?.id, isVisible, statusCheckEnabled]); React.useEffect(() => { if (!metricsEnabled || !currentHostConfig?.id || !isVisible) { setShowStatsUI(false); return; } let cancelled = false; let intervalId: number | undefined; const fetchMetrics = async () => { if (!currentHostConfig?.id) return; try { setIsLoadingMetrics(true); const data = await getServerMetricsById(currentHostConfig.id); if (!cancelled) { setMetrics(data); setMetricsHistory((prev) => { const newHistory = [...prev, data]; return newHistory.slice(-20); }); setShowStatsUI(true); } } catch (error: unknown) { if (!cancelled) { const err = error as { code?: string; response?: { status?: number; data?: { error?: string } }; }; if (err?.response?.status === 404) { setMetrics(null); setShowStatsUI(false); } else if ( err?.code === "TOTP_REQUIRED" || (err?.response?.status === 403 && err?.response?.data?.error === "TOTP_REQUIRED") ) { setMetrics(null); setShowStatsUI(false); toast.error(t("serverStats.totpUnavailable")); } else { setMetrics(null); setShowStatsUI(false); toast.error(t("serverStats.failedToFetchMetrics")); } } } finally { if (!cancelled) { setIsLoadingMetrics(false); } } }; fetchMetrics(); intervalId = window.setInterval(fetchMetrics, 10000); return () => { cancelled = true; if (intervalId) window.clearInterval(intervalId); }; }, [currentHostConfig?.id, isVisible, metricsEnabled]); const topMarginPx = isTopbarOpen ? 74 : 16; const leftMarginPx = sidebarState === "collapsed" ? 16 : 8; const bottomMarginPx = 8; const isFileManagerAlreadyOpen = React.useMemo(() => { if (!currentHostConfig) return false; return tabs.some( (tab: TabData) => tab.type === "file_manager" && tab.hostConfig?.id === currentHostConfig.id, ); }, [tabs, currentHostConfig]); const wrapperStyle: React.CSSProperties = embedded ? { opacity: isVisible ? 1 : 0, height: "100%", width: "100%" } : { opacity: isVisible ? 1 : 0, marginLeft: leftMarginPx, marginRight: 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, }; const containerClass = embedded ? "h-full w-full text-white overflow-hidden bg-transparent" : "bg-dark-bg text-white rounded-lg border-2 border-dark-border overflow-hidden"; return (

{currentHostConfig?.folder} / {title}

{statusCheckEnabled && ( )}
{currentHostConfig?.enableFileManager && ( )}
{(metricsEnabled && showStatsUI) || (currentHostConfig?.quickActions && currentHostConfig.quickActions.length > 0) ? (
{currentHostConfig?.quickActions && currentHostConfig.quickActions.length > 0 && (

{t("serverStats.quickActions")}

{currentHostConfig.quickActions.map((action, index) => { const isExecuting = executingActions.has( action.snippetId, ); return ( ); })}
)} {metricsEnabled && showStatsUI && (!metrics && serverStatus === "offline" ? (

{t("serverStats.serverOffline")}

{t("serverStats.cannotFetchMetrics")}

) : (
{enabledWidgets.map((widgetType) => (
{renderWidget(widgetType)}
))}
))} {metricsEnabled && showStatsUI && ( )}
) : null} {currentHostConfig?.tunnelConnections && currentHostConfig.tunnelConnections.length > 0 && (
)}

{t("serverStats.feedbackMessage")}{" "} GitHub !

); }