diff --git a/src/main.tsx b/src/main.tsx index c9cb6666..25ee7368 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -143,6 +143,9 @@ function App() { setAuthUsername(u); setPhase("fading-in"); timerRef.current = setTimeout(() => setPhase("idle-app"), 450); + if (isElectron()) { + window.electronAPI?.startC2SAutoStartTunnels?.().catch(() => {}); + } } function handleLogout() { diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index 3b1ddc84..9bc83199 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -288,48 +288,42 @@ export function AppShell({ }; window.addEventListener("termix:open-tab", handle); return () => window.removeEventListener("termix:open-tab", handle); - }, [tabs, allHosts]); + }, [allHosts]); // ─── Tab management ────────────────────────────────────────────────────── function openTab(host: Host, type: TabType) { + const same = tabs.filter( + (t) => t.type === type && t.label.replace(/ \(\d+\)$/, "") === host.name, + ); + if (same.length === 0) { + const tabId = `${host.name}-${type}`; + const ref = type === "terminal" ? createRef() : undefined; + if (ref) terminalRefs.current.set(tabId, ref); + const tab = { id: tabId, type, label: host.name, host, terminalRef: ref }; + setTabs((prev) => [...prev, tab]); + setActiveTabId(tab.id); + return; + } + const tabId = `${host.name}-${type}-${Date.now()}`; + const ref = type === "terminal" ? createRef() : undefined; + if (ref) terminalRefs.current.set(tabId, ref); + const tab = { + id: tabId, + type, + label: `${host.name} (${same.length + 1})`, + host, + terminalRef: ref, + }; setTabs((prev) => { - const same = prev.filter( - (t) => - t.type === type && t.label.replace(/ \(\d+\)$/, "") === host.name, - ); - if (same.length === 0) { - const tabId = `${host.name}-${type}`; - const ref = type === "terminal" ? createRef() : undefined; - if (ref) terminalRefs.current.set(tabId, ref); - const tab = { - id: tabId, - type, - label: host.name, - host, - terminalRef: ref, - }; - setActiveTabId(tab.id); - return [...prev, tab]; - } const next = prev.map((t) => t.id === same[0].id && !/\(\d+\)$/.test(t.label) ? { ...t, label: `${host.name} (1)`, host } : t, ); - const tabId = `${host.name}-${type}-${Date.now()}`; - const ref = type === "terminal" ? createRef() : undefined; - if (ref) terminalRefs.current.set(tabId, ref); - const tab = { - id: tabId, - type, - label: `${host.name} (${same.length + 1})`, - host, - terminalRef: ref, - }; - setActiveTabId(tab.id); return [...next, tab]; }); + setActiveTabId(tab.id); } function connectHost(host: Host, preferredType?: TabType) { @@ -389,16 +383,26 @@ export function AppShell({ function doCloseTab(id: string) { terminalRefs.current.delete(id); + if (id === activeTabId) { + const remaining = tabs.filter((t) => t.id !== id); + setActiveTabId( + remaining.length > 0 ? remaining[remaining.length - 1].id : "dashboard", + ); + } setTabs((prev) => { const next = prev.filter((t) => t.id !== id); - if (id === activeTabId) setActiveTabId(next[next.length - 1].id); + if (next.length === 0) + return [ + { id: "dashboard", type: "dashboard", label: t("nav.dashboard") }, + ]; return next; }); } function closeTab(id: string) { const tab = tabs.find((t) => t.id === id); - if (tab && SESSION_TAB_TYPES.includes(tab.type)) { + const confirmEnabled = localStorage.getItem("confirmTabClose") === "true"; + if (tab && SESSION_TAB_TYPES.includes(tab.type) && confirmEnabled) { toast(t("nav.confirmClose"), { duration: 5000, action: { @@ -670,31 +674,37 @@ export function AppShell({ ) : ( <> {/* Terminal tabs: always in DOM, absolutely positioned so xterm always has real dimensions */} - {tabs - .filter((tab) => tab.type === "terminal") - .map((tab) => { - const visible = tab.id === activeTabId; - return ( -