import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { TERMINAL_THEMES, TERMINAL_FONTS, BELL_STYLES, FAST_SCROLL_MODIFIERS, CURSOR_STYLES, } from "@/lib/terminal-themes"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { PasswordInput } from "@/components/password-input"; import { Slider } from "@/components/slider"; import { Globe, Network, Palette, Plus, Shield, Upload, X, Zap, } from "lucide-react"; import { toast } from "sonner"; import { SectionCard, SettingRow, FakeSwitch } from "@/components/section-card"; import { TerminalPreview } from "@/features/terminal/TerminalPreview"; import { createSSHHost, updateSSHHost, getSnippets, subscribeTunnelStatuses, connectTunnel, disconnectTunnel, } from "@/main-axios"; import type { Host } from "@/types/ui-types"; import type { SSHHost, TunnelStatus } from "@/types"; import { useTabsSafe } from "@/shell/TabContext"; import { buildHostEditorPayload, createHostEditorForm, mapSnippetResponse, type HostAuthType, type HostBellStyle, type HostBackspaceMode, type HostCursorStyle, type HostFastScrollModifier, type HostProtocols, } from "./HostEditorData"; import { HostDockerTab, HostFilesTab } from "./HostEditorFeatureTabs"; import { HostEditorGeneralTab } from "./HostEditorGeneralTab"; import { HostEditorRdpTab, HostEditorTelnetTab, HostEditorVncTab, } from "./HostEditorGuacamoleTabs"; import { HostStatsTab } from "./HostEditorStatsTab"; export function HostEditor({ host, activeTab, onBack, onSave, protocols, onProtocolChange, onTabChange, hosts, credentials, }: { host: Host | null; activeTab: string; onBack: () => void; onSave: (saved: SSHHost) => void; protocols: HostProtocols; onProtocolChange: (p: Partial) => void; onTabChange: (tab: string) => void; hosts: Host[]; credentials: { id: string; name: string; username: string }[]; }) { const { t } = useTranslation(); const { setPreviewTerminalTheme } = useTabsSafe(); const [form, setForm] = useState(() => createHostEditorForm(host)); const setField = (k: K, v: (typeof form)[K]) => setForm((p) => ({ ...p, [k]: v })); const setGuacField = (key: string, value: unknown) => setField("guacamoleConfig", { ...form.guacamoleConfig, [key]: value }); const [saving, setSaving] = useState(false); const [snippets, setSnippets] = useState<{ id: number; name: string }[]>([]); const [tunnelStatuses, setTunnelStatuses] = useState< Record >({}); const [connectingTunnel, setConnectingTunnel] = useState(null); useEffect(() => { getSnippets() .then((res) => setSnippets(mapSnippetResponse(res))) .catch(() => {}); }, []); useEffect(() => { if (activeTab !== "tunnels") return; const unsub = subscribeTunnelStatuses((s) => setTunnelStatuses(s)); return unsub; }, [activeTab]); const handleSave = async () => { setSaving(true); try { const data = buildHostEditorPayload(form, protocols); const saved = host ? await updateSSHHost(Number(host.id), data) : await createSSHHost(data); toast.success(host ? t("hosts.hostUpdated") : t("hosts.hostCreated")); setPreviewTerminalTheme(null); onSave(saved); } catch { toast.error(t("hosts.failedToSave")); } finally { setSaving(false); } }; const authMethod = form.authType; const selectedCredential = credentials.find( (c) => c.id === form.credentialId, ); const handleProtocolToggle = ( proto: keyof typeof protocols, value: boolean, ) => { onProtocolChange({ [proto]: value }); const tabForProto: Record = { enableSsh: "ssh", enableRdp: "rdp", enableVnc: "vnc", enableTelnet: "telnet", }; if (!value && activeTab === tabForProto[proto]) onTabChange("general"); if (value && tabForProto[proto]) onTabChange(tabForProto[proto]); }; return (
{activeTab === "general" && ( )} {activeTab === "ssh" && ( <> } >
setField("sshPort", Number(e.target.value)) } className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" />
} >
{["password", "key", "credential", "none", "opkssh"].map( (m) => ( ), )}
setField("username", e.target.value)} />
{authMethod === "password" && (
setField("password", e.target.value)} />
)} {authMethod === "key" && ( <>
{(["paste", "upload"] as const).map((tab) => ( ))}
{form.keySubTab === "paste" ? (
{form.key === "existing_key" && (
{t("hosts.keySaved")} —{" "} {t("hosts.keyReplaceNotice")}
)}