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;