import React from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { NumberInput } from '@/components/ui/number-input'; import { Switch } from '@/components/ui/switch'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLayout'; import { useDesktopSshStore } from '@/stores/useDesktopSshStore'; import { useUIStore } from '@/stores/useUIStore'; import { toast } from '@/components/ui'; import { Icon } from "@/components/icon/Icon"; import { copyTextToClipboard } from '@/lib/clipboard'; import { openExternalUrl } from '@/lib/url'; import { useI18n, type I18nKey } from '@/lib/i18n'; import { desktopSshLogsClear, desktopSshLogs, type DesktopSshInstance, type DesktopSshPortForward, type DesktopSshPortForwardType, } from '@/lib/desktopSsh'; const randomPort = (): number => { return Math.floor(20000 + Math.random() * 30000); }; const isPortInUseError = (error: unknown): boolean => { const message = (error instanceof Error ? error.message : String(error)).toLowerCase(); return message.includes('address already in use') || message.includes('eaddrinuse') || message.includes('port already in use'); }; const phaseLabelKey = (phase?: string): I18nKey => { switch (phase) { case 'config_resolved': return 'settings.remoteInstances.page.phase.resolvingConfiguration'; case 'auth_check': return 'settings.remoteInstances.page.phase.checkingAuth'; case 'master_connecting': return 'settings.remoteInstances.page.phase.establishingSsh'; case 'remote_probe': return 'settings.remoteInstances.page.phase.probingRemote'; case 'installing': return 'settings.remoteInstances.page.phase.installingOpenChamber'; case 'updating': return 'settings.remoteInstances.page.phase.updatingOpenChamber'; case 'server_detecting': return 'settings.remoteInstances.page.phase.detectingServer'; case 'server_starting': return 'settings.remoteInstances.page.phase.startingServer'; case 'forwarding': return 'settings.remoteInstances.page.phase.forwardingPorts'; case 'ready': return 'settings.remoteInstances.sidebar.phase.ready'; case 'degraded': return 'settings.remoteInstances.page.phase.reconnecting'; case 'error': return 'settings.remoteInstances.sidebar.phase.error'; default: return 'settings.remoteInstances.sidebar.phase.idle'; } }; const CONNECTING_PHASES = new Set([ 'config_resolved', 'auth_check', 'master_connecting', 'remote_probe', 'installing', 'updating', 'server_detecting', 'server_starting', 'forwarding', ]); const isConnectingPhase = (phase?: string): boolean => { return Boolean(phase && CONNECTING_PHASES.has(phase)); }; const phaseDotClass = (phase?: string): string => { if (phase === 'ready') { return 'bg-[var(--status-success)] animate-pulse'; } if (phase === 'error') { return 'bg-[var(--status-error)] animate-pulse'; } if (phase === 'degraded' || isConnectingPhase(phase)) { return 'bg-[var(--status-warning)] animate-pulse'; } return 'bg-muted-foreground/40'; }; const buildForwardLabel = (forward: DesktopSshPortForward): string => { if (forward.type === 'dynamic') { return `${forward.localHost || '127.0.0.1'}:${forward.localPort || 0}`; } if (forward.type === 'remote') { return `${forward.remoteHost || '127.0.0.1'}:${forward.remotePort || 0} -> ${forward.localHost || '127.0.0.1'}:${forward.localPort || 0}`; } return `${forward.localHost || '127.0.0.1'}:${forward.localPort || 0} -> ${forward.remoteHost || '127.0.0.1'}:${forward.remotePort || 0}`; }; const makeForward = (): DesktopSshPortForward => { return { id: `forward-${Date.now()}-${Math.random().toString(16).slice(2)}`, enabled: true, type: 'local', localHost: '127.0.0.1', localPort: randomPort(), remoteHost: '127.0.0.1', remotePort: 80, }; }; const suggestConcreteHost = (pattern: string): string => { const value = pattern.trim().replace(/\*/g, 'host').replace(/\?/g, 'x'); return value || 'user@host'; }; const HintLabel: React.FC<{ label: string; hint: React.ReactNode }> = ({ label, hint }) => { return ( {label}
{hint}
); }; const forwardTypeDescriptionKey = (type: DesktopSshPortForwardType): I18nKey => { switch (type) { case 'remote': return 'settings.remoteInstances.page.forwardTypeDescription.remote'; case 'dynamic': return 'settings.remoteInstances.page.forwardTypeDescription.dynamic'; default: return 'settings.remoteInstances.page.forwardTypeDescription.local'; } }; const formatEndpoint = (host: string | undefined, port: number | undefined): string => { const value = (host || '').trim(); const normalizedHost = !value || value === '127.0.0.1' || value === '::1' ? 'localhost' : value; return `${normalizedHost}:${port || 0}`; }; const toBrowserHost = (host: string | undefined): string => { const value = (host || '').trim(); if (!value || value === '0.0.0.0' || value === '::') { return '127.0.0.1'; } return value; }; const formatLogLine = (line: string): string => { const match = line.match(/^\[(\d{10,})\]\s*(?:\[([A-Z]+)\]\s*)?(.*)$/); if (!match) { return line; } const millis = Number(match[1]); const iso = Number.isFinite(millis) ? new Date(millis).toISOString() : match[1]; const level = (match[2] || 'INFO').toUpperCase(); const message = match[3] || ''; return `[${iso}] [${level}] ${message}`; }; const navigateToUrl = (rawUrl: string): void => { const target = rawUrl.trim(); if (!target) { return; } try { window.location.assign(target); } catch { window.location.href = target; } }; const normalizeForSave = (instance: DesktopSshInstance): DesktopSshInstance => { const trimmedCommand = instance.sshCommand.trim(); const nickname = instance.nickname?.trim(); const forwards = instance.portForwards.map((forward) => ({ ...forward, localHost: forward.localHost?.trim() || '127.0.0.1', localPort: typeof forward.localPort === 'number' ? Math.max(1, Math.min(65535, Math.round(forward.localPort))) : undefined, remoteHost: forward.remoteHost?.trim(), remotePort: typeof forward.remotePort === 'number' ? Math.max(1, Math.min(65535, Math.round(forward.remotePort))) : undefined, })); return { ...instance, sshCommand: trimmedCommand, ...(nickname ? { nickname } : { nickname: undefined }), connectionTimeoutSec: Math.max(5, Math.min(240, Math.round(instance.connectionTimeoutSec || 60))), localForward: { ...instance.localForward, bindHost: instance.localForward.bindHost === 'localhost' || instance.localForward.bindHost === '0.0.0.0' ? instance.localForward.bindHost : '127.0.0.1', preferredLocalPort: typeof instance.localForward.preferredLocalPort === 'number' ? Math.max(1, Math.min(65535, Math.round(instance.localForward.preferredLocalPort))) : undefined, }, remoteOpenchamber: { ...instance.remoteOpenchamber, preferredPort: typeof instance.remoteOpenchamber.preferredPort === 'number' ? Math.max(1, Math.min(65535, Math.round(instance.remoteOpenchamber.preferredPort))) : undefined, }, portForwards: forwards, }; }; export const RemoteInstancesPage: React.FC = () => { const { t } = useI18n(); const instances = useDesktopSshStore((state) => state.instances); const statusesById = useDesktopSshStore((state) => state.statusesById); const importCandidates = useDesktopSshStore((state) => state.importCandidates); const isImportsLoading = useDesktopSshStore((state) => state.isImportsLoading); const isSaving = useDesktopSshStore((state) => state.isSaving); const error = useDesktopSshStore((state) => state.error); const load = useDesktopSshStore((state) => state.load); const loadImports = useDesktopSshStore((state) => state.loadImports); const refreshStatuses = useDesktopSshStore((state) => state.refreshStatuses); const upsertInstance = useDesktopSshStore((state) => state.upsertInstance); const createFromCommand = useDesktopSshStore((state) => state.createFromCommand); const removeInstance = useDesktopSshStore((state) => state.removeInstance); const connect = useDesktopSshStore((state) => state.connect); const disconnect = useDesktopSshStore((state) => state.disconnect); const retry = useDesktopSshStore((state) => state.retry); const selectedId = useUIStore((state) => state.settingsRemoteInstancesSelectedId); const setSelectedId = useUIStore((state) => state.setSettingsRemoteInstancesSelectedId); const selectedInstance = React.useMemo(() => { if (!selectedId) return null; return instances.find((instance) => instance.id === selectedId) || null; }, [instances, selectedId]); const [draft, setDraft] = React.useState(null); const [logDialogOpen, setLogDialogOpen] = React.useState(false); const [logDialogLoading, setLogDialogLoading] = React.useState(false); const [logDialogError, setLogDialogError] = React.useState(null); const [logDialogLines, setLogDialogLines] = React.useState([]); const [patternHost, setPatternHost] = React.useState(null); const [patternDestination, setPatternDestination] = React.useState(''); const [patternCreating, setPatternCreating] = React.useState(false); const [expandedForwards, setExpandedForwards] = React.useState>({}); const [isPrimaryActionPending, setIsPrimaryActionPending] = React.useState(false); const [isRetryPending, setIsRetryPending] = React.useState(false); const [clockMs, setClockMs] = React.useState(() => Date.now()); React.useEffect(() => { void load(); void loadImports(); }, [load, loadImports]); React.useEffect(() => { setDraft(selectedInstance); }, [selectedInstance]); React.useEffect(() => { if (!selectedId) { return; } const interval = window.setInterval(() => { // Skip polling when tab is hidden to reduce background work if (typeof document !== 'undefined' && document.visibilityState !== 'visible') { return; } void refreshStatuses(); }, 2_000); return () => { window.clearInterval(interval); }; }, [refreshStatuses, selectedId]); React.useEffect(() => { // Use requestAnimationFrame for smoother clock updates without setInterval overhead let rafId: number | null = null; let lastTime = Date.now(); const tick = () => { const now = Date.now(); // Update only once per second if (now - lastTime >= 1_000) { setClockMs(now); lastTime = now; } rafId = requestAnimationFrame(tick); }; // Only run when visible if (typeof document === 'undefined' || document.visibilityState === 'visible') { rafId = requestAnimationFrame(tick); } const onVisibility = () => { if (document.visibilityState === 'visible' && rafId === null) { rafId = requestAnimationFrame(tick); } else if (document.visibilityState !== 'visible' && rafId !== null) { cancelAnimationFrame(rafId); rafId = null; } }; document.addEventListener('visibilitychange', onVisibility); return () => { document.removeEventListener('visibilitychange', onVisibility); if (rafId !== null) { cancelAnimationFrame(rafId); } }; }, []); const status = selectedId ? statusesById[selectedId] : null; const statusPhase = status?.phase; const isReady = statusPhase === 'ready'; const isReconnecting = statusPhase === 'degraded'; const isConnecting = isConnectingPhase(statusPhase); const isBusy = isConnecting || isReconnecting; const canDisconnect = isReady || isBusy; const statusAgeMs = status ? Math.max(0, clockMs - status.updatedAtMs) : 0; const reconnectAppearsStuck = isReconnecting && statusAgeMs > 12_000; const hasChanges = React.useMemo(() => { if (!draft || !selectedInstance) return false; return JSON.stringify(draft) !== JSON.stringify(selectedInstance); }, [draft, selectedInstance]); const updateDraft = React.useCallback((updater: (current: DesktopSshInstance) => DesktopSshInstance) => { setDraft((current) => (current ? updater(current) : current)); }, []); const handleSave = React.useCallback(async () => { if (!draft) return; const normalized = normalizeForSave(draft); if (!normalized.sshCommand.trim()) { toast.error(t('settings.remoteInstances.page.toast.sshCommandRequired')); return; } if (normalized.localForward.bindHost === '0.0.0.0') { const allow = window.confirm( t('settings.remoteInstances.page.confirm.bindAllInterfaces'), ); if (!allow) { return; } } if ( normalized.auth.sshPassword?.enabled && normalized.auth.sshPassword.value?.trim() && normalized.auth.sshPassword.store !== 'settings' ) { const store = window.confirm(t('settings.remoteInstances.page.confirm.storeSshPasswordPlaintext')); normalized.auth.sshPassword.store = store ? 'settings' : 'never'; if (!store) { normalized.auth.sshPassword.value = undefined; } } if ( normalized.auth.openchamberPassword?.enabled && normalized.auth.openchamberPassword.value?.trim() && normalized.auth.openchamberPassword.store !== 'settings' ) { const store = window.confirm(t('settings.remoteInstances.page.confirm.storeUiPasswordPlaintext')); normalized.auth.openchamberPassword.store = store ? 'settings' : 'never'; if (!store) { normalized.auth.openchamberPassword.value = undefined; } } try { await upsertInstance(normalized); toast.success(t('settings.remoteInstances.page.toast.instanceSaved')); } catch (error) { toast.error(t('settings.remoteInstances.page.toast.saveFailed'), { description: error instanceof Error ? error.message : String(error), }); } }, [draft, t, upsertInstance]); const createImportedInstance = React.useCallback( async (host: string, destination: string): Promise => { const id = `ssh-${Date.now()}-${Math.random().toString(16).slice(2)}`; try { await createFromCommand(id, `ssh ${destination}`, host); setSelectedId(id); toast.success(t('settings.remoteInstances.page.toast.instanceCreated')); return true; } catch (error) { toast.error(t('settings.remoteInstances.sidebar.toast.createFailed'), { description: error instanceof Error ? error.message : String(error), }); return false; } }, [createFromCommand, setSelectedId, t], ); const closePatternDialog = React.useCallback(() => { if (patternCreating) { return; } setPatternHost(null); setPatternDestination(''); }, [patternCreating]); const handleImportCandidate = React.useCallback( (host: string, pattern: boolean) => { if (pattern) { setPatternHost(host); setPatternDestination(suggestConcreteHost(host)); return; } void createImportedInstance(host, host); }, [createImportedInstance], ); const handlePatternCreate = React.useCallback(async () => { const host = patternHost; const destination = patternDestination.trim(); if (!host) { return; } if (!destination) { toast.error(t('settings.remoteInstances.page.toast.destinationRequired')); return; } setPatternCreating(true); try { const created = await createImportedInstance(host, destination); if (created) { setPatternHost(null); setPatternDestination(''); } } finally { setPatternCreating(false); } }, [createImportedInstance, patternDestination, patternHost, t]); const connectWithPortRecovery = React.useCallback(async () => { if (!selectedInstance) return; try { await connect(selectedInstance.id); return; } catch (error) { if (!isPortInUseError(error)) { throw error; } const allow = window.confirm(t('settings.remoteInstances.sidebar.confirm.localPortInUseRetry')); if (!allow) { throw error; } const nextInstance: DesktopSshInstance = { ...selectedInstance, localForward: { ...selectedInstance.localForward, preferredLocalPort: randomPort(), }, }; await upsertInstance(nextInstance); await connect(nextInstance.id); toast.success(t('settings.remoteInstances.sidebar.toast.retriedWithRandomPort')); } }, [connect, selectedInstance, t, upsertInstance]); const readLogsForInstance = React.useCallback(async (id: string) => { const lines = await desktopSshLogs(id, 600); return lines.map((line) => formatLogLine(line)); }, []); const handleOpenLogs = React.useCallback(async () => { if (!draft) return; setLogDialogOpen(true); setLogDialogLoading(true); setLogDialogError(null); try { const lines = await readLogsForInstance(draft.id); setLogDialogLines(lines); } catch (error) { setLogDialogLines([]); setLogDialogError(error instanceof Error ? error.message : String(error)); } finally { setLogDialogLoading(false); } }, [draft, readLogsForInstance]); React.useEffect(() => { if (!logDialogOpen || !draft) { return; } let disposed = false; const run = async () => { try { const lines = await readLogsForInstance(draft.id); if (!disposed) { setLogDialogLines(lines); setLogDialogError(null); } } catch (error) { if (!disposed) { setLogDialogError(error instanceof Error ? error.message : String(error)); } } }; void run(); const interval = window.setInterval(() => { // Skip polling when tab is hidden if (typeof document !== 'undefined' && document.visibilityState !== 'visible') { return; } void run(); }, 1_000); return () => { disposed = true; window.clearInterval(interval); }; }, [draft, logDialogOpen, readLogsForInstance]); const logLinesText = React.useMemo(() => logDialogLines.join('\n'), [logDialogLines]); const handleCopyAllLogs = React.useCallback(() => { if (!logLinesText.trim()) { toast.error(t('settings.remoteInstances.page.toast.noLogsToCopy')); return; } void copyTextToClipboard(logLinesText).then((result) => { if (result.ok) { toast.success(t('settings.remoteInstances.page.toast.logsCopied')); } }); }, [logLinesText, t]); const handleClearLogs = React.useCallback(async () => { if (!draft) { return; } try { await desktopSshLogsClear(draft.id); setLogDialogLines([]); toast.success(t('settings.remoteInstances.page.toast.logsCleared')); } catch (error) { toast.error(t('settings.remoteInstances.page.toast.clearLogsFailed'), { description: error instanceof Error ? error.message : String(error), }); } }, [draft, t]); const handleOpenCurrentInstance = React.useCallback(async () => { if (!status?.localUrl) { toast.error(t('settings.remoteInstances.page.toast.instanceUrlUnavailable')); return; } const target = status.localUrl.trim(); if (!target) { toast.error(t('settings.remoteInstances.page.toast.instanceUrlUnavailable')); return; } navigateToUrl(target); }, [status?.localUrl, t]); const handlePrimaryConnectionAction = React.useCallback(() => { if (!draft) { return; } setIsPrimaryActionPending(true); const operation = canDisconnect ? disconnect(draft.id) : connectWithPortRecovery(); void operation .catch((error) => { const key = canDisconnect ? (isReady ? 'settings.remoteInstances.page.toast.disconnectFailed' : 'settings.remoteInstances.page.toast.cancelConnectionFailed') : 'settings.remoteInstances.page.toast.connectFailed'; toast.error(t(key), { description: error instanceof Error ? error.message : String(error), }); }) .finally(() => { setIsPrimaryActionPending(false); }); }, [canDisconnect, connectWithPortRecovery, disconnect, draft, isReady, t]); const handleRetryAction = React.useCallback(() => { if (!draft) { return; } if (isConnecting) { return; } setIsRetryPending(true); const operation = isReconnecting ? disconnect(draft.id).then(() => connectWithPortRecovery()) : retry(draft.id); void operation .catch((error) => { toast.error(t('settings.remoteInstances.page.toast.retryFailed'), { description: error instanceof Error ? error.message : String(error), }); }) .finally(() => { setIsRetryPending(false); }); }, [connectWithPortRecovery, disconnect, draft, isConnecting, isReconnecting, retry, t]); const retryButtonLabel = isConnecting ? t('settings.remoteInstances.page.actions.connecting') : isReconnecting ? reconnectAppearsStuck ? t('settings.remoteInstances.page.actions.reconnectNow') : t('settings.remoteInstances.page.actions.reconnecting') : t('settings.remoteInstances.sidebar.actions.retry'); const canRetry = !isPrimaryActionPending && !isRetryPending && (statusPhase === 'error' || statusPhase === 'idle' || !statusPhase || (isReconnecting && reconnectAppearsStuck)) && !isConnecting; const primaryButtonLabel = isReady ? t('settings.remoteInstances.sidebar.actions.disconnect') : canDisconnect ? t('settings.remoteInstances.page.actions.cancel') : t('settings.remoteInstances.sidebar.actions.connect'); if (!draft) { return (

{t('settings.remoteInstances.page.title')}

{t('settings.remoteInstances.page.description')}

{t('settings.remoteInstances.page.empty.selectInstance')}

{t('settings.remoteInstances.page.import.sectionTitle')}

{isImportsLoading ? (

{t('settings.remoteInstances.page.import.loading')}

) : importCandidates.length === 0 ? (

{t('settings.remoteInstances.page.import.noneFound')}

) : (
{importCandidates.map((candidate) => (
{candidate.host} {candidate.pattern ? ` ${t('settings.remoteInstances.page.import.patternSuffix')}` : ''}
{candidate.source} config
))}
)}
{ if (!open) { closePatternDialog(); } }} > {t('settings.remoteInstances.page.patternDialog.title')} {patternHost ? t('settings.remoteInstances.page.patternDialog.descriptionWithHost', { host: patternHost }) : t('settings.remoteInstances.page.patternDialog.description')}
{ event.preventDefault(); handlePatternCreate(); }} > setPatternDestination(event.target.value)} placeholder={t('settings.remoteInstances.page.patternDialog.destinationPlaceholder')} autoFocus />
); } const isManagedMode = draft.remoteOpenchamber.mode === 'managed'; const instanceTitle = draft.nickname?.trim() || draft.sshParsed?.destination || draft.id; return (

{instanceTitle}

{t(phaseLabelKey(statusPhase))} {status?.localUrl ? {status.localUrl} : null} {reconnectAppearsStuck ? {t('settings.remoteInstances.page.status.reconnectStale')} : null}

{t('settings.remoteInstances.page.section.actions')}

{t('settings.remoteInstances.page.section.actionsDescription')}

{status?.localUrl ? (
{t('settings.remoteInstances.page.status.currentLocalUrl')} {status.localUrl}
) : null}

{t('settings.remoteInstances.page.section.instance')}

{t('settings.remoteInstances.page.section.instanceDescription')}

{t('settings.remoteInstances.page.field.sshCommand')} updateDraft((current) => ({ ...current, sshCommand: event.target.value, })) } placeholder={t('settings.remoteInstances.page.field.sshCommandPlaceholder')} />
{t('settings.remoteInstances.page.field.nickname')} updateDraft((current) => ({ ...current, nickname: event.target.value, })) } placeholder={t('settings.remoteInstances.page.field.nicknamePlaceholder')} />
{t('settings.remoteInstances.page.field.connectionTimeoutSeconds')} { updateDraft((current) => ({ ...current, connectionTimeoutSec: Number.isFinite(next) ? next : current.connectionTimeoutSec, })); }} />

