import { getHostOsSelection, HOST_OS_SELECTIONS, resolveHostOs } from '../domain/host'; import React from "react"; import { AlertTriangle, ChevronDown, ChevronUp, Forward, Globe, HeartPulse, KeyRound, Link2, Palette, Plus, Router, ShieldAlert, TerminalSquare, Timer, Wifi, X, Variable } from "lucide-react"; import { customThemeStore } from "../application/state/customThemeStore"; import { clearHostFontSizeOverride, clearHostThemeOverride } from "../domain/terminalAppearance"; import { resolveSshAgentToggleUpdate } from "../domain/sshAuth"; import { MAX_FONT_SIZE, MIN_FONT_SIZE } from "../infrastructure/config/fonts"; import { AlgorithmOverridesPanel } from "./host-details/AlgorithmOverridesPanel"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { HostDetailsSection, HostDetailsSettingRow, HostDetailsOverrideReset } from "./host-details"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"; import { Input } from "./ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { Switch } from "./ui/switch"; import { Textarea } from "./ui/textarea"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"; import { DEFAULT_SSH_AUTH_READY_TIMEOUT_SECONDS, DEFAULT_SSH_TCP_CONNECT_TIMEOUT_SECONDS, MAX_SSH_CONNECTION_TIMEOUT_SECONDS, } from "../domain/sshConnectionTimeouts"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type HostDetailsAdvancedSectionsProps = Record; const ToggleRow: React.FC<{ label: string; hint?: React.ReactNode; enabled: boolean; disabled?: boolean; onToggle: () => void }> = ({ label, hint, enabled, disabled, onToggle }) => { return ( onToggle()} /> ); }; export const HostDetailsAdvancedSections: React.FC = ({ t, form, setForm, update, effectiveThemeId, hasEffectiveThemeOverride, effectiveFontSize, hasEffectiveFontSizeOverride, sshAgentStatus, sshForwardingAgentStatus, effectiveGroupDefaults, effectiveAuthMethod, showAlgorithmOverrides, setShowAlgorithmOverrides, chainedHosts, setActiveSubPanel, clearHostChain, proxySummaryType, proxySummaryLabel, proxySummaryTooltip, clearProxyConfig, }) => { const inheritedDeviceType = effectiveGroupDefaults?.deviceType; const effectiveDeviceType = form.deviceType ?? inheritedDeviceType; const inheritedStartupCommandRunMode = effectiveGroupDefaults?.startupCommandRunMode ?? "paste"; const effectiveStartupCommandRunMode = form.startupCommandRunMode ?? inheritedStartupCommandRunMode; const systemSshAgentSupported = effectiveAuthMethod === "auto" || effectiveAuthMethod === "key"; const systemSshAgentEnabled = effectiveAuthMethod === "auto" ? form.useSshAgent !== false : effectiveAuthMethod === "key" && form.useSshAgent === true; return ( <> } title={t("hostDetails.section.appearance")} > {/* SSH Theme Selection */}
{hasEffectiveThemeOverride && ( { event.stopPropagation(); setForm((prev) => clearHostThemeOverride(prev)); }} /> )}
{/* Font Size */}
{ const val = parseInt(e.target.value); if (val >= MIN_FONT_SIZE && val <= MAX_FONT_SIZE) { setForm((prev) => ({ ...prev, fontSize: val, fontSizeOverride: true, })); } }} className="h-8 w-16 text-center" /> pt
{hasEffectiveFontSizeOverride && ( setForm((prev) => clearHostFontSizeOverride(prev))} /> )}
} title={t("hostDetails.section.mosh")} > { const enabling = !form.moshEnabled; if (enabling) { setForm(prev => ({ ...prev, moshEnabled: true, etEnabled: false, x11Forwarding: undefined, })); } else { update("moshEnabled", false); } }} /> } title={t("hostDetails.section.et")} > { const enabling = !form.etEnabled; if (enabling) { setForm(prev => ({ ...prev, etEnabled: true, moshEnabled: false, x11Forwarding: undefined, })); } else { update("etEnabled", false); } }} /> {form.etEnabled && ( <> { const v = e.target.value.trim(); update("etPort", v === "" ? undefined : Number(v)); }} /> )} {/* System SSH Agent login */} } title={t("hostDetails.section.systemSshAgent")} > setForm((previous: typeof form) => { const enabled = effectiveAuthMethod === "auto" ? previous.useSshAgent !== false : effectiveAuthMethod === "key" && previous.useSshAgent === true; const enabling = !enabled; return { ...previous, // Automatic mode treats the ambient agent as an optional first // choice. Keep the unset state when re-enabling that default so // an unavailable agent still falls back to local keys/password. ...resolveSshAgentToggleUpdate(previous, effectiveAuthMethod, enabling), }; })} /> {systemSshAgentEnabled && ( <> setForm((previous: typeof form) => ({ ...previous, useSshAgent: true, identityAgent: event.target.value || undefined, }))} /> setForm((previous: typeof form) => ({ ...previous, useSshAgent: true, identitiesOnly: !previous.identitiesOnly, }))} /> )} {systemSshAgentEnabled && sshAgentStatus && !sshAgentStatus.running && (

{t("hostDetails.systemSshAgent.notRunning")}

{t("hostDetails.systemSshAgent.notRunningHint")}

)}
{/* Agent Forwarding */} } title={t("hostDetails.section.agentForwarding")} > update("agentForwarding", !form.agentForwarding)} /> {form.agentForwarding && sshForwardingAgentStatus && !sshForwardingAgentStatus.running && (

{t("hostDetails.agentForwarding.agentNotRunning")}

{t("hostDetails.agentForwarding.agentNotRunningHint")}

)}
{/* X11 Forwarding */} {(!form.protocol || form.protocol === "ssh") && !form.moshEnabled && !form.etEnabled && ( } title={t("hostDetails.section.x11Forwarding")} > update("x11Forwarding", !form.x11Forwarding)} /> )} {(!form.protocol || ['ssh', 'telnet', 'mosh', 'et'].includes(form.protocol)) && ( } title={t("hostDetails.section.deviceType")} > {(!form.protocol || form.protocol === 'ssh') && !form.moshEnabled && !form.etEnabled && (<> update("deviceType", effectiveDeviceType === 'network' ? 'general' : 'network')} /> {effectiveDeviceType === 'network' && (

{t("hostDetails.deviceType.warning")}

)} )}

