import React, { useState, useEffect, useRef, useCallback, useImperativeHandle, } from "react"; import { GuacamoleDisplay, type GuacamoleDisplayHandle, } from "@/features/guacamole/GuacamoleDisplay.tsx"; import { getGuacamoleTokenFromHost, getGuacdStatus, getSSHHosts, logActivity, } from "@/main-axios.ts"; import { useTranslation } from "react-i18next"; import { AlertCircle, RefreshCw } from "lucide-react"; import { GuacamoleToolbar } from "@/features/guacamole/GuacamoleToolbar.tsx"; import { Button } from "@/components/button.tsx"; import { SimpleLoader } from "@/lib/SimpleLoader.tsx"; import type { SSHHost } from "@/types"; interface GuacamoleAppProps { hostId?: string; tabId?: string; protocol?: "rdp" | "vnc" | "telnet"; } export interface GuacamoleAppHandle { disconnect: () => void; isConnected: () => boolean; } const GuacamoleApp = React.forwardRef( function GuacamoleApp({ hostId, tabId, protocol }, ref) { const { t } = useTranslation(); const [hostConfig, setHostConfig] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { if (!hostId) { setLoading(false); return; } getSSHHosts() .then((hosts) => { const host = hosts.find((h) => h.id === parseInt(hostId, 10)); setHostConfig(host ?? null); }) .catch(() => setHostConfig(null)) .finally(() => setLoading(false)); }, [hostId]); if (loading) { return (
); } if (!hostConfig || !hostId) { return (
{t("guacamole.hostNotFound")}
); } return ( ); }, ); interface GuacamoleAppInnerProps { hostId: number; hostConfig: Pick; hostName: string; tabId?: string; protocol?: "rdp" | "vnc" | "telnet"; } const GuacamoleAppInner = React.forwardRef< GuacamoleAppHandle, GuacamoleAppInnerProps >(function GuacamoleAppInner( { hostId, hostConfig, hostName, tabId, protocol }, ref, ) { const { t } = useTranslation(); const [token, setToken] = useState(null); const [error, setError] = useState(null); const [connectionError, setConnectionError] = useState(null); const [retryCount, setRetryCount] = useState(0); const displayRef = useRef(null); useImperativeHandle(ref, () => ({ disconnect: () => displayRef.current?.disconnect(), isConnected: () => displayRef.current?.isConnected() === true, })); useEffect(() => { setToken(null); setError(null); getGuacdStatus() .then((status) => { if (status.guacd.status !== "connected") { setError(t("guacamole.guacdUnavailable")); return; } return getGuacamoleTokenFromHost(hostId, protocol); }) .then((result) => { if (result) { setToken(result.token); const resolvedProtocol = (protocol ?? hostConfig.connectionType ?? "rdp") as "rdp" | "vnc" | "telnet"; logActivity(resolvedProtocol, hostId, hostName).catch(() => {}); } }) .catch((err) => setError(err?.message || t("guacamole.failedToConnect"))); }, [hostConfig.connectionType, hostId, hostName, protocol, retryCount, t]); const handleReconnect = useCallback(() => { setConnectionError(null); setError(null); setToken(null); setRetryCount((c) => c + 1); }, []); useEffect(() => { if (!tabId) return; const handler = (e: Event) => { const { tabId: eventTabId } = (e as CustomEvent).detail; if (eventTabId === tabId) handleReconnect(); }; window.addEventListener("termix:refresh-guacamole", handler); return () => window.removeEventListener("termix:refresh-guacamole", handler); }, [tabId, handleReconnect]); if (error) { return (

{t("guacamole.connectionFailed")}

{error}

); } if (!token) { return (
); } const resolvedProtocol = (protocol ?? hostConfig.connectionType) as | "rdp" | "vnc" | "telnet"; return (
{connectionError && (

{t("guacamole.connectionFailed")}

{connectionError}

)} setConnectionError(err)} />
); }); export default GuacamoleApp;