fix: improve node name display and configuration handling

- Add support for custom display names via PROXMOX_NODE_NAME configuration
- Handle multi-node clusters properly by prefixing node names with endpoint name
- Fix dashboard and nodes views to show configured display names
- Fix non-sequential endpoint numbering issue in settings (node_2, node_8, node_14)
- Improve endpoint configuration loading to handle any index number

Fixes #100

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
courtmanr@gmail.com
2025-05-31 21:41:01 +01:00
parent bba5e6be09
commit d08ca39312
4 changed files with 85 additions and 10 deletions
+23 -2
View File
@@ -275,9 +275,20 @@ async function fetchDataForPveEndpoint(endpointId, apiClientInstance, config) {
const correspondingNodeInfo = nodes[index];
if (!correspondingNodeInfo || !correspondingNodeInfo.node) return;
// Determine display name based on cluster configuration
let nodeDisplayName = correspondingNodeInfo.node;
if (endpointType === 'standalone' && config.name) {
// For standalone nodes, use the configured name
nodeDisplayName = config.name;
} else if (endpointType === 'cluster' && nodes.length > 1 && config.name) {
// For multi-node clusters, prefix with configured name
nodeDisplayName = `${config.name} - ${correspondingNodeInfo.node}`;
}
const finalNode = {
cpu: null, mem: null, disk: null, maxdisk: null, uptime: 0, loadavg: null, storage: [],
node: correspondingNodeInfo.node,
displayName: nodeDisplayName,
maxcpu: correspondingNodeInfo.maxcpu,
maxmem: correspondingNodeInfo.maxmem,
level: correspondingNodeInfo.level,
@@ -292,8 +303,18 @@ async function fetchDataForPveEndpoint(endpointId, apiClientInstance, config) {
if (result.status === 'fulfilled' && result.value) {
const nodeData = result.value;
// Use endpointId (the actual key) for constructing IDs and tagging
endpointVms.push(...(nodeData.vms || []).map(vm => ({ ...vm, endpointId: endpointId, id: `${endpointId}-${vm.node}-${vm.vmid}` })));
endpointContainers.push(...(nodeData.containers || []).map(ct => ({ ...ct, endpointId: endpointId, id: `${endpointId}-${ct.node}-${ct.vmid}` })));
endpointVms.push(...(nodeData.vms || []).map(vm => ({
...vm,
endpointId: endpointId,
id: `${endpointId}-${vm.node}-${vm.vmid}`,
nodeDisplayName: nodeDisplayName // Use the calculated display name
})));
endpointContainers.push(...(nodeData.containers || []).map(ct => ({
...ct,
endpointId: endpointId,
id: `${endpointId}-${ct.node}-${ct.vmid}`,
nodeDisplayName: nodeDisplayName // Use the calculated display name
})));
if (nodeData.nodeStatus && Object.keys(nodeData.nodeStatus).length > 0) {
const statusData = nodeData.nodeStatus;
+1 -1
View File
@@ -282,7 +282,7 @@ PulseApp.ui.dashboard = (() => {
uniqueId: guestUniqueId,
vmid: guest.vmid,
name: guest.name || `${guest.type === 'qemu' ? GUEST_TYPE_VM : GUEST_TYPE_CT} ${guest.vmid}`,
node: guest.node,
node: guest.nodeDisplayName || guest.node, // Use display name if available
type: guest.type === 'qemu' ? GUEST_TYPE_VM : GUEST_TYPE_CT,
status: guest.status,
cpu: avgCpu,
+3 -3
View File
@@ -64,7 +64,7 @@ PulseApp.ui.nodes = (() => {
<span class="capitalize">${statusText}</span>
</span>
</td>
<td class="p-1 px-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-0 text-gray-900 dark:text-gray-100" title="${node.node || 'N/A'}">${node.node || 'N/A'}</td>
<td class="p-1 px-2 whitespace-nowrap overflow-hidden text-ellipsis max-w-0 text-gray-900 dark:text-gray-100" title="${node.displayName || node.node || 'N/A'}">${node.displayName || node.node || 'N/A'}</td>
<td class="p-1 px-2 min-w-[200px]">${cpuBarHTML}</td>
<td class="p-1 px-2 min-w-[200px]">${memoryBarHTML}</td>
<td class="p-1 px-2 min-w-[200px]">${diskBarHTML}</td>
@@ -99,7 +99,7 @@ PulseApp.ui.nodes = (() => {
card.innerHTML = `
<div class="flex justify-between items-center">
<h3 class="text-sm font-semibold text-gray-800 dark:text-gray-200 truncate" title="${node.node || 'N/A'}">${node.node || 'N/A'}</h3>
<h3 class="text-sm font-semibold text-gray-800 dark:text-gray-200 truncate" title="${node.displayName || node.node || 'N/A'}">${node.displayName || node.node || 'N/A'}</h3>
<div class="flex items-center">
<span class="h-2.5 w-2.5 rounded-full ${statusColor} mr-1.5 flex-shrink-0"></span>
<span class="text-xs capitalize text-gray-600 dark:text-gray-400">${statusText}</span>
@@ -237,7 +237,7 @@ PulseApp.ui.nodes = (() => {
<div class="flex items-center justify-between gap-2">
<div class="flex items-center min-w-0">
<span class="h-2 w-2 rounded-full ${statusDotColor} mr-1.5 flex-shrink-0"></span>
<h3 class="font-semibold text-xs truncate">${node.node || 'Unknown'}</h3>
<h3 class="font-semibold text-xs truncate">${node.displayName || node.node || 'Unknown'}</h3>
</div>
<div class="flex items-center gap-3 text-[10px] text-gray-600 dark:text-gray-400">
<span class="flex items-center gap-1">
+58 -4
View File
@@ -814,7 +814,24 @@ PulseApp.ui.settings = (() => {
// Count only actual endpoint divs (not the empty state)
const existingEndpoints = container.querySelectorAll('.border:not(.border-dashed)');
const index = existingEndpoints.length + 2; // Start from _2 for additional endpoints
// Find the next available index by checking existing endpoints
let index = 2;
const usedIndexes = new Set();
existingEndpoints.forEach(endpoint => {
const header = endpoint.querySelector('h4');
if (header) {
const match = header.textContent.match(/#(\d+)/);
if (match) {
usedIndexes.add(parseInt(match[1]));
}
}
});
// Find the first unused index starting from 2
while (usedIndexes.has(index)) {
index++;
}
const endpointHtml = `
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-4 relative">
<button type="button" onclick="PulseApp.ui.settings.removeEndpoint(this)"
@@ -876,7 +893,24 @@ PulseApp.ui.settings = (() => {
// Count only actual endpoint divs (not the empty state)
const existingEndpoints = container.querySelectorAll('.border:not(.border-dashed)');
const index = existingEndpoints.length + 2; // Start from _2 for additional endpoints
// Find the next available index by checking existing endpoints
let index = 2;
const usedIndexes = new Set();
existingEndpoints.forEach(endpoint => {
const header = endpoint.querySelector('h4');
if (header) {
const match = header.textContent.match(/#(\d+)/);
if (match) {
usedIndexes.add(parseInt(match[1]));
}
}
});
// Find the first unused index starting from 2
while (usedIndexes.has(index)) {
index++;
}
const endpointHtml = `
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-4 relative">
<button type="button" onclick="PulseApp.ui.settings.removeEndpoint(this)"
@@ -942,7 +976,17 @@ PulseApp.ui.settings = (() => {
if (!container) return;
// Load additional PVE endpoints from config
for (let i = 2; i <= 10; i++) {
// Find all PROXMOX_HOST_N keys in the config
const pveHostKeys = Object.keys(config)
.filter(key => key.match(/^PROXMOX_HOST_\d+$/))
.map(key => {
const match = key.match(/^PROXMOX_HOST_(\d+)$/);
return match ? parseInt(match[1]) : null;
})
.filter(num => num !== null && num > 1) // Exclude primary endpoint (no suffix)
.sort((a, b) => a - b);
for (const i of pveHostKeys) {
if (config[`PROXMOX_HOST_${i}`]) {
addPveEndpoint();
// Populate the newly added endpoint with data
@@ -968,7 +1012,17 @@ PulseApp.ui.settings = (() => {
if (!container) return;
// Load additional PBS endpoints from config
for (let i = 2; i <= 10; i++) {
// Find all PBS_HOST_N keys in the config
const pbsHostKeys = Object.keys(config)
.filter(key => key.match(/^PBS_HOST_\d+$/))
.map(key => {
const match = key.match(/^PBS_HOST_(\d+)$/);
return match ? parseInt(match[1]) : null;
})
.filter(num => num !== null && num > 1) // Exclude primary endpoint (no suffix)
.sort((a, b) => a - b);
for (const i of pbsHostKeys) {
if (config[`PBS_HOST_${i}`]) {
addPbsEndpoint();
// Populate the newly added endpoint with data