{t("hostDetails.os.current")}: {t(`hostDetails.os.${resolveHostOs({ ...form, deviceType: effectiveDeviceType })}`)}

)} {/* SSH Algorithms */} } title={t("hostDetails.section.sshAlgorithms")} > {/* Display the *effective* value of these toggles (host field falling back to the resolved group default). Without the fallback a host that inherits the flag from its group would show "off" while the runtime applied it anyway, and the toggle's onToggle handler would compute the wrong "next" value from the raw host field. */} update( "legacyAlgorithms", !(form.legacyAlgorithms ?? effectiveGroupDefaults?.legacyAlgorithms), )} /> {(form.legacyAlgorithms ?? effectiveGroupDefaults?.legacyAlgorithms) && (

{t("hostDetails.legacyAlgorithms.warning")}

)} update( "skipEcdsaHostKey", !(form.skipEcdsaHostKey ?? effectiveGroupDefaults?.skipEcdsaHostKey), )} /> update("algorithms", next)} />
{/* Terminal Behavior — input/output key mappings (backspace, etc.) */} } title={t("hostDetails.section.terminalBehavior")} > update("showLineTimestamps", !form.showLineTimestamps)} /> {/* Per-host keepalive override */} } title={t("hostDetails.section.keepalive")} > { const next = !form.keepaliveOverride; update("keepaliveOverride", next); // Seed sensible per-host defaults the first time the user // turns the override on so the inputs aren't empty. if (next) { if (form.keepaliveInterval == null) update("keepaliveInterval", 0); if (form.keepaliveCountMax == null) update("keepaliveCountMax", 3); } }} /> {form.keepaliveOverride && (
{ const v = parseInt(e.target.value, 10); if (!Number.isFinite(v)) return; if (v < 0 || v > 3600) return; update("keepaliveInterval", v); }} /> { const v = parseInt(e.target.value, 10); if (!Number.isFinite(v)) return; if (v < 1 || v > 100) return; update("keepaliveCountMax", v); }} /> {(form.keepaliveInterval ?? 0) === 0 && (

{t("hostDetails.keepalive.disabledHint")}

)}
)}
} title={t("hostDetails.section.sshTimeouts")} > { const value = Number.parseInt(e.target.value, 10); if (!Number.isFinite(value) || value < 1 || value > MAX_SSH_CONNECTION_TIMEOUT_SECONDS) return; update("sshTcpConnectTimeoutSeconds", value); }} /> { const value = Number.parseInt(e.target.value, 10); if (!Number.isFinite(value) || value < 1 || value > MAX_SSH_CONNECTION_TIMEOUT_SECONDS) return; update("sshAuthReadyTimeoutSeconds", value); }} /> {/* Proxy via Hosts (Jump Hosts / ProxyJump) */} } title={t("hostDetails.jumpHosts")} action={ chainedHosts.length > 0 ? ( {t("hostDetails.jumpHosts.hops", { count: chainedHosts.length })} ) : ( {t("hostDetails.jumpHosts.direct")} ) } > {chainedHosts.length > 0 && ( )} {chainedHosts.length === 0 && ( )} {/* Proxy Configuration */} } title={t("hostDetails.proxy")} className="overflow-hidden" > {form.proxyConfig?.host || form.proxyConfig?.command || form.proxyProfileId ? (
) : ( )}
{/* Environment Variables */} } title={t("hostDetails.envVars")} > {(form.environmentVariables?.length || 0) > 0 ? ( ) : ( )} {/* Startup Command */} } title={t("hostDetails.startupCommand")} hint={t("hostDetails.startupCommand.help")} >