{t('settings.remoteInstances.page.section.remoteServer')}

{t('settings.remoteInstances.page.section.remoteServerDescription')}

{ updateDraft((current) => ({ ...current, remoteOpenchamber: { ...current.remoteOpenchamber, preferredPort: Number.isFinite(next) && next > 0 ? next : undefined, }, })); }} onClear={() => { updateDraft((current) => ({ ...current, remoteOpenchamber: { ...current.remoteOpenchamber, preferredPort: undefined, }, })); }} emptyLabel={t('settings.remoteInstances.page.field.auto')} />
{isManagedMode ? (
) : null} {isManagedMode ? (
updateDraft((current) => ({ ...current, remoteOpenchamber: { ...current.remoteOpenchamber, keepRunning: checked, }, })) } />
) : null}

{t('settings.remoteInstances.page.section.mainTunnel')}

{t('settings.remoteInstances.page.section.mainTunnelDescription')}

{ updateDraft((current) => ({ ...current, localForward: { ...current.localForward, preferredLocalPort: Number.isFinite(next) && next > 0 ? next : undefined, }, })); }} onClear={() => { updateDraft((current) => ({ ...current, localForward: { ...current.localForward, preferredLocalPort: undefined, }, })); }} emptyLabel={t('settings.remoteInstances.page.field.auto')} />

