From d87021913699a03c7d6e1ce403b7d63d4d96b949 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Mon, 15 Sep 2025 22:32:59 +0200 Subject: [PATCH] feat: fresh tabs --- .../components/NodeDetailsComponent.js | 50 +++++++++++++++++++ public/styles/main.css | 23 +++++++++ 2 files changed, 73 insertions(+) diff --git a/public/scripts/components/NodeDetailsComponent.js b/public/scripts/components/NodeDetailsComponent.js index b716481..64d9010 100644 --- a/public/scripts/components/NodeDetailsComponent.js +++ b/public/scripts/components/NodeDetailsComponent.js @@ -2,6 +2,7 @@ class NodeDetailsComponent extends Component { constructor(container, viewModel, eventBus) { super(container, viewModel, eventBus); + this.suppressLoadingUI = false; } setupViewModelListeners() { @@ -31,6 +32,7 @@ class NodeDetailsComponent extends Component { // Handle loading state update handleLoadingUpdate(isLoading) { if (isLoading) { + if (this.suppressLoadingUI) return; this.renderLoading('
Loading detailed information...
'); } } @@ -103,6 +105,13 @@ class NodeDetailsComponent extends Component { +
@@ -145,6 +154,7 @@ class NodeDetailsComponent extends Component { this.setHTML('', html); this.setupTabs(); + this.setupTabRefreshButton(); // Restore last active tab from view model if available const restored = this.viewModel && typeof this.viewModel.get === 'function' ? this.viewModel.get('activeTab') : null; if (restored) { @@ -153,6 +163,46 @@ class NodeDetailsComponent extends Component { this.setupFirmwareUpload(); } + setupTabRefreshButton() { + const btn = this.findElement('.tab-refresh-btn'); + if (!btn) return; + this.addEventListener(btn, 'click', async (e) => { + e.stopPropagation(); + const original = btn.innerHTML; + btn.disabled = true; + btn.innerHTML = ` + + + + + + `; + + try { + const activeTab = (this.viewModel && typeof this.viewModel.get === 'function') ? (this.viewModel.get('activeTab') || 'status') : 'status'; + const nodeIp = (this.viewModel && typeof this.viewModel.get === 'function') ? this.viewModel.get('nodeIp') : null; + this.suppressLoadingUI = true; + + if (activeTab === 'endpoints' && typeof this.viewModel.loadEndpointsData === 'function') { + await this.viewModel.loadEndpointsData(); + } else if (activeTab === 'tasks' && typeof this.viewModel.loadTasksData === 'function') { + await this.viewModel.loadTasksData(); + } else { + // status or firmware: refresh core node details + if (nodeIp && typeof this.viewModel.loadNodeDetails === 'function') { + await this.viewModel.loadNodeDetails(nodeIp); + } + } + } catch (err) { + logger.error('Tab refresh failed:', err); + } finally { + this.suppressLoadingUI = false; + btn.disabled = false; + btn.innerHTML = original; + } + }); + } + renderEndpointsTab(endpoints) { if (!endpoints || !Array.isArray(endpoints.endpoints) || endpoints.endpoints.length === 0) { return ` diff --git a/public/styles/main.css b/public/styles/main.css index 7398230..fbf0b42 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -2184,6 +2184,29 @@ p { border-bottom: none; } +/* Refresh button aligned to the right of tabs, blends with tab header */ +.tabs-header .tab-refresh-btn { + margin-left: auto; + background: transparent; + border: 1px solid transparent; + color: var(--text-secondary); + padding: 0.4rem; + border-radius: 8px; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} +.tabs-header .tab-refresh-btn:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.12); + color: var(--text-primary); +} +.tabs-header .tab-refresh-btn:disabled { + opacity: 0.6; + cursor: default; +} + .tab-button { background: transparent; border: 1px solid transparent;