From 489fdafa1c3e9ec1b3652d984c1d61dfe6cdb962 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 2 Oct 2025 20:26:34 +0200 Subject: [PATCH] feat(terminal): styling improvements and shortcut --- .../components/ClusterMembersComponent.js | 18 ++-- public/scripts/components/DrawerComponent.js | 3 + .../components/TerminalPanelComponent.js | 89 ++++++++++++++++++- .../components/TopologyGraphComponent.js | 9 ++ 4 files changed, 108 insertions(+), 11 deletions(-) diff --git a/public/scripts/components/ClusterMembersComponent.js b/public/scripts/components/ClusterMembersComponent.js index ba27dfe..48a3dcd 100644 --- a/public/scripts/components/ClusterMembersComponent.js +++ b/public/scripts/components/ClusterMembersComponent.js @@ -517,11 +517,11 @@ class ClusterMembersComponent extends Component { }); } - if (terminalBtn) { + if (terminalBtn) { this.addEventListener(terminalBtn, 'click', async (e) => { - e.stopPropagation(); - e.preventDefault(); - try { + e.stopPropagation(); + e.preventDefault(); + try { if (!window.TerminalPanel) return; this.ensureTerminalContainer(); const panel = window.TerminalPanel; @@ -530,11 +530,11 @@ class ClusterMembersComponent extends Component { if (wasMinimized && panel.restore) { panel.restore(); } - } catch (err) { - console.error('Failed to open member terminal:', err); - } - }); - } + } catch (err) { + console.error('Failed to open member terminal:', err); + } + }); + } }); }, 100); } diff --git a/public/scripts/components/DrawerComponent.js b/public/scripts/components/DrawerComponent.js index 4955389..1bcef9d 100644 --- a/public/scripts/components/DrawerComponent.js +++ b/public/scripts/components/DrawerComponent.js @@ -75,6 +75,9 @@ class DrawerComponent { const panel = window.TerminalPanel; const wasMinimized = panel.isMinimized; panel.open(this.terminalPanelContainer, nodeIp); + if (nodeIp && panel._updateTitle) { + panel._updateTitle(nodeIp); + } if (wasMinimized && panel.restore) { panel.restore(); } diff --git a/public/scripts/components/TerminalPanelComponent.js b/public/scripts/components/TerminalPanelComponent.js index fded6de..e563191 100644 --- a/public/scripts/components/TerminalPanelComponent.js +++ b/public/scripts/components/TerminalPanelComponent.js @@ -14,6 +14,14 @@ this.dockEl = null; this.dockBtnEl = null; this.lastNodeIp = null; + this.fallbackContainer = null; + + try { + this._onKeydown = this._onKeydown.bind(this); + document.addEventListener('keydown', this._onKeydown); + } catch (_) { + // ignore if document not available + } } open(container, nodeIp) { @@ -60,6 +68,8 @@ this._connect(nodeIp); } else if (this.lastNodeIp && !this.socket) { this._connect(this.lastNodeIp); + } else if (!this.socket && !this.lastNodeIp) { + this._updateTitle(null); } } catch (err) { console.error('TerminalPanel.open error:', err); @@ -136,8 +146,7 @@ _connect(nodeIp) { try { - const titleEl = this.panelEl.querySelector('.terminal-title'); - if (titleEl) titleEl.textContent = `Terminal — ${nodeIp}`; + this._updateTitle(nodeIp); // Close previous socket if switching node if (this.socket) { @@ -254,6 +263,75 @@ if (this.dockEl) this.dockEl.classList.remove('visible'); } + _resolveContainer() { + if (this.container && document.body && document.body.contains(this.container)) { + return this.container; + } + + const sharedDrawer = window.__sharedDrawerInstance; + if (sharedDrawer && sharedDrawer.terminalPanelContainer) { + return sharedDrawer.terminalPanelContainer; + } + + if (this.fallbackContainer && document.body && document.body.contains(this.fallbackContainer)) { + return this.fallbackContainer; + } + + if (typeof document !== 'undefined') { + const fallback = document.createElement('div'); + fallback.className = 'terminal-panel-container'; + document.body.appendChild(fallback); + this.fallbackContainer = fallback; + return fallback; + } + + return null; + } + + _onKeydown(event) { + try { + if (!event || event.defaultPrevented) return; + if (event.key !== 't' && event.key !== 'T') return; + if (event.repeat) return; + if (event.metaKey || event.ctrlKey || event.altKey) return; + + const activeEl = document.activeElement; + if (activeEl) { + const tagName = activeEl.tagName; + const isEditable = activeEl.isContentEditable; + if (isEditable || tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') { + return; + } + } + + event.preventDefault(); + this.toggleVisibility(); + } catch (_) { + // swallow errors from key handler to avoid breaking global listeners + } + } + + toggleVisibility() { + try { + if (this.isOpen && !this.isMinimized) { + this.minimize(); + return; + } + + if (this.isOpen && this.isMinimized) { + this.restore(); + return; + } + + const targetContainer = this._resolveContainer(); + if (!targetContainer) return; + const targetIp = this.lastNodeIp || this.connectedIp || null; + this.open(targetContainer, targetIp); + } catch (err) { + console.error('TerminalPanel.toggleVisibility error:', err); + } + } + _send(text) { try { if (!this.socket || this.socket.readyState !== 1) { @@ -267,6 +345,13 @@ } } + _updateTitle(nodeIp) { + if (!this.panelEl) return; + const titleEl = this.panelEl.querySelector('.terminal-title'); + if (!titleEl) return; + titleEl.textContent = nodeIp ? `Terminal — ${nodeIp}` : 'Terminal'; + } + _clear() { if (this.logEl) this.logEl.textContent = ''; } diff --git a/public/scripts/components/TopologyGraphComponent.js b/public/scripts/components/TopologyGraphComponent.js index 48311fc..ea366c0 100644 --- a/public/scripts/components/TopologyGraphComponent.js +++ b/public/scripts/components/TopologyGraphComponent.js @@ -59,6 +59,15 @@ class TopologyGraphComponent extends Component { `; }); }); + + try { + if (window.TerminalPanel && typeof nodeData.ip === 'string') { + window.TerminalPanel.lastNodeIp = nodeData.ip; + if (window.TerminalPanel._updateTitle) { + window.TerminalPanel._updateTitle(nodeData.ip); + } + } + } catch (_) {} } closeDrawer() {