Skip to content

Commit 328e67b

Browse files
rcourtmanclaude
andcommitted
feat: add clickable host names in dashboard and PBS views
- Add endpoint configuration sharing between server and client - Make Proxmox node group headers clickable using API-based mapping - Make PBS instance names clickable in both desktop and mobile views - Links open correct cluster endpoints based on node's actual endpointId - Subtle hover styling without blue underlines for cleaner UI - All links open in new tabs with security attributes Resolves issue with users wanting direct access to host web interfaces. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 57e90ab commit 328e67b

File tree

6 files changed

+109
-4
lines changed

6 files changed

+109
-4
lines changed

server/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ try {
3535
// Set the placeholder status in stateManager *after* config loading is complete
3636
stateManager.setConfigPlaceholderStatus(configIsPlaceholder);
3737

38+
// Set endpoint configurations for client use
39+
stateManager.setEndpointConfigurations(endpoints, pbsConfigs);
40+
3841
const fs = require('fs'); // Add fs module
3942
const express = require('express');
4043
const http = require('http');

server/state.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const state = {
1212
guestSnapshots: []
1313
},
1414
isConfigPlaceholder: false, // Add this flag
15+
endpoints: [], // Add endpoint configurations for client
16+
pbsConfigs: [], // Add PBS configurations for client
1517

1618
// Enhanced monitoring data
1719
performance: {
@@ -88,6 +90,8 @@ function getState() {
8890
pbs: state.pbs, // This is what's sent to the client and should now be correct
8991
pveBackups: state.pveBackups, // Add PVE backup data
9092
isConfigPlaceholder: state.isConfigPlaceholder,
93+
endpoints: state.endpoints, // Add endpoint configurations
94+
pbsConfigs: state.pbsConfigs, // Add PBS configurations
9195

9296
// Enhanced monitoring data
9397
performance: state.performance,
@@ -343,6 +347,24 @@ function setConfigPlaceholderStatus(isPlaceholder) {
343347
state.isConfigPlaceholder = isPlaceholder;
344348
}
345349

350+
function setEndpointConfigurations(endpoints, pbsConfigs) {
351+
// Store endpoint configurations for client use
352+
state.endpoints = endpoints.map(endpoint => ({
353+
id: endpoint.id,
354+
name: endpoint.name,
355+
host: endpoint.host,
356+
port: endpoint.port,
357+
enabled: endpoint.enabled
358+
}));
359+
360+
state.pbsConfigs = pbsConfigs.map(config => ({
361+
id: config.id,
362+
name: config.name,
363+
host: config.host,
364+
port: config.port
365+
}));
366+
}
367+
346368
function getPerformanceHistory(limit = 50) {
347369
return performanceHistory.slice(0, limit);
348370
}
@@ -405,6 +427,7 @@ module.exports = {
405427
init,
406428
getState,
407429
setConfigPlaceholderStatus,
430+
setEndpointConfigurations,
408431
updateDiscoveryData,
409432
updateMetricsData,
410433
clearMetricsData,

src/public/js/state.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ PulseApp.state = (() => {
1010
metricsData: [],
1111
dashboardData: [],
1212
pbsDataArray: [],
13+
endpoints: [], // Add endpoint configurations
14+
pbsConfigs: [], // Add PBS configurations
1315
pveBackups: { // Add PVE backup data
1416
backupTasks: [],
1517
storageBackups: [],
@@ -176,6 +178,14 @@ PulseApp.state = (() => {
176178
}
177179
});
178180

181+
// Update endpoint configurations (these don't need change tracking)
182+
if (newData.endpoints) {
183+
internalState.endpoints = newData.endpoints;
184+
}
185+
if (newData.pbsConfigs) {
186+
internalState.pbsConfigs = newData.pbsConfigs;
187+
}
188+
179189
// Only update enhanced monitoring data if provided
180190
if (newData.alerts) {
181191
internalState.alerts = {

src/public/js/ui/common.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,16 @@ PulseApp.ui.common = (() => {
360360
function generateNodeGroupHeaderCellHTML(text, colspan, cellTag = 'td') {
361361
const baseClasses = 'py-0.5 px-2 text-left font-medium text-xs sm:text-sm text-gray-700 dark:text-gray-300';
362362

363+
// Check if we can make this node name clickable
364+
const hostUrl = PulseApp.utils.getHostUrl(text);
365+
let nodeContent = text;
366+
367+
if (hostUrl) {
368+
nodeContent = `<a href="${hostUrl}" target="_blank" rel="noopener noreferrer" class="text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-150 cursor-pointer" title="Open ${text} web interface">${text}</a>`;
369+
}
370+
363371
// Always create individual cells so first one can be sticky
364-
let html = `<${cellTag} class="sticky left-0 bg-gray-200 dark:bg-gray-700 z-10 ${baseClasses}">${text}</${cellTag}>`;
372+
let html = `<${cellTag} class="sticky left-0 bg-gray-200 dark:bg-gray-700 z-10 ${baseClasses}">${nodeContent}</${cellTag}>`;
365373
// Add empty cells for remaining columns
366374
for (let i = 1; i < colspan; i++) {
367375
html += `<${cellTag} class="bg-gray-200 dark:bg-gray-700"></${cellTag}>`;

src/public/js/ui/pbs.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,22 @@ PulseApp.ui.pbs = (() => {
994994
headerDiv.className = `${CSS_CLASSES.FLEX} ${CSS_CLASSES.JUSTIFY_BETWEEN} ${CSS_CLASSES.ITEMS_CENTER} ${CSS_CLASSES.MB3}`;
995995
const instanceTitleElement = document.createElement('h3');
996996
instanceTitleElement.className = `${CSS_CLASSES.TEXT_LG} ${CSS_CLASSES.FONT_SEMIBOLD} ${CSS_CLASSES.TEXT_GRAY_800_DARK_GRAY_200} ${CSS_CLASSES.FLEX} ${CSS_CLASSES.ITEMS_CENTER}`;
997-
instanceTitleElement.appendChild(document.createTextNode(instanceName));
997+
998+
// Check if we can make this PBS instance name clickable
999+
const hostUrl = PulseApp.utils.getHostUrl(instanceName);
1000+
if (hostUrl) {
1001+
const linkElement = document.createElement('a');
1002+
linkElement.href = hostUrl;
1003+
linkElement.target = '_blank';
1004+
linkElement.rel = 'noopener noreferrer';
1005+
linkElement.className = 'text-gray-800 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-150 cursor-pointer';
1006+
linkElement.title = `Open ${instanceName} web interface`;
1007+
linkElement.appendChild(document.createTextNode(instanceName));
1008+
instanceTitleElement.appendChild(linkElement);
1009+
} else {
1010+
instanceTitleElement.appendChild(document.createTextNode(instanceName));
1011+
}
1012+
9981013
headerDiv.appendChild(instanceTitleElement);
9991014
return headerDiv;
10001015
};
@@ -1471,10 +1486,18 @@ PulseApp.ui.pbs = (() => {
14711486
statusClass = 'text-yellow-600 dark:text-yellow-400';
14721487
}
14731488

1489+
// Check if we can make this PBS instance name clickable
1490+
const hostUrl = PulseApp.utils.getHostUrl(instanceName);
1491+
let instanceNameHtml = `<span class="font-medium text-sm">${instanceName}</span>`;
1492+
1493+
if (hostUrl) {
1494+
instanceNameHtml = `<a href="${hostUrl}" target="_blank" rel="noopener noreferrer" class="font-medium text-sm text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-150 cursor-pointer" title="Open ${instanceName} web interface">${instanceName}</a>`;
1495+
}
1496+
14741497
headerContent.innerHTML = `
14751498
<div class="flex items-center gap-2 mb-1">
14761499
<span class="${statusClass} text-sm">${statusIcon}</span>
1477-
<span class="font-medium text-sm">${instanceName}</span>
1500+
${instanceNameHtml}
14781501
</div>
14791502
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">${statusInfo.statusText}</div>
14801503
`;

src/public/js/utils.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,43 @@ PulseApp.utils = (() => {
322322
return null;
323323
}
324324

325+
// Get URL for a host based on endpoint configuration and API data
326+
function getHostUrl(nodeName) {
327+
const endpoints = PulseApp.state.get('endpoints') || [];
328+
const pbsConfigs = PulseApp.state.get('pbsConfigs') || [];
329+
330+
// First check PBS configs for exact name match
331+
for (const config of pbsConfigs) {
332+
if (config.name === nodeName) {
333+
return config.host;
334+
}
335+
}
336+
337+
// For Proxmox nodes, we need to find which endpoint this node belongs to
338+
// by looking at the nodes data from the API
339+
const nodesData = PulseApp.state.get('nodesData') || [];
340+
341+
// Find the node in the API data to get its endpointId
342+
const nodeInfo = nodesData.find(node => node.node === nodeName);
343+
344+
if (nodeInfo && nodeInfo.endpointId) {
345+
// Find the endpoint that matches this endpointId
346+
const endpoint = endpoints.find(ep => ep.id === nodeInfo.endpointId);
347+
if (endpoint) {
348+
return endpoint.host;
349+
}
350+
}
351+
352+
// Fallback: try direct name matches with endpoints
353+
for (const endpoint of endpoints) {
354+
if (endpoint.name === nodeName) {
355+
return endpoint.host;
356+
}
357+
}
358+
359+
return null;
360+
}
361+
325362
// Return the public API for this module
326363
return {
327364
sanitizeForId: (str) => str.replace(/[^a-zA-Z0-9-]/g, '-'),
@@ -342,6 +379,7 @@ PulseApp.utils = (() => {
342379
updateProgressBarTexts,
343380
updateProgressBarTextsDebounced,
344381
preserveScrollPosition,
345-
getScrollableParent
382+
getScrollableParent,
383+
getHostUrl
346384
};
347385
})();

0 commit comments

Comments
 (0)