diff --git a/pvemanagerlib.js b/pvemanagerlib.js
index c35b83b..2d71d6f 100644
--- a/pvemanagerlib.js
+++ b/pvemanagerlib.js
@@ -49105,7 +49105,9 @@ Ext.define('PVE.node.StatusView', {
printBar: false,
title: gettext('KSM sharing'),
textField: 'ksm',
- renderer: (record) => Proxmox.Utils.render_size(record.shared),
+ renderer: function (record) {
+ return Proxmox.Utils.render_size(record.shared);
+ },
padding: '0 10 10 10',
},
{
@@ -49128,53 +49130,99 @@ Ext.define('PVE.node.StatusView', {
printBar: false,
textField: 'gpuStats',
renderer: function(gpuStats) {
- console.log(gpuStats);
if (!gpuStats || !gpuStats.Graphics || !gpuStats.Graphics.Intel) {
return 'N/A';
}
let html = '';
- Object.keys(gpuStats.Graphics.Intel).forEach(key => {
- const gpuData = gpuStats.Graphics.Intel[key];
- console.log("here1");
- html += `
`;
- html += `
${gpuData.name}
`;
- html += `
`;
-
- if (gpuData.stats.engines) {
- console.log("here2");
- // Render/3D
- if (gpuData.stats.engines['Render/3D']) {
- html += `Render/3D: ${gpuData.stats.engines['Render/3D'].busy}% | `;
- }
-
- // Video
- if (gpuData.stats.engines['Video']) {
- html += `Video: ${gpuData.stats.engines['Video'].busy}% | `;
- }
-
- // Blitter
- if (gpuData.stats.engines['Blitter']) {
- html += `Blitter: ${gpuData.stats.engines['Blitter'].busy}% | `;
- }
-
- // VideoEnhance
- if (gpuData.stats.engines['VideoEnhance']) {
- html += `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'}`;
-
- html += `
`;
- });
+ console.log(gpuStats);
- // todo add NVIDIA
+ // Intel GPUs
+ if (gpuStats.Graphics.Intel) {
+ Object.keys(gpuStats.Graphics.Intel).sort().forEach(key => {
+ const gpuData = gpuStats.Graphics.Intel[key];
+ html += ``;
+ html += `
${gpuData.name}
`;
+ html += `
`;
+
+ if (gpuData.stats.engines) {
+ // Render/3D
+ if (gpuData.stats.engines['Render/3D']) {
+ html += `Render/3D: ${gpuData.stats.engines['Render/3D'].busy}% | `;
+ }
+
+ // Video
+ if (gpuData.stats.engines['Video']) {
+ html += `Video: ${gpuData.stats.engines['Video'].busy}% | `;
+ }
+
+ // Blitter
+ if (gpuData.stats.engines['Blitter']) {
+ html += `Blitter: ${gpuData.stats.engines['Blitter'].busy}% | `;
+ }
+
+ // VideoEnhance
+ if (gpuData.stats.engines['VideoEnhance']) {
+ html += `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'}`;
+
+ html += `
`;
+ });
+ }
- // todo add NVIDIA
+ // NVIDIA GPUs
+ 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 += `
`;
+
+ // GPU Utilization
+ if (stats.utilization) {
+ html += `GPU: ${stats.utilization.gpu}${stats.utilization.unit} | `;
+ html += `MEM: ${stats.utilization.memory}${stats.utilization.unit} | `;
+ }
+
+ // Memory Usage
+ if (stats.memory) {
+ html += `VRAM: ${stats.memory.used}/${stats.memory.total} ${stats.memory.unit} | `;
+ }
+
+ // Temperature
+ if (stats.temperature) {
+ let tempStyle = '';
+ if (stats.temperature.gpu >= 80) {
+ tempStyle = 'color: red; font-weight: bold;';
+ } 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} | `;
+ }
+
+ // Power
+ if (stats.power) {
+ html += `Power: ${stats.power.draw}/${stats.power.limit} ${stats.power.unit}`;
+ }
+
+ html += `
`;
+ });
+ }
+
+ // todo add AMD
return html;
},
@@ -49318,7 +49366,11 @@ Ext.define('PVE.node.StatusView', {
// Call the recursive function to find fan keys and values
findFanKeys(parentObj, fanKeys);
// Sort the fan keys
- fanKeys.sort();
+ fanKeys.sort((a, b) => {
+ if (a.key < b.key) return -1;
+ if (a.key > b.key) return 1;
+ return 0;
+ });
// Process each fan key and value
fanKeys.forEach(({ key: fanKey, value: fanSpeed }) => {
try {
@@ -49332,6 +49384,216 @@ Ext.define('PVE.node.StatusView', {
return '' + (speeds.length > 0 ? speeds.join(' | ') : 'N/A') + '
';
}
},
+ {
+ xtype: 'box',
+ colspan: 2,
+ html: gettext('UPS'),
+ },
+ {
+ itemId: 'upsc',
+ colspan: 2,
+ printBar: false,
+ title: gettext('Device'),
+ iconCls: 'fa fa-fw fa-battery-three-quarters',
+ textField: 'upsStats',
+ renderer: function(value) {
+ let objValue = {};
+ try {
+ // Parse the UPS data
+ if (typeof value === 'string') {
+ objValue = JSON.parse(value) || {};
+ } else if (typeof value === 'object') {
+ objValue = value || {};
+ }
+ } catch(e) {
+ objValue = {};
+ }
+
+ // If objValue is null or empty, return N/A
+ if (!objValue || Object.keys(objValue).length === 0) {
+ return 'N/A
';
+ }
+
+ // Helper function to get status color
+ function getStatusColor(status) {
+ if (!status) return '#999';
+ const statusUpper = status.toUpperCase();
+ if (statusUpper.includes('OL')) return null;
+ if (statusUpper.includes('OB')) return '#d9534f';
+ if (statusUpper.includes('LB')) return '#d9534f';
+ return '#f0ad4e';
+ }
+
+ // Helper function to get load/charge color
+ function getPercentageColor(value, isLoad = false) {
+ if (!value || isNaN(value)) return '#999';
+ const num = parseFloat(value);
+ if (isLoad) {
+ if (num >= 80) return '#d9534f';
+ if (num >= 60) return '#f0ad4e';
+ return null;
+ } else {
+ if (num <= 20) return '#d9534f';
+ if (num <= 50) return '#f0ad4e';
+ return null;
+ }
+ }
+
+ // Helper function to format runtime
+ function formatRuntime(seconds) {
+ if (!seconds || isNaN(seconds)) return 'N/A';
+ const mins = Math.floor(seconds / 60);
+ const secs = seconds % 60;
+ return `${mins}m ${secs}s`;
+ }
+
+ // Process each UPS in the data
+ let allDisplayItems = [];
+
+ Object.keys(objValue).forEach(upsKey => {
+ const upsData = objValue[upsKey];
+
+ // Extract key UPS information
+ const batteryCharge = upsData['battery.charge'];
+ const batteryRuntime = upsData['battery.runtime'];
+ const inputVoltage = upsData['input.voltage'];
+ const upsLoad = upsData['ups.load'];
+ const upsStatus = upsData['ups.status'];
+ const upsModel = upsData['ups.model'] || upsData['device.model'];
+ const testResult = upsData['ups.test.result'];
+ const batteryChargeLow = upsData['battery.charge.low'];
+ const batteryRuntimeLow = upsData['battery.runtime.low'];
+ 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 = '';
+
+ // Status
+ if (upsStatus) {
+ const statusUpper = upsStatus.toUpperCase();
+ let statusText = 'Unknown';
+ let statusColor = '#f0ad4e';
+
+ if (statusUpper.includes('OL')) {
+ statusText = 'Online';
+ statusColor = null;
+ } else if (statusUpper.includes('OB')) {
+ statusText = 'On Battery';
+ statusColor = '#d9534f';
+ } else if (statusUpper.includes('LB')) {
+ statusText = 'Low Battery';
+ statusColor = '#d9534f';
+ } else {
+ statusText = upsStatus;
+ statusColor = '#f0ad4e';
+ }
+
+ let statusStyle = statusColor ? ('color: ' + statusColor + ';') : '';
+ statusLine += 'Status: ' + statusText + '';
+ } else {
+ statusLine += 'Status: N/A';
+ }
+
+ // Battery charge
+ if (statusLine) statusLine += ' | ';
+ if (batteryCharge) {
+ const chargeColor = getPercentageColor(batteryCharge, false);
+ let chargeStyle = chargeColor ? ('color: ' + chargeColor + ';') : '';
+ statusLine += 'Battery: ' + batteryCharge + '%';
+ } else {
+ statusLine += 'Battery: N/A';
+ }
+
+ // Load percentage
+ if (statusLine) statusLine += ' | ';
+ if (upsLoad) {
+ const loadColor = getPercentageColor(upsLoad, true);
+ let loadStyle = loadColor ? ('color: ' + loadColor + ';') : '';
+ statusLine += 'Load: ' + upsLoad + '%';
+ } else {
+ statusLine += 'Load: N/A';
+ }
+
+ // Runtime
+ if (statusLine) statusLine += ' | ';
+ if (batteryRuntime) {
+ const runtime = parseInt(batteryRuntime);
+ const runtimeLowThreshold = batteryRuntimeLow ? parseInt(batteryRuntimeLow) : 600;
+ let runtimeColor = null;
+ if (runtime <= runtimeLowThreshold / 2) runtimeColor = '#d9534f';
+ else if (runtime <= runtimeLowThreshold) runtimeColor = '#f0ad4e';
+ let runtimeStyle = runtimeColor ? ('color: ' + runtimeColor + ';') : '';
+ statusLine += 'Runtime: ' + formatRuntime(runtime) + '';
+ } else {
+ statusLine += 'Runtime: N/A';
+ }
+
+ // Input voltage
+ if (statusLine) statusLine += ' | ';
+ if (inputVoltage) {
+ statusLine += 'Input: ' + parseFloat(inputVoltage).toFixed(0) + 'V';
+ } else {
+ statusLine += 'Input: N/A';
+ }
+
+ // Calculate actual watt usage
+ if (statusLine) statusLine += ' | ';
+ let actualWattage = null;
+ if (upsLoad && upsRealPowerNominal) {
+ const load = parseFloat(upsLoad);
+ const nominal = parseFloat(upsRealPowerNominal);
+ if (!isNaN(load) && !isNaN(nominal)) {
+ actualWattage = Math.round((load / 100) * nominal);
+ }
+ }
+
+ // Real power (calculated watt usage)
+ if (actualWattage !== null) {
+ statusLine += 'Output: ' + actualWattage + 'W';
+ } else {
+ 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';
+ }
+
+ if (testResult && !testResult.toLowerCase().includes('no test')) {
+ const testColor = testResult.toLowerCase().includes('passed') ? null : '#d9534f';
+ let testStyle = testColor ? ('color: ' + testColor + ';') : '';
+ batteryTestLine += ' | Test: ' + testResult + '';
+ } else {
+ batteryTestLine += ' | Test: N/A';
+ }
+
+ displayItems.push(batteryTestLine);
+
+ // Add this UPS's display to the overall collection
+ allDisplayItems.push('' + displayItems.join('
') + '
');
+ });
+
+ // Format the final output for all UPS devices
+ return '' + allDisplayItems.join('') + '
';
+ }
+ },
{
xtype: 'box',
colspan: 2,
@@ -49927,6 +50189,14 @@ Ext.define('PVE.node.Summary', {
store: rrdstore,
unit: 'percent',
},
+ {
+ // todo find me
+ xtype: 'proxmoxRRDChart',
+ title: gettext('Graphics Intel'),
+ fields: ['netin', 'netout'],
+ fieldTitles: [gettext('Incoming'), gettext('Outgoing')],
+ store: rrdstore,
+ },
],
listeners: {
resize: function (panel) {