diff --git a/pvemanagerlib.js b/pvemanagerlib.js index 2d71d6f..2efa46c 100644 --- a/pvemanagerlib.js +++ b/pvemanagerlib.js @@ -48875,46 +48875,177 @@ Ext.define('PVE.node.StatusView', { defaults: { xtype: 'pmxInfoWidget', - padding: '0 10 5 10', + padding: '0 10 2 10', }, items: [ + // ========== PRIMARY KPIs (Tier 1) ========== { - itemId: 'cpuss', + xtype: 'box', colspan: 2, - printBar: false, - title: gettext('CPU (s)'), - textField: 'cpuinfo', - renderer: Proxmox.Utils.render_cpu_model, - value: '', - }, + padding: '0', + html: '
Primary Metrics
', + }, { itemId: 'cpu', iconCls: 'fa fa-fw pmx-itype-icon-processor pmx-icon', - title: gettext('Usage'), + title: gettext('CPU Usage'), valueField: 'cpu', maxField: 'cpuinfo', - renderer: Proxmox.Utils.render_node_cpu_usage, + renderer: function(value, record) { + let result = Proxmox.Utils.render_node_cpu_usage(value, record); + // Append CPU model if available + if (record && record.cpuinfo && record.cpuinfo.model) { + result += ` (${record.cpuinfo.model})`; + } + return result; + }, }, { - itemId: 'wait', - iconCls: 'fa fa-fw fa-clock-o', - title: gettext('IO delay'), - valueField: 'wait', - rowspan: 2, + iconCls: 'fa fa-fw pmx-itype-icon-memory pmx-icon', + itemId: 'memory', + title: gettext('Memory Usage'), + valueField: 'memory', + maxField: 'memory', + warningThreshold: 0.9, + criticalThreshold: 0.975, + renderer: Proxmox.Utils.render_node_size_usage, + }, + { + itemId: 'ksm', + iconCls: 'fa fa-fw fa-clone', + printBar: false, + title: gettext('KSM sharing'), + textField: 'ksm', + renderer: function (record) { + return Proxmox.Utils.render_size(record.shared); + }, + }, + { + itemId: 'gpu', + iconCls: 'fa fa-fw fa-desktop', + title: gettext('GPU Usage'), + printBar: false, + textField: 'gpuStats', + renderer: function(gpuStats) { + if (!gpuStats || !gpuStats.Graphics) { + return ''; + } + + let hasActiveGPU = false; + let gpuName = ''; + + // Check Intel GPUs + if (gpuStats.Graphics.Intel) { + const keys = Object.keys(gpuStats.Graphics.Intel).sort(); + if (keys.length > 0) { + const gpuData = gpuStats.Graphics.Intel[keys[0]]; + hasActiveGPU = true; + gpuName = gpuData.name; + } + } + + // Check NVIDIA GPUs + if (gpuStats.Graphics.NVIDIA) { + const keys = Object.keys(gpuStats.Graphics.NVIDIA).sort(); + if (keys.length > 0) { + const stats = gpuStats.Graphics.NVIDIA[keys[0]].stats; + hasActiveGPU = true; + gpuName = stats.name; + } + } + + return hasActiveGPU ? gpuName : ''; + }, + }, + { + itemId: 'gpu_usage', + iconCls: 'fa fa-fw fa-desktop', + title: gettext('GPU 0'), + valueField: 'gpuStats', + printBar: false, + textField: 'gpuStats', + renderer: function(gpuStats) { + if (!gpuStats || !gpuStats.Graphics) { + return ''; + } + + // Check Intel GPUs + if (gpuStats.Graphics.Intel) { + const keys = Object.keys(gpuStats.Graphics.Intel).sort(); + if (keys.length > 0) { + const gpuData = gpuStats.Graphics.Intel[keys[0]]; + if (gpuData.stats.engines && gpuData.stats.engines['Render/3D']) { + const usage = gpuData.stats.engines['Render/3D'].busy; + return `${usage}%`; + } + } + } + + // Check NVIDIA GPUs + if (gpuStats.Graphics.NVIDIA) { + const keys = Object.keys(gpuStats.Graphics.NVIDIA).sort(); + if (keys.length > 0) { + const stats = gpuStats.Graphics.NVIDIA[keys[0]].stats; + if (stats.utilization) { + return `${stats.utilization.gpu}%`; + } + } + } + + return ''; + }, + }, + { + iconCls: 'fa fa-fw fa-hdd-o', + itemId: 'rootfs', + title: gettext('Disk (/) Usage'), + valueField: 'rootfs', + maxField: 'rootfs', + renderer: Proxmox.Utils.render_node_size_usage, + }, + { + iconCls: 'fa fa-fw fa-refresh', + itemId: 'swap', + title: gettext('SWAP Usage'), + valueField: 'swap', + maxField: 'swap', + warningThreshold: 0.4, + criticalThreshold: 0.8, + renderer: Proxmox.Utils.render_node_size_usage, + }, + // Fill the remaining cell so the next colspan:2 section header starts on a new row. + { + xtype: 'box', + html: '', + padding: 0, + }, + + // ========== SECONDARY DETAILS (Tier 2) ========== + { + xtype: 'box', + colspan: 2, + padding: '15 0 5 0', + html: '
Secondary Details
', }, { itemId: 'load', iconCls: 'fa fa-fw fa-tasks', - title: gettext('Load average'), + title: gettext('CPU Load Average'), printBar: false, textField: 'loadavg', }, + { + itemId: 'wait', + iconCls: 'fa fa-fw fa-clock-o', + title: gettext('CPU I/O Delay'), + valueField: 'wait', + }, { itemId: 'thermalCpu', colspan: 2, printBar: false, - title: gettext('Thermal State'), + title: gettext('CPU Thermal State'), iconCls: 'fa fa-fw fa-thermometer-half', textField: 'sensorsOutput', renderer: function(value){ @@ -49062,139 +49193,108 @@ Ext.define('PVE.node.StatusView', { } }); - let result = ''; + let html = ''; temps.forEach((cpuData, cpuIndex) => { const strCoreTemps = cpuData.temps.map((strTemp, index, arr) => { return strTemp + (index + 1 < arr.length ? (itemsPerRow > 0 && (index + 1) % itemsPerRow === 0 ? '
' : ' | ') : ''); }); if(strCoreTemps.length > 0) { - let cpuPrefix = cpuCount > 1 ? `CPU ${cpuIndex + 1}` : 'CPU'; - let cpuModelStr = cpuData.model ? ` (${cpuData.model})` : ''; - result += `${cpuPrefix}${cpuModelStr}: ` + strCoreTemps.join('') + (cpuIndex < cpuCount - 1 ? '
' : ''); + let cpuLabel = cpuCount > 1 ? `Socket ${cpuIndex + 1}` : 'Socket 1'; + let cpuModelStr = cpuData.model || 'Unknown CPU'; + + html += ''; + html += ``; + html += ``; + html += ''; } }); - - return '
' + (result.length > 0 ? result : 'N/A') + '
'; + html += '
${cpuModelStr}${strCoreTemps.join('')}
'; + + return html.indexOf('') > 0 + ? '
' + html + '
' + : 'N/A'; } }, { - xtype: 'box', + itemId: 'gpu_details', colspan: 2, - padding: '0 0 10 0', - }, - { - itemId: 'memory2', - colspan: 2, - printBar: false, - title: gettext('Memory'), - textField: 'Memory', - }, - { - iconCls: 'fa fa-fw pmx-itype-icon-memory pmx-icon', - itemId: 'memory', - title: gettext('Usage'), - valueField: 'memory', - maxField: 'memory', - warningThreshold: 0.9, - criticalThreshold: 0.975, - // TODO: split out ARC usage - renderer: Proxmox.Utils.render_node_size_usage, - }, - { - itemId: 'ksm', - printBar: false, - title: gettext('KSM sharing'), - textField: 'ksm', - renderer: function (record) { - return Proxmox.Utils.render_size(record.shared); - }, - padding: '0 10 10 10', - }, - { - xtype: 'box', - colspan: 2, - padding: '0 0 10 0', - }, - { - itemId: 'gpu_heading', - colspan: 2, - printBar: false, - title: gettext('GPU(s)'), - textField: 'Memory', - }, - { - itemId: 'gpu', - colspan: 2, - iconCls: 'fa fa-desktop', - title: gettext('Device'), + iconCls: 'fa fa-fw fa-desktop', + title: gettext('GPU Details'), printBar: false, textField: 'gpuStats', renderer: function(gpuStats) { - if (!gpuStats || !gpuStats.Graphics || !gpuStats.Graphics.Intel) { - return 'N/A'; + if (!gpuStats || !gpuStats.Graphics) { + return ''; } - let html = ''; - - console.log(gpuStats); + let html = ''; - // Intel GPUs + // Intel GPUs - Secondary details if (gpuStats.Graphics.Intel) { Object.keys(gpuStats.Graphics.Intel).sort().forEach(key => { const gpuData = gpuStats.Graphics.Intel[key]; - html += `
`; - html += `
${gpuData.name}
`; - html += `
`; + let details = []; + + // All engine details if (gpuData.stats.engines) { - // Render/3D if (gpuData.stats.engines['Render/3D']) { - html += `Render/3D: ${gpuData.stats.engines['Render/3D'].busy}% | `; + details.push(`Render/3D: ${gpuData.stats.engines['Render/3D'].busy}%`); } - - // Video if (gpuData.stats.engines['Video']) { - html += `Video: ${gpuData.stats.engines['Video'].busy}% | `; + details.push(`Video: ${gpuData.stats.engines['Video'].busy}%`); } - - // Blitter if (gpuData.stats.engines['Blitter']) { - html += `Blitter: ${gpuData.stats.engines['Blitter'].busy}% | `; + details.push(`Blitter: ${gpuData.stats.engines['Blitter'].busy}%`); } - - // VideoEnhance if (gpuData.stats.engines['VideoEnhance']) { - html += `VideoEnhance: ${gpuData.stats.engines['VideoEnhance'].busy}% | `; + details.push(`VideoEnhance: ${gpuData.stats.engines['VideoEnhance'].busy}%`); } } - // Power and Frequency info - html += `Power: ${gpuData.stats.power?.GPU ?? 'N/A'} / ${gpuData.stats.power?.Package ?? 'N/A'} ${gpuData.stats.power?.unit || 'W'}`; - html += ` | Freq: ${gpuData.stats.frequency?.actual ?? 'N/A'}/${gpuData.stats.frequency?.requested ?? 'N/A'} ${gpuData.frequency?.unit || 'MHz'}`; + // Power + if (gpuData.stats.power) { + details.push(`Power: ${gpuData.stats.power?.GPU ?? 'N/A'} / ${gpuData.stats.power?.Package ?? 'N/A'} ${gpuData.stats.power?.unit || 'W'}`); + } - html += `
`; + // Frequency + if (gpuData.stats.frequency) { + details.push(`Freq: ${gpuData.stats.frequency?.actual ?? 'N/A'}/${gpuData.stats.frequency?.requested ?? 'N/A'} ${gpuData.stats.frequency?.unit || 'MHz'}`); + } + + html += ''; + html += ``; + html += ``; + html += ''; }); } - // NVIDIA GPUs + // NVIDIA GPUs - Secondary details if (gpuStats.Graphics.NVIDIA) { Object.keys(gpuStats.Graphics.NVIDIA).sort().forEach(key => { const gpuData = gpuStats.Graphics.NVIDIA[key]; const stats = gpuData.stats; - html += `
`; - html += `
${stats.name}
`; - html += `
`; + let details = []; - // GPU Utilization - if (stats.utilization) { - html += `GPU: ${stats.utilization.gpu}${stats.utilization.unit} | `; - html += `MEM: ${stats.utilization.memory}${stats.utilization.unit} | `; + // Memory Utilization + if (stats.utilization && stats.utilization.memory) { + const memUsage = parseInt(stats.utilization.memory); + let memStyle = ''; + if (memUsage >= 90) memStyle = 'color: #d9534f; font-weight: bold;'; + else if (memUsage >= 70) memStyle = 'color: #f0ad4e; font-weight: bold;'; + details.push(`MEM: ${stats.utilization.memory}%`); } - // Memory Usage + // VRAM Usage if (stats.memory) { - html += `VRAM: ${stats.memory.used}/${stats.memory.total} ${stats.memory.unit} | `; + const vramUsedGB = parseInt(stats.memory.used); + const vramTotalGB = parseInt(stats.memory.total); + const vramPercent = (vramUsedGB / vramTotalGB) * 100; + let vramStyle = ''; + if (vramPercent >= 90) vramStyle = 'color: #d9534f; font-weight: bold;'; + else if (vramPercent >= 70) vramStyle = 'color: #f0ad4e; font-weight: bold;'; + details.push(`VRAM: ${stats.memory.used}/${stats.memory.total} ${stats.memory.unit}`); } // Temperature @@ -49205,62 +49305,32 @@ Ext.define('PVE.node.StatusView', { } else if (stats.temperature.gpu >= 70) { tempStyle = 'color: #FFC300; font-weight: bold;'; } - html += `Temp: ${stats.temperature.gpu}${stats.temperature.unit} | `; - } - - // Fan Speed - if (stats.fan) { - html += `Fan: ${stats.fan.speed}${stats.fan.unit} | `; + details.push(`Temp: ${stats.temperature.gpu}${stats.temperature.unit}`); } // Power if (stats.power) { - html += `Power: ${stats.power.draw}/${stats.power.limit} ${stats.power.unit}`; + details.push(`Power: ${stats.power.draw}/${stats.power.limit} ${stats.power.unit}`); } - html += `
`; + html += ''; + html += ``; + html += ``; + html += ''; }); } - // todo add AMD - - return html; + html += '
${gpuData.name}${details.join(' | ')}
${stats.name}${details.join(' | ')}
'; + return html.indexOf('') > 0 + ? '
' + html + '
' + : ''; }, - }, - { - xtype: 'box', - colspan: 2, - padding: '0 0 10 0', }, - { - itemId: 'Storage', - colspan: 2, - printBar: false, - title: gettext('Storage'), - textField: 'Memory', - }, - { - iconCls: 'fa fa-fw fa-hdd-o', - itemId: 'rootfs', - title: '/ ' + gettext('HD space'), - valueField: 'rootfs', - maxField: 'rootfs', - renderer: Proxmox.Utils.render_node_size_usage, - }, - { - iconCls: 'fa fa-fw fa-refresh', - itemId: 'swap', - printSize: true, - title: gettext('SWAP usage'), - valueField: 'swap', - maxField: 'swap', - renderer: Proxmox.Utils.render_node_size_usage, - }, { itemId: 'thermalNvme', colspan: 2, printBar: false, - title: gettext('NVMe Thermal State'), + title: gettext('NVMe Temperatures'), iconCls: 'fa fa-fw fa-thermometer-half', textField: 'sensorsOutput', renderer: function(value) { @@ -49279,7 +49349,7 @@ Ext.define('PVE.node.StatusView', { objValue = {}; } const nvmeKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort(); - let temps = []; + let nvmeData = []; nvmeKeys.forEach((nvmeKey, index) => { try { let tempVal = NaN, tempMax = NaN, tempCrit = NaN, model = '', serial = ''; @@ -49291,9 +49361,10 @@ Ext.define('PVE.node.StatusView', { } else if (secondLevelKey.endsWith('_crit')) { tempCrit = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); } - model = objValue[nvmeKey]['model'] || ''; - serial = objValue[nvmeKey]['serial'] || ''; }); + model = objValue[nvmeKey]['model'] || 'Unknown'; + serial = objValue[nvmeKey]['serial'] || ''; + if (!isNaN(tempVal)) { let tempStyle = ''; if (!isNaN(tempMax) && tempVal >= tempMax) { @@ -49302,31 +49373,49 @@ Ext.define('PVE.node.StatusView', { if (!isNaN(tempCrit) && tempVal >= tempCrit) { tempStyle = 'color: red; font-weight: bold;'; } - const tempStr = `${model} (${serial}): ${Ext.util.Format.number(tempVal, '0.0')}${tempHelper.getUnit()}`; - temps.push(tempStr); + nvmeData.push({ + model: model, + serial: serial, + temp: tempVal, + tempStyle: tempStyle, + unit: tempHelper.getUnit() + }); } } catch(e) { /*_*/ } }); - const result = temps.map((strTemp, index, arr) => { return strTemp + (index + 1 < arr.length ? ((index + 1) % itemsPerRow === 0 ? '
' : ' | ') : ''); }); - return '
' + (result.length > 0 ? result.join('') : 'N/A') + '
'; + + if (nvmeData.length === 0) { + return 'N/A'; + } + + let html = ''; + nvmeData.forEach((data) => { + let deviceName = data.model; + if (data.serial) { + deviceName += ` (${data.serial})`; + } + html += ''; + html += ``; + html += ``; + html += ''; + }); + html += '
${deviceName}${Ext.util.Format.number(data.temp, '0.0')}${data.unit}
'; + return '
' + html + '
'; } }, + + // ========== TERTIARY DIAGNOSTICS (Tier 3) ========== { xtype: 'box', colspan: 2, - padding: '0 0 10 0', - }, - { - itemId: 'Coooling', - colspan: 2, - printBar: false, - title: gettext('Cooling'), + padding: '15 0 5 0', + html: '
Diagnostics
', }, { itemId: 'speedFan', colspan: 2, printBar: false, - title: gettext('Fan Speed(s)'), + title: gettext('System Fans'), iconCls: 'fa fa-fw fa-snowflake-o', textField: 'sensorsOutput', renderer: function(value) { @@ -49381,19 +49470,54 @@ Ext.define('PVE.node.StatusView', { } }); }); - return '
' + (speeds.length > 0 ? speeds.join(' | ') : 'N/A') + '
'; + return '
' + (speeds.length > 0 ? speeds.join(' | ') : 'N/A') + '
'; } }, { - xtype: 'box', - colspan: 2, - html: gettext('UPS'), - }, - { + itemId: 'gpuFans', + colspan: 2, + printBar: false, + title: gettext('GPU Fans'), + iconCls: 'fa fa-fw fa-snowflake-o', + textField: 'gpuStats', + renderer: function(gpuStats) { + if (!gpuStats || !gpuStats.Graphics || !gpuStats.Graphics.NVIDIA) { + return 'N/A'; + } + + let rows = []; + + Object.keys(gpuStats.Graphics.NVIDIA).sort().forEach(key => { + const gpuData = gpuStats.Graphics.NVIDIA[key]; + const stats = gpuData?.stats; + const fan = stats?.fan; + + if (!fan || fan.speed === undefined || fan.speed === null) { + return; + } + + const gpuName = stats?.name || key; + const unit = fan.unit || '%'; + rows.push( + '' + + `${gpuName}` + + `Fan: ${fan.speed}${unit}` + + '', + ); + }); + + if (rows.length === 0) { + return 'N/A'; + } + + return '
' + rows.join('') + '
'; + }, + }, + { itemId: 'upsc', colspan: 2, printBar: false, - title: gettext('Device'), + title: gettext('UPS Status'), iconCls: 'fa fa-fw fa-battery-three-quarters', textField: 'upsStats', renderer: function(value) { @@ -49411,7 +49535,7 @@ Ext.define('PVE.node.StatusView', { // If objValue is null or empty, return N/A if (!objValue || Object.keys(objValue).length === 0) { - return '
N/A
'; + return 'N/A'; } // Helper function to get status color @@ -49466,17 +49590,6 @@ Ext.define('PVE.node.StatusView', { const upsRealPowerNominal = upsData['ups.realpower.nominal']; const batteryMfrDate = upsData['battery.mfr.date']; - let displayItems = []; - - // First line: UPS identifier and model - let modelLine = ''; - if (upsModel) { - modelLine = `${upsModel}:`; - } else { - modelLine = `${upsKey}: N/A`; - } - displayItems.push(modelLine); - // Main status line with all metrics let statusLine = ''; @@ -49566,39 +49679,35 @@ Ext.define('PVE.node.StatusView', { statusLine += 'Output: N/A'; } - displayItems.push(statusLine); - - // Combined battery and test line - let batteryTestLine = ''; - if (batteryMfrDate) { - batteryTestLine += 'Battery MFD: ' + batteryMfrDate + ''; - } else { - batteryTestLine += 'Battery MFD: N/A'; - } - + // Append battery MFD + last test to the same line (single-line UPS summary) + statusLine += ' | Battery MFD: ' + (batteryMfrDate || 'N/A'); if (testResult && !testResult.toLowerCase().includes('no test')) { const testColor = testResult.toLowerCase().includes('passed') ? null : '#d9534f'; let testStyle = testColor ? ('color: ' + testColor + ';') : ''; - batteryTestLine += ' | Test: ' + testResult + ''; + statusLine += ' | Test: ' + testResult + ''; } else { - batteryTestLine += ' | Test: N/A'; + statusLine += ' | Test: N/A'; } - - displayItems.push(batteryTestLine); - // Add this UPS's display to the overall collection - allDisplayItems.push('
' + displayItems.join('
') + '
'); + // Build UPS display with model on left, details on right + let upsHtml = ''; + upsHtml += '' + (upsModel || upsKey) + ''; + upsHtml += '' + statusLine + ''; + upsHtml += ''; + + allDisplayItems.push(upsHtml); }); // Format the final output for all UPS devices - return '
' + allDisplayItems.join('') + '
'; + return '
' + allDisplayItems.join('') + '
'; } - }, + }, { xtype: 'box', colspan: 2, - padding: '0 0 10 0', - }, + padding: '15 0 5 0', + html: '
System
', + }, { colspan: 2, title: gettext('Kernel Version'), @@ -49644,7 +49753,7 @@ Ext.define('PVE.node.StatusView', { colspan: 2, printBar: false, title: gettext('Sensor Mod Version'), - textField: 'pveversion', + textField: 'pveModVersion', value: '', }, ],