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, 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, } from "./widgets"; interface HostConfig { id: number; name: string; ip: string; username: string; folder?: string; enableFileManager?: boolean; tunnelConnections?: unknown[]; 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 enabledWidgets = React.useMemo((): WidgetType[] => { if (!currentHostConfig?.statsConfig) { return DEFAULT_STATS_CONFIG.enabledWidgets; } try { const parsed = typeof currentHostConfig.statsConfig === "string" ? JSON.parse(currentHostConfig.statsConfig) : currentHostConfig.statsConfig; return parsed?.enabledWidgets || DEFAULT_STATS_CONFIG.enabledWidgets; } catch (error) { console.error("Failed to parse statsConfig:", error); return DEFAULT_STATS_CONFIG.enabledWidgets; } }, [currentHostConfig?.statsConfig]); React.useEffect(() => { setCurrentHostConfig(hostConfig); }, [hostConfig]); 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 ( ); 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(() => { 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"); } toast.error(t("serverStats.failedToFetchStatus")); } } }; 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]; // Keep last 20 data points for chart return newHistory.slice(-20); }); setShowStatsUI(true); } } catch (error: unknown) { if (!cancelled) { setMetrics(null); setShowStatsUI(false); const err = error as { code?: string; response?: { status?: number; data?: { error?: string } }; }; if ( err?.code === "TOTP_REQUIRED" || (err?.response?.status === 403 && err?.response?.data?.error === "TOTP_REQUIRED") ) { toast.error(t("serverStats.totpUnavailable")); } else { toast.error(t("serverStats.failedToFetchMetrics")); } } } finally { if (!cancelled) { setIsLoadingMetrics(false); } } }; if (currentHostConfig?.id && isVisible) { fetchStatus(); fetchMetrics(); intervalId = window.setInterval(() => { fetchStatus(); fetchMetrics(); }, 30000); } return () => { cancelled = true; if (intervalId) window.clearInterval(intervalId); }; }, [currentHostConfig?.id, isVisible]); 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}

{currentHostConfig?.enableFileManager && ( )}
{showStatsUI && (
{isLoadingMetrics && !metrics ? (
{t("serverStats.loadingMetrics")}
) : !metrics && serverStatus === "offline" ? (

{t("serverStats.serverOffline")}

{t("serverStats.cannotFetchMetrics")}

) : (
{enabledWidgets.map((widgetType) => (
{renderWidget(widgetType)}
))}
)}
)} {/* SSH Tunnels */} {currentHostConfig?.tunnelConnections && currentHostConfig.tunnelConnections.length > 0 && (
)}

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

); }