From 0eabae4f40bd14b67a7fad68396fbffa29a5ad9a Mon Sep 17 00:00:00 2001 From: Meliox Date: Sat, 6 Sep 2025 21:26:01 +0200 Subject: [PATCH] fix env export --- pve-mod-gui-sensors.sh | 899 +++++++++++++++++++++-------------------- 1 file changed, 457 insertions(+), 442 deletions(-) diff --git a/pve-mod-gui-sensors.sh b/pve-mod-gui-sensors.sh index 198f1d2..ca1de49 100644 --- a/pve-mod-gui-sensors.sh +++ b/pve-mod-gui-sensors.sh @@ -394,31 +394,42 @@ function install_mod { # Expand space in StatusView expand_statusview_space + read -p "Press [Enter] key to continue..." + # Insert temp helper generate_and_insert_temp_helper + read -p "Press [Enter] key to continue..." + # Generate and insert widgets using the helper function generate_and_insert_widget "$ENABLE_SYSTEM_INFO" "generate_system_info" "system_info" + + # pause wait for user to press enter + read -p "Press [Enter] key to continue..." + ENABLE_CPU=true - generate_and_insert_widget "$ENABLE_CPU" "generate_cpu_widget" "cpu" # # NOTE: The following items will be added in reverse order # generate_and_insert_widget "$ENABLE_UPS" "generate_ups_widget" "ups" + read -p "Press [Enter] key to continue..." generate_and_insert_widget "$ENABLE_HDD_TEMP" "generate_hdd_widget" "hdd" + read -p "Press [Enter] key to continue..." generate_and_insert_widget "$ENABLE_NVME_TEMP" "generate_nvme_widget" "nvme" + read -p "Press [Enter] key to continue..." # Add drive header boxes if either nvme or drive temp is enabled generate_drive_header generate_and_insert_widget "$ENABLE_FAN_SPEED" "generate_fan_widget" "fan" generate_and_insert_widget "$ENABLE_RAM_TEMP" "generate_ram_widget" "ram" + generate_and_insert_widget "$ENABLE_CPU" "generate_cpu_widget" "cpu" # Add an empty line to separate modified items as a visual group - add_visual_separator + #add_visual_separator # Move the node summary box into its own container and deactivate the original box instance - setup_node_summary_container + #setup_node_summary_container msg "Sensor display items added to the summary panel in \"$PVE_MANAGER_LIB_JS_FILE\"." @@ -441,8 +452,8 @@ insert_widget_after_thermal() { :a /items:/!{N;ba;} :b - /'thermal.*},/!{N;bb;} - cat $widget_file + /'cpus.*},/!{N;bb;} + r $widget_file }" "$PVE_MANAGER_LIB_JS_FILE" } @@ -491,7 +502,7 @@ expand_statusview_space() { s/height: [0-9]\+/minHeight: 360,\n\tflex: 1,\n\tcollapsible: true,\n\ttitleCollapse: true/ s/\(tableAttrs:.*$\)/trAttrs: \{ valign: 'top' \},\n\t\1/ }" "$PVE_MANAGER_LIB_JS_FILE" - + if [[ $? -ne 0 ]]; then echo "Error: Failed to expand StatusView space" >&2 exit 1 @@ -601,16 +612,16 @@ EOF generate_system_info() { #region system info heredoc cat > "$1" <<'EOF' - { - itemId: 'sysinfo', - colspan: 2, - printBar: false, - title: gettext('System Information'), - textField: 'systemInfo', - renderer: function(value){ - return value; - } - }, + { + itemId: 'sysinfo', + colspan: 2, + printBar: false, + title: gettext('System Information'), + textField: 'systemInfo', + renderer: function(value){ + return value; + } + }, EOF #endregion system info heredoc if [[ $? -ne 0 ]]; then @@ -731,7 +742,7 @@ generate_cpu_widget() { export CPU_TEMP_TARGET export HELPERCTORPARAMS - cat <<'EOF' | envsubst > "$1" + cat <<'EOF' | envsubst '$CPU_ITEMS_PER_ROW $CPU_TEMP_TARGET $HELPERCTORPARAMS' > "$1" { itemId: 'thermalCpu', colspan: 2, @@ -859,60 +870,61 @@ generate_nvme_widget() { #region nvme widget heredoc # use subshell to allow variable expansion ( - export HELPERCTORPARAMS - cat <<'EOF' | envsubst > "$1" - { - itemId: 'thermalNvme', - colspan: 2, - printBar: false, - title: gettext('NVMe Thermal State'), - iconCls: 'fa fa-fw fa-thermometer-half', - textField: 'sensorsOutput', - renderer: function(value) { - // sensors configuration - const addressPrefix = "nvme-pci-"; - const sensorName = "Composite"; - const tempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS); - // display configuration - const itemsPerRow = ${NVME_ITEMS_PER_ROW}; - // --- - let objValue; - try { - objValue = JSON.parse(value) || {}; - } catch(e) { - objValue = {}; - } - const nvmeKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort(); - let temps = []; - nvmeKeys.forEach((nvmeKey, index) => { + export HELPERCTORPARAMS + export NVME_ITEMS_PER_ROW + cat <<'EOF' | envsubst '$HELPERCTORPARAMS $NVME_ITEMS_PER_ROW' > "$1" + { + itemId: 'thermalNvme', + colspan: 2, + printBar: false, + title: gettext('NVMe Thermal State'), + iconCls: 'fa fa-fw fa-thermometer-half', + textField: 'sensorsOutput', + renderer: function(value) { + // sensors configuration + const addressPrefix = "nvme-pci-"; + const sensorName = "Composite"; + const tempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS); + // display configuration + const itemsPerRow = $NVME_ITEMS_PER_ROW; + // --- + let objValue; try { - let tempVal = NaN, tempMax = NaN, tempCrit = NaN; - Object.keys(objValue[nvmeKey][sensorName]).forEach((secondLevelKey) => { - if (secondLevelKey.endsWith('_input')) { - tempVal = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); - } else if (secondLevelKey.endsWith('_max')) { - tempMax = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); - } else if (secondLevelKey.endsWith('_crit')) { - tempCrit = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); + objValue = JSON.parse(value) || {}; + } catch(e) { + objValue = {}; + } + const nvmeKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort(); + let temps = []; + nvmeKeys.forEach((nvmeKey, index) => { + try { + let tempVal = NaN, tempMax = NaN, tempCrit = NaN; + Object.keys(objValue[nvmeKey][sensorName]).forEach((secondLevelKey) => { + if (secondLevelKey.endsWith('_input')) { + tempVal = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); + } else if (secondLevelKey.endsWith('_max')) { + tempMax = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); + } else if (secondLevelKey.endsWith('_crit')) { + tempCrit = tempHelper.getTemp(parseFloat(objValue[nvmeKey][sensorName][secondLevelKey])); + } + }); + if (!isNaN(tempVal)) { + let tempStyle = ''; + if (!isNaN(tempMax) && tempVal >= tempMax) { + tempStyle = 'color: #FFC300; font-weight: bold;'; + } + if (!isNaN(tempCrit) && tempVal >= tempCrit) { + tempStyle = 'color: red; font-weight: bold;'; + } + const tempStr = `Drive ${index + 1}: ${Ext.util.Format.number(tempVal, '0.0')}${tempHelper.getUnit()}`; + temps.push(tempStr); } - }); - if (!isNaN(tempVal)) { - let tempStyle = ''; - if (!isNaN(tempMax) && tempVal >= tempMax) { - tempStyle = 'color: #FFC300; font-weight: bold;'; - } - if (!isNaN(tempCrit) && tempVal >= tempCrit) { - tempStyle = 'color: red; font-weight: bold;'; - } - const tempStr = `Drive ${index + 1}: ${Ext.util.Format.number(tempVal, '0.0')}${tempHelper.getUnit()}`; - temps.push(tempStr); - } - } 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') + '
'; - } - }, + } 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') + '
'; + } + }, EOF ) #endregion nvme widget heredoc @@ -925,70 +937,74 @@ EOF # Function to generate UPS widget generate_fan_widget() { #region fan widget heredoc - cat > "$1" <<'EOF' - { - xtype: 'box', - colspan: 2, - html: gettext('Cooling'), - }, - { - itemId: 'speedFan', - colspan: 2, - printBar: false, - title: gettext('Fan Speed(s)'), - iconCls: 'fa fa-fw fa-snowflake-o', - textField: 'sensorsOutput', - renderer: function(value) { - // --- - let objValue; - try { - objValue = JSON.parse(value) || {}; - } catch(e) { - objValue = {}; - } - - // Recursive function to find fan keys and values - function findFanKeys(obj, fanKeys, parentKey = null) { - Object.keys(obj).forEach(key => { - const value = obj[key]; - if (typeof value === 'object' && value !== null) { - // If the value is an object, recursively call the function - findFanKeys(value, fanKeys, key); - } else if (/^fan[0-9]+(_input)?$/.test(key)) { - if ($DISPLAY_ZERO_SPEED_FANS != true && value === 0) { - // Skip this fan if DISPLAY_ZERO_SPEED_FANS is false and value is 0 - return; + # use subshell to allow variable expansion + ( + export DISPLAY_ZERO_SPEED_FANS + cat <<'EOF' | envsubst '$DISPLAY_ZERO_SPEED_FANS' > "$1" + { + xtype: 'box', + colspan: 2, + html: gettext('Cooling'), + }, + { + itemId: 'speedFan', + colspan: 2, + printBar: false, + title: gettext('Fan Speed(s)'), + iconCls: 'fa fa-fw fa-snowflake-o', + textField: 'sensorsOutput', + renderer: function(value) { + // --- + let objValue; + try { + objValue = JSON.parse(value) || {}; + } catch(e) { + objValue = {}; } - // If the key matches the pattern, add the parent key and value to the fanKeys array - fanKeys.push({ key: parentKey, value: value }); - } - }); - } - let speeds = []; - // Loop through the parent keys - Object.keys(objValue).forEach(parentKey => { - const parentObj = objValue[parentKey]; - // Array to store fan keys and values - const fanKeys = []; - // Call the recursive function to find fan keys and values - findFanKeys(parentObj, fanKeys); - // Sort the fan keys - fanKeys.sort(); - // Process each fan key and value - fanKeys.forEach(({ key: fanKey, value: fanSpeed }) => { - try { - const fan = fanKey.charAt(0).toUpperCase() + fanKey.slice(1); // Capitalize the first letter of fanKey - speeds.push(`${fan}: ${fanSpeed} RPM`); - } catch(e) { - console.error(`Error retrieving fan speed for ${fanKey} in ${parentKey}:`, e); // Debug: Log specific error + // Recursive function to find fan keys and values + function findFanKeys(obj, fanKeys, parentKey = null) { + Object.keys(obj).forEach(key => { + const value = obj[key]; + if (typeof value === 'object' && value !== null) { + // If the value is an object, recursively call the function + findFanKeys(value, fanKeys, key); + } else if (/^fan[0-9]+(_input)?$/.test(key)) { + if ($DISPLAY_ZERO_SPEED_FANS != true && value === 0) { + // Skip this fan if DISPLAY_ZERO_SPEED_FANS is false and value is 0 + return; + } + // If the key matches the pattern, add the parent key and value to the fanKeys array + fanKeys.push({ key: parentKey, value: value }); + } + }); + } + + let speeds = []; + // Loop through the parent keys + Object.keys(objValue).forEach(parentKey => { + const parentObj = objValue[parentKey]; + // Array to store fan keys and values + const fanKeys = []; + // Call the recursive function to find fan keys and values + findFanKeys(parentObj, fanKeys); + // Sort the fan keys + fanKeys.sort(); + // Process each fan key and value + fanKeys.forEach(({ key: fanKey, value: fanSpeed }) => { + try { + const fan = fanKey.charAt(0).toUpperCase() + fanKey.slice(1); // Capitalize the first letter of fanKey + speeds.push(`${fan}: ${fanSpeed} RPM`); + } catch(e) { + console.error(`Error retrieving fan speed for ${fanKey} in ${parentKey}:`, e); // Debug: Log specific error + } + }); + }); + return '
' + (speeds.length > 0 ? speeds.join(' | ') : 'N/A') + '
'; } - }); - }); - return '
' + (speeds.length > 0 ? speeds.join(' | ') : 'N/A') + '
'; - } - }, + }, EOF + ) #endregion fan widget heredoc if [[ $? -ne 0 ]]; then echo "Error: Failed to generate fan widget code" >&2 @@ -1002,59 +1018,59 @@ generate_hdd_widget() { # use subshell to allow variable expansion ( export HELPERCTORPARAMS - cat <<'EOF' | envsubst > "$1" - { - itemId: 'thermalHdd', - colspan: 2, - printBar: false, - title: gettext('HDD/SSD Thermal State'), - iconCls: 'fa fa-fw fa-thermometer-half', - textField: 'sensorsOutput', - renderer: function(value) { - // sensors configuration - const addressPrefix = "drivetemp-scsi-"; - const sensorName = "temp1"; - const tempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS); - // display configuration - const itemsPerRow = ${HDD_ITEMS_PER_ROW}; - // --- - let objValue; - try { - objValue = JSON.parse(value) || {}; - } catch(e) { - objValue = {}; - } - const drvKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort(); - let temps = []; - drvKeys.forEach((drvKey, index) => { + cat <<'EOF' | envsubst '$HELPERCTORPARAMS' > "$1" + { + itemId: 'thermalHdd', + colspan: 2, + printBar: false, + title: gettext('HDD/SSD Thermal State'), + iconCls: 'fa fa-fw fa-thermometer-half', + textField: 'sensorsOutput', + renderer: function(value) { + // sensors configuration + const addressPrefix = "drivetemp-scsi-"; + const sensorName = "temp1"; + const tempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS); + // display configuration + const itemsPerRow = ${HDD_ITEMS_PER_ROW}; + // --- + let objValue; try { - let tempVal = NaN, tempMax = NaN, tempCrit = NaN; - Object.keys(objValue[drvKey][sensorName]).forEach((secondLevelKey) => { - if (secondLevelKey.endsWith('_input')) { - tempVal = tempHelper.getTemp(parseFloat(objValue[drvKey][sensorName][secondLevelKey])); - } else if (secondLevelKey.endsWith('_max')) { - tempMax = tempHelper.getTemp(parseFloat(objValue[drvKey][sensorName][secondLevelKey])); - } else if (secondLevelKey.endsWith('_crit')) { - tempCrit = tempHelper.getTemp(parseFloat(objValue[drvKey][sensorName][secondLevelKey])); + objValue = JSON.parse(value) || {}; + } catch(e) { + objValue = {}; + } + const drvKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort(); + let temps = []; + drvKeys.forEach((drvKey, index) => { + try { + let tempVal = NaN, tempMax = NaN, tempCrit = NaN; + Object.keys(objValue[drvKey][sensorName]).forEach((secondLevelKey) => { + if (secondLevelKey.endsWith('_input')) { + tempVal = tempHelper.getTemp(parseFloat(objValue[drvKey][sensorName][secondLevelKey])); + } else if (secondLevelKey.endsWith('_max')) { + tempMax = tempHelper.getTemp(parseFloat(objValue[drvKey][sensorName][secondLevelKey])); + } else if (secondLevelKey.endsWith('_crit')) { + tempCrit = tempHelper.getTemp(parseFloat(objValue[drvKey][sensorName][secondLevelKey])); + } + }); + if (!isNaN(tempVal)) { + let tempStyle = ''; + if (!isNaN(tempMax) && tempVal >= tempMax) { + tempStyle = 'color: #FFC300; font-weight: bold;'; + } + if (!isNaN(tempCrit) && tempVal >= tempCrit) { + tempStyle = 'color: red; font-weight: bold;'; + } + const tempStr = `Drive ${index + 1}: ${Ext.util.Format.number(tempVal, '0.0')}${tempHelper.getUnit()}`; + temps.push(tempStr); } - }); - if (!isNaN(tempVal)) { - let tempStyle = ''; - if (!isNaN(tempMax) && tempVal >= tempMax) { - tempStyle = 'color: #FFC300; font-weight: bold;'; - } - if (!isNaN(tempCrit) && tempVal >= tempCrit) { - tempStyle = 'color: red; font-weight: bold;'; - } - const tempStr = `Drive ${index + 1}: ${Ext.util.Format.number(tempVal, '0.0')}${tempHelper.getUnit()}`; - temps.push(tempStr); - } - } 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') + '
'; - } - }, + } 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') + '
'; + } + }, EOF ) #endregion hdd widget heredoc @@ -1070,85 +1086,84 @@ generate_ram_widget() { # use subshell to allow variable expansion ( export HELPERCTORPARAMS - cat <<'EOF' | envsubst > "$1" - { - { - xtype: 'box', - colspan: 2, - html: gettext('RAM'), - }, - { - itemId: 'thermalRam', - colspan: 2, - printBar: false, - title: gettext('Thermal State'), - iconCls: 'fa fa-fw fa-thermometer-half', - textField: 'sensorsOutput', - renderer: function(value) { - const cpuTempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS); - // Make SODIMM unique keys - value = value.split('\n'); // Split by newlines - for (let i = 0; i < value.length; i++) { - // Check if the current line contains 'SODIMM' - if (value[i].includes('SODIMM') && i + 1 < value.length) { - // Extract the number '3' following 'temp' from the next line (e.g., "temp3_input": 25.000) - let nextLine = value[i + 1]; - let match = nextLine.match(/"temp(\d+)_input": (\d+\.\d+)/); + cat <<'EOF' | envsubst '$HELPERCTORPARAMS' > "$1" + { + xtype: 'box', + colspan: 2, + html: gettext('RAM'), + }, + { + itemId: 'thermalRam', + colspan: 2, + printBar: false, + title: gettext('Thermal State'), + iconCls: 'fa fa-fw fa-thermometer-half', + textField: 'sensorsOutput', + renderer: function(value) { + const cpuTempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS); + // Make SODIMM unique keys + value = value.split('\n'); // Split by newlines + for (let i = 0; i < value.length; i++) { + // Check if the current line contains 'SODIMM' + if (value[i].includes('SODIMM') && i + 1 < value.length) { + // Extract the number '3' following 'temp' from the next line (e.g., "temp3_input": 25.000) + let nextLine = value[i + 1]; + let match = nextLine.match(/"temp(\d+)_input": (\d+\.\d+)/); - if (match) { - let number = match[1]; // Extracted number - // Replace the current line with SODIMM by the extracted number - value[i] = value[i].replace('SODIMM', `SODIMM${number}`); + if (match) { + let number = match[1]; // Extracted number + // Replace the current line with SODIMM by the extracted number + value[i] = value[i].replace('SODIMM', `SODIMM${number}`); + } } } - } - value = value.join('\n'); // Reverse line split + value = value.join('\n'); // Reverse line split - let objValue; - try { - objValue = JSON.parse(value) || {}; - } catch(e) { - objValue = {}; - } - - // Recursive function to find ram keys and values - function findRamKeys(obj, ramKeys, parentKey = null) { - Object.keys(obj).forEach(key => { - const value = obj[key]; - if (typeof value === 'object' && value !== null) { - // If the value is an object, recursively call the function - findRamKeys(value, ramKeys, key); - } else if (/^temp\d+_input$/.test(key) && parentKey && parentKey.startsWith("SODIMM")) { - if (value !== 0) { - ramKeys.push({ key: parentKey, value: value}); - } - } - }); - } - - let ramTemps = []; - // Loop through the parent keys - Object.keys(objValue).forEach(parentKey => { - const parentObj = objValue[parentKey]; - // Array to store ram keys and values - const ramKeys = []; - // Call the recursive function to find ram keys and values - findRamKeys(parentObj, ramKeys); - // Sort the ramKeys keys - ramKeys.sort(); - // Process each ram key and value - ramKeys.forEach(({ key: ramKey, value: ramTemp }) => { + let objValue; try { - ram = ramKey.replace('SODIMM', 'SODIMM '); - ramTemps.push(`${ram}: ${ramTemp}${cpuTempHelper.getUnit()}`); + objValue = JSON.parse(value) || {}; } catch(e) { - console.error(`Error retrieving Ram Temp for ${ramTemps} in ${parentKey}:`, e); // Debug: Log specific error + objValue = {}; } + + // Recursive function to find ram keys and values + function findRamKeys(obj, ramKeys, parentKey = null) { + Object.keys(obj).forEach(key => { + const value = obj[key]; + if (typeof value === 'object' && value !== null) { + // If the value is an object, recursively call the function + findRamKeys(value, ramKeys, key); + } else if (/^temp\d+_input$/.test(key) && parentKey && parentKey.startsWith("SODIMM")) { + if (value !== 0) { + ramKeys.push({ key: parentKey, value: value}); + } + } + }); + } + + let ramTemps = []; + // Loop through the parent keys + Object.keys(objValue).forEach(parentKey => { + const parentObj = objValue[parentKey]; + // Array to store ram keys and values + const ramKeys = []; + // Call the recursive function to find ram keys and values + findRamKeys(parentObj, ramKeys); + // Sort the ramKeys keys + ramKeys.sort(); + // Process each ram key and value + ramKeys.forEach(({ key: ramKey, value: ramTemp }) => { + try { + ram = ramKey.replace('SODIMM', 'SODIMM '); + ramTemps.push(`${ram}: ${ramTemp}${cpuTempHelper.getUnit()}`); + } catch(e) { + console.error(`Error retrieving Ram Temp for ${ramTemps} in ${parentKey}:`, e); // Debug: Log specific error + } + }); }); - }); - return '
' + (ramTemps.length > 0 ? ramTemps.join(' | ') : 'N/A') + '
'; - } - }, + return '
' + (ramTemps.length > 0 ? ramTemps.join(' | ') : 'N/A') + '
'; + } + }, EOF ) #endregion ram widget heredoc @@ -1162,212 +1177,212 @@ EOF generate_ups_widget() { #region UPS widget heredoc 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) { - 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 || {}; + { + 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) { + 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 = {}; } - } catch(e) { - objValue = {}; - } - // If objValue is null or empty, return N/A - if (!objValue || Object.keys(objValue).length === 0) { - return '
N/A
'; - } + // 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 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 + // 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 { - // 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 + modelLine = `N/A`; } - } + displayItems.push(modelLine); - // 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`; - } + // Main status line with all metrics + let statusLine = ''; - // 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']; + // Status + if (upsStatus) { + const statusUpper = upsStatus.toUpperCase(); + let statusText = 'Unknown'; + let statusColor = '#f0ad4e'; - // Build the status display - let displayItems = []; + 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 + } - // 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 + statusLine += `Status: ${statusText}`; } else { - statusText = upsStatus; - statusColor = '#f0ad4e'; // Orange for unknown status + statusLine += `Status: N/A`; } - 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); + // 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('
') + '
'; } - - // 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 #endregion UPS widget heredoc if [[ $? -ne 0 ]]; then