diff --git a/src/modules/node_info/files/PveMod_pvemanagerlib.js b/src/modules/node_info/files/PveMod_pvemanagerlib.js
index 0d4f013..a9b9005 100644
--- a/src/modules/node_info/files/PveMod_pvemanagerlib.js
+++ b/src/modules/node_info/files/PveMod_pvemanagerlib.js
@@ -391,7 +391,6 @@ Ext.define('PVE.node.StatusView', {
this.hide();
return '';
} else if (!gpuStats || !gpuStats.Graphics) {
- this.hide();
return '';
}
@@ -509,12 +508,9 @@ Ext.define('PVE.node.StatusView', {
}
html += '';
- if (html.indexOf('
') <= 0) {
- this.hide();
- return '';
- }
- this.show();
- return '' + html + '
';
+ return html.indexOf('
') > 0
+ ? '' + html + '
'
+ : '';
},
},
{
@@ -869,7 +865,6 @@ Ext.define('PVE.node.StatusView', {
this.hide();
return '';
} else if (!gpuStats || !gpuStats.Graphics || !gpuStats.Graphics.NVIDIA) {
- this.hide();
return '';
}
@@ -897,10 +892,8 @@ Ext.define('PVE.node.StatusView', {
});
if (rows.length === 0) {
- this.hide();
return '';
}
- this.show();
return '';
},
@@ -908,184 +901,236 @@ Ext.define('PVE.node.StatusView', {
{
itemId: 'upsc',
colspan: 2,
- printBar: false,
title: gettext('UPS Status'),
iconCls: 'fa fa-fw fa-battery-three-quarters',
- textField: 'PveMod_upsInfo',
+ valueField: 'PveMod_upsInfo',
+ printBar: true,
+ warningThreshold: 1.1,
+ criticalThreshold: 1.2,
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+ items: [
+ {
+ xtype: 'component',
+ itemId: 'label',
+ data: {
+ title: '',
+ usage: '',
+ iconCls: undefined,
+ },
+ tpl: [
+ '',
+ '',
+ ' ',
+ '',
+ '{title}
',
+ ],
+ },
+ {
+ xtype: 'container',
+ layout: {
+ type: 'hbox',
+ align: 'middle',
+ },
+ items: [
+ {
+ xtype: 'component',
+ itemId: 'usageText',
+ flex: 1,
+ minWidth: 0,
+ margin: '0 16 0 0',
+ },
+ {
+ xtype: 'progressbar',
+ itemId: 'progress',
+ flex: 1,
+ minWidth: 0,
+ height: 5,
+ value: 0,
+ animate: true,
+ },
+ ],
+ },
+ ],
+ initComponent: function() {
+ var me = this;
+ if (!me.title) {
+ throw 'no title defined';
+ }
+ Ext.container.Container.prototype.initComponent.call(me);
+ var progress = me.down('#progress');
+ if (progress) {
+ progress.setVisible(!!me.printBar);
+ }
+ me.updateValue(me.text, me.value);
+ me.setIconCls(me.iconCls);
+ },
+ setPrintBar: function(enable) {
+ var me = this;
+ me.printBar = enable;
+ var progress = me.down('#progress');
+ if (progress) {
+ progress.setVisible(enable);
+ }
+ },
+ updateValue: function(text, usage) {
+ var me = this;
+ if (me.lastText === text && me.lastUsage === usage) {
+ return;
+ }
+ me.lastText = text;
+ me.lastUsage = usage;
+
+ var label = me.getComponent('label');
+ if (label) {
+ label.update(Ext.apply(label.data, { title: me.title, usage: '' }));
+ }
+
+ var usageText = me.down('#usageText');
+ if (usageText) {
+ if (usageText.setHtml) {
+ usageText.setHtml(text || '');
+ } else {
+ usageText.update(text || '');
+ }
+ }
+
+ var progressBar = me.down('#progress');
+ if (usage !== undefined && me.printBar && Ext.isNumeric(usage) && usage >= 0 && progressBar) {
+ progressBar.updateProgress(usage, '');
+ if (usage > me.criticalThreshold) {
+ progressBar.removeCls('warning');
+ progressBar.addCls('critical');
+ } else if (usage > me.warningThreshold) {
+ progressBar.removeCls('critical');
+ progressBar.addCls('warning');
+ } else {
+ progressBar.removeCls('warning');
+ progressBar.removeCls('critical');
+ }
+ }
+ },
+ calculate: function(used) {
+ if (!used || used.disabled === true || typeof used !== 'object') {
+ return 0;
+ }
+ let charge = NaN;
+ Object.keys(used).forEach(function(k) {
+ const row = used[k];
+ if (row && typeof row === 'object' && row['battery.charge'] != null) {
+ charge = parseFloat(row['battery.charge']);
+ }
+ });
+ if (isNaN(charge)) {
+ return 0;
+ }
+ return Math.max(0, Math.min(1, charge / 100));
+ },
renderer: function(value) {
- let objValue;
- try {
- objValue = value || {};
- } catch(e) {
- objValue = {};
- }
+ let objValue;
+ try {
+ objValue = value || {};
+ } catch (e) {
+ objValue = {};
+ }
- if (objValue.disabled === true) {
- this.hide();
- return '';
- } else if (!objValue || Object.keys(objValue).length === 0) {
- return 'N/A';
- }
+ if (objValue.disabled === true) {
+ this.hide();
+ this.setPrintBar(false);
+ return '';
+ }
- // 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';
- }
+ const upsKeys = Object.keys(objValue).filter(function(k) {
+ return objValue[k] && typeof objValue[k] === 'object' && !Array.isArray(objValue[k]);
+ });
+ if (!upsKeys.length) {
+ this.hide();
+ this.setPrintBar(false);
+ return '';
+ }
+ this.show();
+ this.setPrintBar(true);
- // 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;
- }
- }
+ function formatRuntime(seconds) {
+ const s = parseInt(seconds, 10);
+ if (!s || isNaN(s)) {
+ return null;
+ }
+ const h = Math.floor(s / 3600);
+ const m = Math.floor((s % 3600) / 60);
+ if (h > 0) {
+ return h + 'h ' + m + 'm';
+ }
+ return m + 'm';
+ }
- // 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`;
- }
+ function statusText(upsStatus) {
+ const u = String(upsStatus || '').toUpperCase();
+ if (u.indexOf('LB') >= 0) {
+ return { text: 'Low Battery', color: '#d9534f' };
+ }
+ if (u.indexOf('OB') >= 0) {
+ return { text: 'On Battery', color: '#d9534f' };
+ }
+ if (u.indexOf('FSD') >= 0) {
+ return { text: 'Shutdown', color: '#d9534f' };
+ }
+ if (u.indexOf('OL') >= 0) {
+ return {
+ text: u.indexOf('CHRG') >= 0 ? 'Online, charging' : 'Online',
+ color: null,
+ };
+ }
+ return { text: upsStatus || 'Unknown', color: '#f0ad4e' };
+ }
- // 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 upsRealPower = upsData['ups.realpower'];
- const batteryMfrDate = upsData['battery.mfr.date'];
+ function colorize(label, color) {
+ if (!color) {
+ return label;
+ }
+ return '' + label + '';
+ }
- // Main status line with all metrics
- let statusLine = '';
+ const lines = [];
+ upsKeys.forEach(function(upsKey) {
+ const upsData = objValue[upsKey] || {};
+ const charge = parseFloat(upsData['battery.charge']);
+ const runtime = formatRuntime(upsData['battery.runtime']);
+ const inputVoltage = parseFloat(upsData['input.voltage']);
+ const load = parseFloat(upsData['ups.load']);
+ const watts = parseFloat(upsData['ups.realpower']);
+ const model = upsData['ups.model'] || upsData['device.model'] || upsKey;
+ const st = statusText(upsData['ups.status']);
+ const bits = [];
+ if (model) {
+ bits.push(model);
+ }
+ if (st.text) {
+ bits.push(colorize(st.text, st.color));
+ }
+ if (!isNaN(charge)) {
+ bits.push('Battery ' + Math.round(charge) + '%');
+ }
+ if (!isNaN(watts)) {
+ bits.push(Math.round(watts) + ' W');
+ }
+ if (!isNaN(load)) {
+ bits.push('Load ' + Math.round(load) + '%');
+ }
+ if (runtime) {
+ bits.push(runtime + ' left');
+ }
+ if (!isNaN(inputVoltage)) {
+ const places = inputVoltage >= 50 ? 0 : 1;
+ bits.push(inputVoltage.toFixed(places) + ' V in');
+ }
+ lines.push(bits.join(' | '));
+ });
- // 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 (upsRealPower && !isNaN(parseFloat(upsRealPower))) {
- actualWattage = Math.round(parseFloat(upsRealPower));
- }
-
- // Real power (calculated watt usage)
- if (actualWattage !== null) {
- statusLine += 'Output: ' + actualWattage + 'W';
- } else {
- statusLine += 'Output: 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 + ';') : '';
- statusLine += ' | Test: ' + testResult + '';
- } else {
- statusLine += ' | Test: N/A';
- }
-
- // 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 lines.join('
');
+ }
},
{
xtype: 'box',