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 { getServerStatusById, getServerMetricsById, startMetricsPolling, stopMetricsPolling, submitMetricsTOTP, executeSnippet, logActivity, sendMetricsHeartbeat, getSSHHosts, type ServerMetrics, } from "@/ui/main-axios.ts"; import { TOTPDialog } from "@/ui/desktop/navigation/TOTPDialog.tsx"; 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.ts"; 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 ServerStats({ hostConfig, title, isVisible = true, isTopbarOpen = true, embedded = false, }: ServerProps): React.ReactElement { const { t } = useTranslation(); const { state: sidebarState } = useSidebar(); const { addTab, tabs, currentTab, removeTab } = useTabs() as { addTab: (tab: { type: string; [key: string]: unknown }) => number; tabs: TabData[]; currentTab: number | null; removeTab: (tabId: number) => void; }; 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 [totpRequired, setTotpRequired] = React.useState(false); const [totpSessionId, setTotpSessionId] = React.useState(null); const [totpPrompt, setTotpPrompt] = React.useState(""); const [isPageVisible, setIsPageVisible] = React.useState(!document.hidden); const [totpVerified, setTotpVerified] = React.useState(false); const [viewerSessionId, setViewerSessionId] = React.useState( null, ); const activityLoggedRef = React.useRef(false); const activityLoggingRef = React.useRef(false); 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(() => { const handleVisibilityChange = () => { setIsPageVisible(!document.hidden); }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => document.removeEventListener("visibilitychange", handleVisibilityChange); }, []); const isActuallyVisible = isVisible && isPageVisible; React.useEffect(() => { if (!viewerSessionId || !isActuallyVisible) return; const heartbeatInterval = setInterval(async () => { try { await sendMetricsHeartbeat(viewerSessionId); } catch (error) { console.error("Failed to send heartbeat:", error); } }, 30000); return () => clearInterval(heartbeatInterval); }, [viewerSessionId, isActuallyVisible]); React.useEffect(() => { if (hostConfig?.id !== currentHostConfig?.id) { setServerStatus("offline"); setMetrics(null); setMetricsHistory([]); setShowStatsUI(true); } setCurrentHostConfig(hostConfig); }, [hostConfig?.id]); const logServerActivity = async () => { if ( !currentHostConfig?.id || activityLoggedRef.current || activityLoggingRef.current ) { return; } activityLoggingRef.current = true; activityLoggedRef.current = true; try { const hostName = currentHostConfig.name || `${currentHostConfig.username}@${currentHostConfig.ip}`; await logActivity("server_stats", currentHostConfig.id, hostName); } catch (err) { console.warn("Failed to log server stats activity:", err); activityLoggedRef.current = false; } finally { activityLoggingRef.current = false; } }; const handleTOTPSubmit = async (totpCode: string) => { if (!totpSessionId || !currentHostConfig) return; try { const result = await submitMetricsTOTP(totpSessionId, totpCode); if (result.success) { setTotpRequired(false); setTotpSessionId(null); setShowStatsUI(true); setTotpVerified(true); if (result.viewerSessionId) { setViewerSessionId(result.viewerSessionId); } } else { toast.error(t("serverStats.totpFailed")); } } catch (error) { toast.error(t("serverStats.totpFailed")); console.error("TOTP verification failed:", error); } }; const handleTOTPCancel = async () => { setTotpRequired(false); if (currentHostConfig?.id) { try { await stopMetricsPolling(currentHostConfig.id); } catch (error) { console.error("Failed to stop metrics polling:", error); } } if (currentTab !== null) { removeTab(currentTab); } }; 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 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 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) { 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, statsConfig.statusCheckInterval * 1000, ); return () => { cancelled = true; if (intervalId) window.clearInterval(intervalId); }; }, [ currentHostConfig?.id, statusCheckEnabled, statsConfig.statusCheckInterval, ]); React.useEffect(() => { if (!metricsEnabled || !currentHostConfig?.id) { return; } let cancelled = false; let pollingIntervalId: number | undefined; let debounceTimeout: NodeJS.Timeout | undefined; if (isActuallyVisible && !metrics) { setIsLoadingMetrics(true); setShowStatsUI(true); } else if (!isActuallyVisible) { setIsLoadingMetrics(false); } const startMetrics = async () => { if (cancelled) return; if (currentHostConfig.authType === "none") { toast.error(t("serverStats.noneAuthNotSupported")); setIsLoadingMetrics(false); if (currentTab !== null) { removeTab(currentTab); } return; } const hasExistingMetrics = metrics !== null; if (!hasExistingMetrics) { setIsLoadingMetrics(true); } setShowStatsUI(true); try { if (!totpVerified) { const result = await startMetricsPolling(currentHostConfig.id); if (cancelled) return; if (result.requires_totp) { setTotpRequired(true); setTotpSessionId(result.sessionId || null); setTotpPrompt(result.prompt || "Verification code"); setIsLoadingMetrics(false); return; } if (result.viewerSessionId) { setViewerSessionId(result.viewerSessionId); } } let retryCount = 0; let data = null; const maxRetries = 15; const retryDelay = 2000; while (retryCount < maxRetries && !cancelled) { try { data = await getServerMetricsById(currentHostConfig.id); break; } catch (error: any) { retryCount++; if (retryCount === 1) { const initialDelay = totpVerified ? 3000 : 5000; await new Promise((resolve) => setTimeout(resolve, initialDelay)); } else if (retryCount < maxRetries && !cancelled) { await new Promise((resolve) => setTimeout(resolve, retryDelay)); } else { throw error; } } } if (cancelled) return; if (data) { setMetrics(data); if (!hasExistingMetrics) { setIsLoadingMetrics(false); logServerActivity(); } } pollingIntervalId = window.setInterval(async () => { if (cancelled) return; try { const data = await getServerMetricsById(currentHostConfig.id); if (!cancelled) { setMetrics(data); setMetricsHistory((prev) => { const newHistory = [...prev, data]; return newHistory.slice(-20); }); } } catch (error) { if (!cancelled) { console.error("Failed to fetch metrics:", error); } } }, statsConfig.metricsInterval * 1000); } catch (error) { if (!cancelled) { console.error("Failed to start metrics polling:", error); setIsLoadingMetrics(false); toast.error(t("serverStats.failedToFetchMetrics")); if (currentTab !== null) { removeTab(currentTab); } } } }; const stopMetrics = async () => { if (pollingIntervalId) { window.clearInterval(pollingIntervalId); pollingIntervalId = undefined; } if (currentHostConfig?.id) { try { await stopMetricsPolling( currentHostConfig.id, viewerSessionId || undefined, ); } catch (error) { console.error("Failed to stop metrics polling:", error); } } }; debounceTimeout = setTimeout(() => { if (isActuallyVisible) { startMetrics(); } else { stopMetrics(); } }, 500); return () => { cancelled = true; if (debounceTimeout) clearTimeout(debounceTimeout); if (pollingIntervalId) window.clearInterval(pollingIntervalId); if (currentHostConfig?.id) { stopMetricsPolling(currentHostConfig.id).catch(() => {}); } }; }, [ currentHostConfig?.id, isActuallyVisible, metricsEnabled, statsConfig.metricsInterval, totpVerified, ]); 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-foreground overflow-hidden bg-transparent" : "bg-canvas text-foreground rounded-lg border-2 border-edge overflow-hidden"; return (
{!totpRequired && (

{currentHostConfig?.folder} / {title}

{statusCheckEnabled && ( )}
{currentHostConfig?.enableFileManager && ( )} {currentHostConfig?.enableDocker && ( )}
)} {!totpRequired && }
{(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 && !isLoadingMetrics && (!metrics && serverStatus === "offline" ? (

{t("serverStats.serverOffline")}

{t("serverStats.cannotFetchMetrics")}

) : metrics ? (
{enabledWidgets.map((widgetType) => (
{renderWidget(widgetType)}
))}
) : null)}
) : null} {metricsEnabled && ( )}
); }