diff --git a/pve-mod-gui-sensors.sh b/pve-mod-gui-sensors.sh
index ceb61eb..ad62347 100644
--- a/pve-mod-gui-sensors.sh
+++ b/pve-mod-gui-sensors.sh
@@ -861,226 +861,6 @@ Ext.define('PVE.mod.TempHelper', {\n\
}" "$PVE_MANAGER_LIB_JS_FILE"
fi
- if [ $ENABLE_UPS = true ]; then
- # Add UPS status display
- sed -i "/^Ext.define('PVE.node.StatusView',/ {
- :a;
- /items:/!{N;ba;}
- :b;
- /'thermal.*},/!{N;bb;}
- a\
- \\
- {\n\
- xtype: 'box',\n\
- colspan: 2,\n\
- html: gettext('UPS'),\n\
- },\n\
- {\n\
- itemId: 'upsc',\n\
- colspan: 2,\n\
- printBar: false,\n\
- title: gettext('Device'),\n\
- iconCls: 'fa fa-fw fa-battery-three-quarters',\n\
- textField: 'upsc',\n\
- renderer: function(value) {\n\
- console.log(value);\n\
-\n\
- let objValue = {};\n\
- try {\n\
- // Parse the UPS data\n\
- if (typeof value === 'string') {\n\
- const lines = value.split('\n');\n\
- lines.forEach(line => {\n\
- const colonIndex = line.indexOf(':');\n\
- if (colonIndex > 0) {\n\
- const key = line.substring(0, colonIndex).trim();\n\
- const val = line.substring(colonIndex + 1).trim();\n\
- objValue[key] = val;\n\
- }\n\
- });\n\
- } else if (typeof value === 'object') {\n\
- objValue = value || {};\n\
- }\n\
- } catch(e) {\n\
- objValue = {};\n\
- }\n\
-\n\
- // If objValue is null or empty, return N/A\n\
- if (!objValue || Object.keys(objValue).length === 0) {\n\
- return '
N/A
';\n\
- }\n\
-\n\
- // Helper function to get status color\n\
- function getStatusColor(status) {\n\
- if (!status) return '#999';\n\
- const statusUpper = status.toUpperCase();\n\
- if (statusUpper.includes('OL')) return 'white'; // Green for online\n\
- if (statusUpper.includes('OB')) return '#d9534f'; // Red for on battery\n\
- if (statusUpper.includes('LB')) return '#d9534f'; // Red for low battery\n\
- return '#f0ad4e'; // Orange for other states\n\
- }\n\
-\n\
- // Helper function to get load/charge color\n\
- function getPercentageColor(value, isLoad = false) {\n\
- if (!value || isNaN(value)) return '#999';\n\
- const num = parseFloat(value);\n\
- if (isLoad) {\n\
- if (num >= 80) return '#d9534f'; // Red for high load\n\
- if (num >= 60) return '#f0ad4e'; // Orange for medium load\n\
- return '#white'; // Green for low load\n\
- } else {\n\
- // For battery charge\n\
- if (num <= 20) return '#d9534f'; // Red for low charge\n\
- if (num <= 50) return '#f0ad4e'; // Orange for medium charge\n\
- return '#white'; // Green for good charge\n\
- }\n\
- }\n\
-\n\
- // Helper function to format runtime\n\
- function formatRuntime(seconds) {\n\
- if (!seconds || isNaN(seconds)) return 'N/A';\n\
- const mins = Math.floor(seconds / 60);\n\
- const secs = seconds % 60;\n\
- return \`\${mins}m \${secs}s\`;\n\
- }\n\
-\n\
- // Extract key UPS information\n\
- const batteryCharge = objValue['battery.charge'];\n\
- const batteryRuntime = objValue['battery.runtime'];\n\
- const inputVoltage = objValue['input.voltage'];\n\
- const upsLoad = objValue['ups.load'];\n\
- const upsStatus = objValue['ups.status'];\n\
- const upsModel = objValue['ups.model'] || objValue['device.model'];\n\
- const testResult = objValue['ups.test.result'];\n\
- const batteryChargeLow = objValue['battery.charge.low'];\n\
- const batteryRuntimeLow = objValue['battery.runtime.low'];\n\
- const upsRealPowerNominal = objValue['ups.realpower.nominal'];\n\
- const batteryMfrDate = objValue['battery.mfr.date'];\n\
-\n\
- // Build the status display\n\
- let displayItems = [];\n\
-\n\
- // First line: Model info\n\
- let modelLine = '';\n\
- if (upsModel) {\n\
- modelLine = \`\${upsModel}\`;\n\
- } else {\n\
- modelLine = \`N/A\`;\n\
- }\n\
- displayItems.push(modelLine);\n\
-\n\
- // Main status line with all metrics\n\
- let statusLine = '';\n\
-\n\
- // Status\n\
- if (upsStatus) {\n\
- const statusUpper = upsStatus.toUpperCase();\n\
- let statusText = 'Unknown';\n\
- let statusColor = '#f0ad4e';\n\
-\n\
- if (statusUpper.includes('OL')) {\n\
- statusText = 'Online';\n\
- statusColor = 'white'; // White for good status\n\
- } else if (statusUpper.includes('OB')) {\n\
- statusText = 'On Battery';\n\
- statusColor = '#d9534f'; // Red for on battery\n\
- } else if (statusUpper.includes('LB')) {\n\
- statusText = 'Low Battery';\n\
- statusColor = '#d9534f'; // Red for low battery\n\
- } else {\n\
- statusText = upsStatus;\n\
- statusColor = '#f0ad4e'; // Orange for unknown status\n\
- }\n\
-\n\
- statusLine += \`Status: \${statusText}\`;\n\
- } else {\n\
- statusLine += \`Status: N/A\`;\n\
- }\n\
-\n\
- // Battery charge\n\
- if (statusLine) statusLine += ' | ';\n\
- if (batteryCharge) {\n\
- const chargeColor = getPercentageColor(batteryCharge, false);\n\
- statusLine += \`Battery: \${batteryCharge}%\`;\n\
- } else {\n\
- statusLine += \`Battery: N/A\`;\n\
- }\n\
-\n\
- // Load percentage\n\
- if (statusLine) statusLine += ' | ';\n\
- if (upsLoad) {\n\
- const loadColor = getPercentageColor(upsLoad, true);\n\
- statusLine += \`Load: \${upsLoad}%\`;\n\
- } else {\n\
- statusLine += \`Load: N/A\`;\n\
- }\n\
-\n\
- // Runtime\n\
- if (statusLine) statusLine += ' | ';\n\
- if (batteryRuntime) {\n\
- const runtime = parseInt(batteryRuntime);\n\
- const runtimeLowThreshold = batteryRuntimeLow ? parseInt(batteryRuntimeLow) : 600;\n\
- let runtimeColor = 'white';\n\
- if (runtime <= runtimeLowThreshold / 2) runtimeColor = '#d9534f'; // Red if less than half of low threshold\n\
- else if (runtime <= runtimeLowThreshold) runtimeColor = '#f0ad4e'; // Orange if at low threshold\n\
-\n\
- statusLine += \`Runtime: \${formatRuntime(runtime)}\`;\n\
- } else {\n\
- statusLine += \`Runtime: N/A\`;\n\
- }\n\
-\n\
- // Input voltage\n\
- if (statusLine) statusLine += ' | ';\n\
- if (inputVoltage) {\n\
- statusLine += \`Input: \${parseFloat(inputVoltage).toFixed(0)}V\`;\n\
- } else {\n\
- statusLine += \`Input: N/A\`;\n\
- }\n\
-\n\
- // Calculate actual watt usage\n\
- if (statusLine) statusLine += ' | ';\n\
- let actualWattage = null;\n\
- if (upsLoad && upsRealPowerNominal) {\n\
- const load = parseFloat(upsLoad);\n\
- const nominal = parseFloat(upsRealPowerNominal);\n\
- if (!isNaN(load) && !isNaN(nominal)) {\n\
- actualWattage = Math.round((load / 100) * nominal);\n\
- }\n\
- }\n\
-\n\
- // Real power (calculated watt usage)\n\
- if (actualWattage !== null) {\n\
- statusLine += \`Output: \${actualWattage}W\`;\n\
- } else {\n\
- statusLine += \`Output: N/A\`;\n\
- }\n\
-\n\
- displayItems.push(statusLine);\n\
-\n\
- // Combined battery and test line\n\
- let batteryTestLine = '';\n\
- if (batteryMfrDate) {\n\
- batteryTestLine += \`Battery MFD: \${batteryMfrDate}\`;\n\
- } else {\n\
- batteryTestLine += \`Battery MFD: N/A\`;\n\
- }\n\
-\n\
- if (testResult && !testResult.toLowerCase().includes('no test')) {\n\
- const testColor = testResult.toLowerCase().includes('passed') ? 'white' : '#d9534f';\n\
- batteryTestLine += \` | Test: \${testResult}\`;\n\
- } else {\n\
- batteryTestLine += \` | Test: N/A\`;\n\
- }\n\
-\n\
- displayItems.push(batteryTestLine);\n\
-\n\
- // Format the final output\n\
- return '' + displayItems.join('
') + '
';\n\
- }\n\
- },
- }" "$PVE_MANAGER_LIB_JS_FILE"
- fi
-
if [ $ENABLE_RAM_TEMP = true ]; then
# Add Ram temperature display
sed -i "/^Ext.define('PVE.node.StatusView',/ {
@@ -1170,6 +950,21 @@ Ext.define('PVE.mod.TempHelper', {\n\
}" "$PVE_MANAGER_LIB_JS_FILE"
fi
+ if [ $ENABLE_UPS = true ]; then
+ local TEMP_JS_FILE="/tmp/ups_widget.js"
+ generate_ups_widget $TEMP_JS_FILE
+
+ sed -i "/^Ext.define('PVE.node.StatusView',/ {
+ :a
+ /items:/!{N;ba;}
+ :b
+ /'thermal.*},/!{N;bb;}
+ r /tmp/ups_widget.js
+ }" "$PVE_MANAGER_LIB_JS_FILE"
+
+ rm $TEMP_JS_FILE
+ fi
+
# Add an empty line to separate modified items as a visual group
# NOTE: Check for the presence of items in the reverse order of display
local lastItemId=""
@@ -1179,6 +974,8 @@ Ext.define('PVE.mod.TempHelper', {\n\
lastItemId="thermalNvme"
elif [ $ENABLE_FAN_SPEED = true ]; then
lastItemId="speedFan"
+ elif [ $ENABLE_UPS = true ]; then
+ lastItemId="UPS"
else
lastItemId="thermalCpu"
fi
@@ -1241,6 +1038,224 @@ Ext.define('PVE.mod.TempHelper', {\n\
fi
}
+generate_ups_widget() {
+ cat > "$1" <<-EOF
+ {
+ 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: 'upsc',
+ renderer: function(value) {
+ console.log(value);
+
+ let objValue = {};
+ try {
+ // Parse the UPS data
+ if (typeof value === 'string') {
+ const lines = value.split('\\n');
+ lines.forEach(line => {
+ const colonIndex = line.indexOf(':');
+ if (colonIndex > 0) {
+ const key = line.substring(0, colonIndex).trim();
+ const val = line.substring(colonIndex + 1).trim();
+ objValue[key] = val;
+ }
+ });
+ } 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 'white'; // White for online
+ if (statusUpper.includes('OB')) return '#d9534f'; // Red for on battery
+ if (statusUpper.includes('LB')) return '#d9534f'; // Red for low battery
+ return '#f0ad4e'; // Orange for other states
+ }
+
+ // 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'; // Red for high load
+ if (num >= 60) return '#f0ad4e'; // Orange for medium load
+ return 'white'; // White for low load
+ } else {
+ // For battery charge
+ if (num <= 20) return '#d9534f'; // Red for low charge
+ if (num <= 50) return '#f0ad4e'; // Orange for medium charge
+ return 'white'; // White for good charge
+ }
+ }
+
+ // 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\`;
+ }
+
+ // Extract key UPS information
+ const batteryCharge = objValue['battery.charge'];
+ const batteryRuntime = objValue['battery.runtime'];
+ const inputVoltage = objValue['input.voltage'];
+ const upsLoad = objValue['ups.load'];
+ const upsStatus = objValue['ups.status'];
+ const upsModel = objValue['ups.model'] || objValue['device.model'];
+ const testResult = objValue['ups.test.result'];
+ const batteryChargeLow = objValue['battery.charge.low'];
+ const batteryRuntimeLow = objValue['battery.runtime.low'];
+ const upsRealPowerNominal = objValue['ups.realpower.nominal'];
+ const batteryMfrDate = objValue['battery.mfr.date'];
+
+ // Build the status display
+ let displayItems = [];
+
+ // First line: Model info
+ let modelLine = '';
+ if (upsModel) {
+ modelLine = \`\${upsModel}\`;
+ } else {
+ modelLine = \`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 = 'white'; // White for good status
+ } else if (statusUpper.includes('OB')) {
+ statusText = 'On Battery';
+ statusColor = '#d9534f'; // Red for on battery
+ } else if (statusUpper.includes('LB')) {
+ statusText = 'Low Battery';
+ statusColor = '#d9534f'; // Red for low battery
+ } else {
+ statusText = upsStatus;
+ statusColor = '#f0ad4e'; // Orange for unknown status
+ }
+
+ statusLine += \`Status: \${statusText}\`;
+ } else {
+ statusLine += \`Status: N/A\`;
+ }
+
+ // Battery charge
+ if (statusLine) statusLine += ' | ';
+ if (batteryCharge) {
+ const chargeColor = getPercentageColor(batteryCharge, false);
+ statusLine += \`Battery: \${batteryCharge}%\`;
+ } else {
+ statusLine += \`Battery: N/A\`;
+ }
+
+ // Load percentage
+ if (statusLine) statusLine += ' | ';
+ if (upsLoad) {
+ const loadColor = getPercentageColor(upsLoad, true);
+ 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 = 'white';
+ if (runtime <= runtimeLowThreshold / 2) runtimeColor = '#d9534f'; // Red if less than half of low threshold
+ else if (runtime <= runtimeLowThreshold) runtimeColor = '#f0ad4e'; // Orange if at low threshold
+
+ 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') ? 'white' : '#d9534f';
+ batteryTestLine += \` | Test: \${testResult}\`;
+ } else {
+ batteryTestLine += \` | Test: N/A\`;
+ }
+
+ displayItems.push(batteryTestLine);
+
+ // Format the final output
+ return '' + displayItems.join('
') + '
';
+ }
+ },
+ EOF
+
+ if [[ $? -ne 0 ]]; then
+ echo "Error: Failed to generate UPS widget code" >&2
+ exit 1
+ fi
+}
+
# Function to uninstall the modification
function uninstall_mod {
check_root_privileges