{t('settings.remoteInstances.page.section.authentication')}

{t('settings.remoteInstances.page.section.authenticationDescription')}

{t('settings.remoteInstances.page.field.sshPasswordOptional')} updateDraft((current) => ({ ...current, auth: { ...current.auth, sshPassword: { enabled: event.target.value.trim().length > 0, value: event.target.value, store: current.auth.sshPassword?.store || 'never', }, }, })) } placeholder={t('settings.remoteInstances.page.field.sshPasswordPlaceholder')} />
{t('settings.remoteInstances.page.field.uiPasswordOptional')} updateDraft((current) => ({ ...current, auth: { ...current.auth, openchamberPassword: { enabled: event.target.value.trim().length > 0, value: event.target.value, store: current.auth.openchamberPassword?.store || 'never', }, }, })) } placeholder={t('settings.remoteInstances.page.field.uiPasswordPlaceholder')} />

{t('settings.remoteInstances.page.section.portForwards')}

{t('settings.remoteInstances.page.section.portForwardsDescription')}

{draft.portForwards.length === 0 ? (

{t('settings.remoteInstances.page.empty.noExtraForwards')}

) : null} {draft.portForwards.map((forward, index) => { const updateForward = (updater: (forward: DesktopSshPortForward) => DesktopSshPortForward) => { updateDraft((current) => ({ ...current, portForwards: current.portForwards.map((item, itemIndex) => itemIndex === index ? updater(item) : item, ), })); }; const localLabel = forward.type === 'remote' ? 'Local target' : 'Local listen'; const localHint = forward.type === 'remote' ? 'Local host and port on your machine that receives traffic from remote -R listener.' : 'Local host and port where this forward listens on your machine.'; const remoteLabel = forward.type === 'remote' ? 'Remote listen' : 'Remote target'; const remoteHint = forward.type === 'remote' ? 'Remote host and port where SSH creates the -R listener.' : 'Remote host and port that receives traffic from local -L listener.'; const localEndpoint = formatEndpoint(forward.localHost || 'localhost', forward.localPort); const remoteEndpoint = formatEndpoint(forward.remoteHost || 'localhost', forward.remotePort); const canOpenLocalEndpoint = forward.type === 'local' && typeof forward.localPort === 'number' && forward.localPort > 0; const localEndpointUrl = canOpenLocalEndpoint ? `http://${toBrowserHost(forward.localHost)}:${forward.localPort}` : ''; const isForwardOpen = Boolean(expandedForwards[forward.id]); const typeLabel = forward.type === 'local' ? t('settings.remoteInstances.page.forwardType.local') : forward.type === 'remote' ? t('settings.remoteInstances.page.forwardType.remote') : t('settings.remoteInstances.page.forwardType.dynamic'); return ( { setExpandedForwards((current) => ({ ...current, [forward.id]: open, })); }} className={`${index > 0 ? 'border-t border-[var(--surface-subtle)]' : ''} py-2`} >
{buildForwardLabel(forward)} {typeLabel}
updateForward((item) => ({ ...item, enabled: checked }))} aria-label={t('settings.remoteInstances.page.actions.enableForwardAria')} />

{t(forwardTypeDescriptionKey(forward.type))}

updateForward((item) => ({ ...item, localHost: event.target.value, })) } placeholder={t('settings.remoteInstances.page.field.localHostPlaceholder')} /> : { updateForward((item) => ({ ...item, localPort: Number.isFinite(next) && next > 0 ? next : undefined, })); }} onClear={() => { updateForward((item) => ({ ...item, localPort: undefined, })); }} emptyLabel={t('settings.remoteInstances.page.field.auto')} />
{forward.type !== 'dynamic' ? (
updateForward((item) => ({ ...item, remoteHost: event.target.value, })) } placeholder={t('settings.remoteInstances.page.field.remoteHostPlaceholder')} /> : { updateForward((item) => ({ ...item, remotePort: Number.isFinite(next) && next > 0 ? next : undefined, })); }} onClear={() => { updateForward((item) => ({ ...item, remotePort: undefined, })); }} emptyLabel={t('settings.remoteInstances.page.field.auto')} />
) : null}
{forward.type === 'dynamic' ? ( <> {localEndpoint} {t('settings.remoteInstances.page.preview.localSocks5')} ) : forward.type === 'remote' ? ( <> {remoteEndpoint} {t('settings.remoteInstances.page.preview.remote')} {localEndpoint} {t('settings.remoteInstances.page.preview.local')} ) : ( <> {localEndpoint} {t('settings.remoteInstances.page.preview.local')} {remoteEndpoint} {t('settings.remoteInstances.page.preview.remote')} )}
{canOpenLocalEndpoint ? ( ) : null}
); })}

{t('settings.remoteInstances.page.import.sectionTitle')}

{isImportsLoading ? (

{t('settings.remoteInstances.page.import.loading')}

) : importCandidates.length === 0 ? (

{t('settings.remoteInstances.page.import.noneAvailable')}

) : (
{importCandidates.slice(0, 8).map((candidate, index) => (
0 ? 'border-t border-[var(--surface-subtle)]' : ''}`} >
{candidate.host} {candidate.pattern ? ' (pattern)' : ''}
{candidate.sshCommand}
))}
)}
{status?.localUrl ? ( <> ) : null} {error ?
{error}
: null}
{t('settings.remoteInstances.page.logsDialog.title')} {draft?.nickname?.trim() || draft?.sshParsed?.destination || draft?.id || t('settings.remoteInstances.page.logsDialog.selectedInstanceFallback')}
{logDialogLoading ? (
{t('settings.remoteInstances.page.logsDialog.loading')}
) : logDialogError ? (
{logDialogError}
) : (
              {logDialogLines.length > 0 ? logDialogLines.join('\n') : t('settings.remoteInstances.page.logsDialog.empty')}
            
)}
{ if (!open) { closePatternDialog(); } }} > {t('settings.remoteInstances.page.patternDialog.title')} {patternHost ? t('settings.remoteInstances.page.patternDialog.descriptionWithHost', { host: patternHost }) : t('settings.remoteInstances.page.patternDialog.description')}
{ event.preventDefault(); handlePatternCreate(); }} > setPatternDestination(event.target.value)} placeholder={t('settings.remoteInstances.page.patternDialog.destinationPlaceholder')} autoFocus />
); };