diff --git a/pve-mod-gui-sensors.sh b/pve-mod-gui-sensors.sh
index c28345e..2daef36 100644
--- a/pve-mod-gui-sensors.sh
+++ b/pve-mod-gui-sensors.sh
@@ -38,8 +38,11 @@ JSON_EXPORT_FILENAME="sensorsdata.json"
# File paths
PVE_MANAGER_LIB_JS_FILE="/usr/share/pve-manager/js/pvemanagerlib.js"
+PVE_MOD_JS_SOURCE_FILE="$SCRIPT_CWD/PveMod_PveNodeStatusView.js"
+PVE_MOD_JS_TARGET_FILE="/usr/share/pve-manager/js/PveMod_PveNodeStatusView.js"
NODES_PM_FILE="/usr/share/perl5/PVE/API2/Nodes.pm"
PVE_SENSOR_INFO_MOD_FILE="/usr/share/perl5/PVE/API2/PveMod_SensorInfo.pm"
+PVE_SENSOR_INFO_SOURCE_FILE="$SCRIPT_CWD/PveMod_SensorInfo.pm"
#region message tools
# Section header (bold)
@@ -480,46 +483,9 @@ function install_mod {
install_sensor_monitor_module
insert_sensor_monitor_into_pve
- exit
-
- #### Temperature helper parameters ####
- msgb "\n=== Creating temperature conversion helper ==="
- HELPERCTORPARAMS=$([[ "$TEMP_UNIT" = "F" ]] && \
- echo '{srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: PVE.mod.TempHelper.FAHRENHEIT}' || \
- echo '{srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: PVE.mod.TempHelper.CELSIUS}')
- info "Temperature helper configured for $TEMP_UNIT."
-
- #### Expand StatusView space ####
- expand_statusview_space
-
- #### Insert temperature helper ####
- generate_and_insert_temp_helper
-
- #### Generate and insert widgets ####
- msgb "\n=== Making visual adjustments ==="
-
- generate_and_insert_widget "$ENABLE_SYSTEM_INFO" "generate_system_info" "system_info"
- generate_and_insert_widget "$ENABLE_UPS" "generate_ups_widget" "ups"
- generate_and_insert_widget "$ENABLE_GPU_INFO" "generate_gpu_widget" "gpu"
- generate_and_insert_widget "$ENABLE_HDD_TEMP" "generate_hdd_widget" "hdd"
- generate_and_insert_widget "$ENABLE_NVME_TEMP" "generate_nvme_widget" "nvme"
-
- if [[ "$ENABLE_HDD_TEMP" = true || "$ENABLE_NVME_TEMP" = true ]]; then
- generate_drive_header
- info "Drive headers added."
- fi
-
- 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"
-
- #### Visual separation ####
- add_visual_separator
- info "Added visual separator for modified items."
-
- #### Node summary ####
- setup_node_summary_container
- info "Node summary box moved into its own container."
+ #### Install UI modification module ####
+ msgb "\n=== Installing UI modification module ==="
+ install_node_status_view_module
msgb "\n=== Finalizing installation ==="
@@ -560,14 +526,13 @@ sanitize_sensors_output() {
# Install and configure the Sensor Monitor Perl module
install_sensor_monitor_module() {
# Check if source file exists
- if [[ ! -f "$GPU_MONITOR_SOURCE_FILE" ]]; then
- err "Source file not found: $GPU_MONITOR_SOURCE_FILE"
+ if [[ ! -f "$PVE_SENSOR_INFO_SOURCE_FILE" ]]; then
+ err "Source file not found: $PVE_SENSOR_INFO_SOURCE_FILE"
fi
# Copy the module file
- # todo - how to install module??!?!
- #cp "$GPU_MONITOR_SOURCE_FILE" "$PVE_SENSOR_INFO_MOD_FILE" || err "Failed to copy $GPU_MONITOR_SOURCE_FILE to $PVE_SENSOR_INFO_MOD_FILE"
- info "Copied GPU Monitor module to $PVE_SENSOR_INFO_MOD_FILE"
+ cp "$PVE_SENSOR_INFO_SOURCE_FILE" "$PVE_SENSOR_INFO_MOD_FILE" || err "Failed to copy $PVE_SENSOR_INFO_SOURCE_FILE to $PVE_SENSOR_INFO_MOD_FILE"
+ info "Copied Sensor Monitor module to $PVE_SENSOR_INFO_MOD_FILE"
# Convert boolean flags to Perl format (1 or 0)
local intel_enabled=$([[ "$ENABLE_INTEL_GPU_INFO" = true ]] && echo 1 || echo 0)
@@ -606,10 +571,10 @@ install_sensor_monitor_module() {
insert_sensor_monitor_into_pve() {
#region PveSensorInfoMod heredoc
sed -i '/my \$dinfo = df('\''\/'\'', 1);/i\
- # Collect sensor data from PveSensorInfoMod\
+ # Collect sensor data from PveMod_SensorInfo\
# Bad practice to add use here, but cleaner implementation would require several extensive modifications.\
- use PVE::API2::GPUMonitor;\
- $res->{sensorsJSONOutput} = PVE::API2::GPUMonitor::get_sensors_stats();\
+ use PVE::API2::PveMod_SensorInfo;\
+ $res->{sensorsJSONOutput} = PVE::API2::PveMod_SensorInfo::get_sensors_stats();\
' "$NODES_PM_FILE"
#endregion PveSensorInfoMod heredoc
info "Sensor data retriever added to \"$NODES_PM_FILE\"."
@@ -641,1072 +606,47 @@ collect_system_info() {
}
#endregion node info insertion
-#region widget generation functions
-# Helper function to insert widget after thermal items
-insert_widget_after_thermal() {
- local widget_file="$1"
- sed -i "/^Ext.define('PVE.node.StatusView',/ {
- :a
- /items:/!{N;ba;}
- :b
- /'cpus.*},/!{N;bb;}
- r $widget_file
- }" "$PVE_MANAGER_LIB_JS_FILE"
-}
-
-# Helper function to generate widget and insert it
-generate_and_insert_widget() {
- local enable_flag="$1"
- local generator_func="$2"
- local widget_name="$3"
-
- if [ "$enable_flag" = true ]; then
- local temp_js_file="/tmp/${widget_name}_widget.js"
- "$generator_func" "$temp_js_file"
- insert_widget_after_thermal "$temp_js_file"
- rm "$temp_js_file"
- info "Inserted $widget_name widget."
- fi
-}
-
-# Function to generate drive header
-generate_drive_header() {
- if [ "$ENABLE_NVME_TEMP" = true ] || [ "$ENABLE_HDD_TEMP" = true ]; then
- local temp_js_file="/tmp/drive_header.js"
- #region drive header heredoc
- cat > "$temp_js_file" <<'EOF'
- {
- xtype: 'box',
- colspan: 2,
- html: gettext('Drive(s)'),
- },
-EOF
-#endregion drive header heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate drive header code" >&2
- exit 1
- fi
-
- insert_widget_after_thermal "$temp_js_file"
- rm "$temp_js_file"
+#region UI Module Installation
+# Install the UI modification module
+install_node_status_view_module() {
+ # Check if source file exists
+ if [[ ! -f "$PVE_MOD_JS_SOURCE_FILE" ]]; then
+ err "Source file not found: $PVE_MOD_JS_SOURCE_FILE"
fi
-}
-# Function to expand space and modify StatusView properties
-expand_statusview_space() {
- msgb "\n=== Expanding StatusView space ==="
+ # Copy the JavaScript module to PVE manager directory
+ cp "$PVE_MOD_JS_SOURCE_FILE" "$PVE_MOD_JS_TARGET_FILE" || err "Failed to copy $PVE_MOD_JS_SOURCE_FILE to $PVE_MOD_JS_TARGET_FILE"
+ info "Copied UI module to $PVE_MOD_JS_TARGET_FILE"
- # Apply multiple modifications to the StatusView definition
- sed -i "/Ext.define('PVE\.node\.StatusView'/,/\},/ {
- s/\(bodyPadding:\) '[^']*'/\1 '20 15 20 15'/
- 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
- fi
-
- info "Expanded space in \"$PVE_MANAGER_LIB_JS_FILE\"."
-}
-
-# Function to move node summary into its own container
-setup_node_summary_container() {
- # Move the node summary box into its own container
- local temp_js_file="/tmp/summary_container.js"
- #region summary container heredoc
- cat > "$temp_js_file" <<'EOF'
-{
- xtype: 'container',
- itemId: 'summarycontainer',
- layout: 'column',
- minWidth: 700,
- defaults: {
- minHeight: 350,
- padding: 5,
- columnWidth: 1,
- },
- items: [
- nodeStatus,
- ]
-},
-EOF
-#endregion summary container heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate summary container code" >&2
- exit 1
- fi
-
- # Insert the new container after finding the nodeStatus and items pattern
- sed -i "/^\s*nodeStatus: nodeStatus,/ {
- :a
- /items: \[/ !{N;ba;}
- r $temp_js_file
- }" "$PVE_MANAGER_LIB_JS_FILE"
-
- rm "$temp_js_file"
-
- # Deactivate the original box instance
- sed -i "/^\s*nodeStatus: nodeStatus,/ {
- :a
- /itemId: 'itemcontainer',/ !{N;ba;}
- n;
- :b
- /nodeStatus,/ !{N;bb;}
- s/nodeStatus/\/\/nodeStatus/
- }" "$PVE_MANAGER_LIB_JS_FILE"
-
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to deactivate original nodeStatus instance" >&2
- exit 1
- fi
-}
-
-# Function to add visual spacing separator after the last widget
-add_visual_separator() {
- # Check for the presence of items in the reverse order of display
- local lastItemId=""
-
- if [ "$ENABLE_UPS" = true ]; then
- lastItemId="upsc"
- elif [ "$ENABLE_GPU" = true ]; then
- lastItemId="gpuInfo"
- elif [ "$ENABLE_HDD_TEMP" = true ]; then
- lastItemId="thermalHdd"
- elif [ "$ENABLE_NVME_TEMP" = true ]; then
- lastItemId="thermalNvme"
- elif [ "$ENABLE_FAN_SPEED" = true ]; then
- lastItemId="speedFan"
+ # Comment out the original PVE.node.StatusView definition in pvemanagerlib.js
+ # This allows our custom module to provide the new definition
+ if grep -q "^Ext.define('PVE.node.StatusView'," "$PVE_MANAGER_LIB_JS_FILE" 2>/dev/null; then
+ # Find the start of the definition and comment it out until the matching closing brace
+ sed -i "/^Ext\.define('PVE\.node\.StatusView',/,/^});/s|^|// |" "$PVE_MANAGER_LIB_JS_FILE"
+ info "Commented out original StatusView definition in pvemanagerlib.js"
else
- lastItemId="thermalCpu"
+ warn "Original StatusView definition not found in expected format in pvemanagerlib.js"
fi
- if [ -n "$lastItemId" ]; then
- local temp_js_file="/tmp/visual_separator.js"
-
- #region visual spacing heredoc
- cat > "$temp_js_file" <<'EOF'
- {
- xtype: 'box',
- colspan: 2,
- padding: '0 0 20 0',
- },
-EOF
-#endregion visual spacing heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate visual separator code" >&2
- exit 1
- fi
-
- # Insert after the specific lastItemId (different pattern than thermal)
- sed -i "/^Ext.define('PVE.node.StatusView',/ {
- :a;
- /^.*{.*'$lastItemId'.*},/!{N;ba;}
- r $temp_js_file
- }" "$PVE_MANAGER_LIB_JS_FILE"
-
- rm "$temp_js_file"
+ # Add a dynamic script loader to load our custom module
+ # Insert before the commented-out Ext.define so it loads early
+ if ! grep -q "PveMod_PveNodeStatusView.js" "$PVE_MANAGER_LIB_JS_FILE" 2>/dev/null; then
+ # Use ExtJS Loader to dynamically load our custom module
+ sed -i "/^\/\/ Ext\.define('PVE\.node\.StatusView',/i\\
+// Load custom PVE.node.StatusView from external module\\
+Ext.Loader.loadScript({\\
+ url: '/pve2/js/PveMod_PveNodeStatusView.js',\\
+ onLoad: function() { console.log('Loaded PveMod_PveNodeStatusView.js'); },\\
+ onError: function() { console.error('Failed to load PveMod_PveNodeStatusView.js'); }\\
+});\\
+" "$PVE_MANAGER_LIB_JS_FILE"
+ info "Added dynamic loader for custom UI module in pvemanagerlib.js"
+ else
+ info "Custom UI module loader already present in pvemanagerlib.js"
fi
}
+#endregion UI Module Installation
-# Function to generate system info widget
-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;
- }
- },
-EOF
- #endregion system info heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate system info code" >&2
- exit 1
- fi
-}
-
-# Function to generate and insert temperature conversion helper class
-generate_and_insert_temp_helper() {
- local temp_js_file="/tmp/temp_helper.js"
-
- msgb "\n=== Inserting temperature helper ==="
-
- #region temp helper heredoc
- cat > "$temp_js_file" <<'EOF'
-Ext.define('PVE.mod.TempHelper', {
- //singleton: true,
-
- requires: ['Ext.util.Format'],
-
- statics: {
- CELSIUS: 0,
- FAHRENHEIT: 1
- },
-
- srcUnit: null,
- dstUnit: null,
-
- isValidUnit: function (unit) {
- return (
- Ext.isNumber(unit) && (unit === this.self.CELSIUS || unit === this.self.FAHRENHEIT)
- );
- },
-
- constructor: function (config) {
- this.srcUnit = config && this.isValidUnit(config.srcUnit) ? config.srcUnit : this.self.CELSIUS;
- this.dstUnit = config && this.isValidUnit(config.dstUnit) ? config.dstUnit : this.self.CELSIUS;
- },
-
- toFahrenheit: function (tempCelsius) {
- return Ext.isNumber(tempCelsius)
- ? tempCelsius * 9 / 5 + 32
- : NaN;
- },
-
- toCelsius: function (tempFahrenheit) {
- return Ext.isNumber(tempFahrenheit)
- ? (tempFahrenheit - 32) * 5 / 9
- : NaN;
- },
-
- getTemp: function (value) {
- if (this.srcUnit !== this.dstUnit) {
- switch (this.srcUnit) {
- case this.self.CELSIUS:
- switch (this.dstUnit) {
- case this.self.FAHRENHEIT:
- return this.toFahrenheit(value);
-
- default:
- Ext.raise({
- msg:
- 'Unsupported destination temperature unit: ' + this.dstUnit,
- });
- }
- case this.self.FAHRENHEIT:
- switch (this.dstUnit) {
- case this.self.CELSIUS:
- return this.toCelsius(value);
-
- default:
- Ext.raise({
- msg:
- 'Unsupported destination temperature unit: ' + this.dstUnit,
- });
- }
- default:
- Ext.raise({
- msg: 'Unsupported source temperature unit: ' + this.srcUnit,
- });
- }
- } else {
- return value;
- }
- },
-
- getUnit: function(plainText) {
- switch (this.dstUnit) {
- case this.self.CELSIUS:
- return plainText !== true ? '°C' : '\'C';
-
- case this.self.FAHRENHEIT:
- return plainText !== true ? '°F' : '\'F';
-
- default:
- Ext.raise({
- msg: 'Unsupported destination temperature unit: ' + this.srcUnit,
- });
- }
- },
-});
-EOF
- #endregion temp helper heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate temp helper code" >&2
- exit 1
- fi
-
- sed -i "/^Ext.define('PVE.node.StatusView'/e cat /tmp/temp_helper.js" "$PVE_MANAGER_LIB_JS_FILE"
- rm "$temp_js_file"
-
- info "Temperature helper inserted successfully."
-}
-
-# Function to generate CPU widget
-generate_cpu_widget() {
- #region cpu widget heredoc
- # use subshell to allow variable expansion
- (
- export CPU_ITEMS_PER_ROW
- export CPU_TEMP_TARGET
- export HELPERCTORPARAMS
-
- cat <<'EOF' | envsubst '$CPU_ITEMS_PER_ROW $CPU_TEMP_TARGET $HELPERCTORPARAMS' > "$1"
- {
- itemId: 'thermalCpu',
- colspan: 2,
- printBar: false,
- title: gettext('CPU Thermal State'),
- iconCls: 'fa fa-fw fa-thermometer-half',
- textField: 'sensorsOutput',
- renderer: function(value){
- // sensors configuration
- const cpuTempHelper = Ext.create('PVE.mod.TempHelper', $HELPERCTORPARAMS);
- // display configuration
- const itemsPerRow = $CPU_ITEMS_PER_ROW;
- // ---
- let objValue;
- try {
- objValue = JSON.parse(value) || {};
- } catch(e) {
- objValue = {};
- }
- const cpuKeysI = Object.keys(objValue).filter(item => String(item).startsWith('coretemp-isa-')).sort();
- const cpuKeysA = Object.keys(objValue).filter(item => String(item).startsWith('k10temp-pci-')).sort();
- const bINTEL = cpuKeysI.length > 0 ? true : false;
- const INTELPackagePrefix = '$CPU_TEMP_TARGET' == 'Core' ? 'Core ' : 'Package id';
- const INTELPackageCaption = '$CPU_TEMP_TARGET' == 'Core' ? 'Core' : 'Package';
- let AMDPackagePrefix = 'Tccd';
- let AMDPackageCaption = 'CCD';
-
- if (cpuKeysA.length > 0) {
- let bTccd = false;
- let bTctl = false;
- let bTdie = false;
- let bCpuCoreTemp = false;
- cpuKeysA.forEach((cpuKey, cpuIndex) => {
- let items = objValue[cpuKey];
- bTccd = Object.keys(items).findIndex(item => { return String(item).startsWith('Tccd'); }) >= 0;
- bTctl = Object.keys(items).findIndex(item => { return String(item).startsWith('Tctl'); }) >= 0;
- bTdie = Object.keys(items).findIndex(item => { return String(item).startsWith('Tdie'); }) >= 0;
- bCpuCoreTemp = Object.keys(items).findIndex(item => { return String(item) === 'CPU Core Temp'; }) >= 0;
- });
- if (bTccd && '$CPU_TEMP_TARGET' == 'Core') {
- AMDPackagePrefix = 'Tccd';
- AMDPackageCaption = 'ccd';
- } else if (bCpuCoreTemp && '$CPU_TEMP_TARGET' == 'Package') {
- AMDPackagePrefix = 'CPU Core Temp';
- AMDPackageCaption = 'CPU Core Temp';
- } else if (bTdie) {
- AMDPackagePrefix = 'Tdie';
- AMDPackageCaption = 'die';
- } else if (bTctl) {
- AMDPackagePrefix = 'Tctl';
- AMDPackageCaption = 'ctl';
- } else {
- AMDPackagePrefix = 'temp';
- AMDPackageCaption = 'Temp';
- }
- }
-
- const cpuKeys = bINTEL ? cpuKeysI : cpuKeysA;
- const cpuItemPrefix = bINTEL ? INTELPackagePrefix : AMDPackagePrefix;
- const cpuTempCaption = bINTEL ? INTELPackageCaption : AMDPackageCaption;
- const formatTemp = bINTEL ? '0' : '0.0';
- const cpuCount = cpuKeys.length;
- let temps = [];
-
- cpuKeys.forEach((cpuKey, cpuIndex) => {
- let cpuTemps = [];
- const items = objValue[cpuKey];
- const itemKeys = Object.keys(items).filter(item => {
- if ('$CPU_TEMP_TARGET' == 'Core') {
- // In Core mode: only show individual cores/CCDs, exclude overall CPU temp
- return String(item).includes(cpuItemPrefix) || String(item).startsWith('Tccd');
- } else {
- // In Package mode: show overall CPU temp and package-level readings
- return String(item).includes(cpuItemPrefix) || String(item) === 'CPU Core Temp';
- }
- });
-
- itemKeys.forEach((coreKey) => {
- try {
- let tempVal = NaN, tempMax = NaN, tempCrit = NaN;
- Object.keys(items[coreKey]).forEach((secondLevelKey) => {
- if (secondLevelKey.endsWith('_input')) {
- tempVal = cpuTempHelper.getTemp(parseFloat(items[coreKey][secondLevelKey]));
- } else if (secondLevelKey.endsWith('_max')) {
- tempMax = cpuTempHelper.getTemp(parseFloat(items[coreKey][secondLevelKey]));
- } else if (secondLevelKey.endsWith('_crit')) {
- tempCrit = cpuTempHelper.getTemp(parseFloat(items[coreKey][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;';
- }
-
- let tempStr = '';
-
- // Enhanced parsing for AMD temperatures
- if (coreKey.startsWith('Tccd')) {
- let tempIndex = coreKey.match(/Tccd(\d+)/);
- if (tempIndex !== null && tempIndex.length > 1) {
- tempIndex = tempIndex[1];
- tempStr = `${cpuTempCaption} ${tempIndex}: ${Ext.util.Format.number(tempVal, formatTemp)}${cpuTempHelper.getUnit()}`;
- } else {
- tempStr = `${cpuTempCaption}: ${Ext.util.Format.number(tempVal, formatTemp)}${cpuTempHelper.getUnit()}`;
- }
- }
- // Handle CPU Core Temp (single overall temperature)
- else if (coreKey === 'CPU Core Temp') {
- tempStr = `${cpuTempCaption}: ${Ext.util.Format.number(tempVal, formatTemp)}${cpuTempHelper.getUnit()}`;
- }
- // Enhanced parsing for Intel cores (P-Core, E-Core, regular Core)
- else {
- let tempIndex = coreKey.match(/(?:P\s+Core|E\s+Core|Core)\s*(\d+)/);
- if (tempIndex !== null && tempIndex.length > 1) {
- tempIndex = tempIndex[1];
- let coreType = coreKey.startsWith('P Core') ? 'P Core' :
- coreKey.startsWith('E Core') ? 'E Core' :
- cpuTempCaption;
- tempStr = `${coreType} ${tempIndex}: ${Ext.util.Format.number(tempVal, formatTemp)}${cpuTempHelper.getUnit()}`;
- } else {
- // fallback for CPUs which do not have a core index
- let coreType = coreKey.startsWith('P Core') ? 'P Core' :
- coreKey.startsWith('E Core') ? 'E Core' :
- cpuTempCaption;
- tempStr = `${coreType}: ${Ext.util.Format.number(tempVal, formatTemp)}${cpuTempHelper.getUnit()}`;
- }
- }
-
- cpuTemps.push(tempStr);
- }
- } catch (e) { /*_*/ }
- });
-
- if(cpuTemps.length > 0) {
- temps.push(cpuTemps);
- }
- });
-
- let result = '';
- temps.forEach((cpuTemps, cpuIndex) => {
- const strCoreTemps = cpuTemps.map((strTemp, index, arr) => {
- return strTemp + (index + 1 < arr.length ? (itemsPerRow > 0 && (index + 1) % itemsPerRow === 0 ? '
' : ' | ') : '');
- })
- if(strCoreTemps.length > 0) {
- result += (cpuCount > 1 ? `CPU ${cpuIndex+1}: ` : '') + strCoreTemps.join('') + (cpuIndex < cpuCount ? '
' : '');
- }
- });
-
- return '
' + (result.length > 0 ? result : 'N/A') + '
';
- }
- },
-EOF
- )
- #endregion cpu widget heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate cpu widget code" >&2
- exit 1
- fi
-}
-
-# Function to generate nvme widget
-generate_nvme_widget() {
- #region nvme widget heredoc
- # use subshell to allow variable expansion
- (
- 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 {
- 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);
- }
- } 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
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate nvme widget code" >&2
- exit 1
- fi
-}
-
-# Function to generate Fan widget
-generate_fan_widget() {
- #region fan widget heredoc
- # 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 = {};
- }
-
- // 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') + '
';
- }
- },
-EOF
- )
- #endregion fan widget heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate fan widget code" >&2
- exit 1
- fi
-}
-
-# Function to generate UPS widget
-generate_hdd_widget() {
- #region hdd widget heredoc
- # use subshell to allow variable expansion
- (
- export HELPERCTORPARAMS
- export HDD_ITEMS_PER_ROW
- cat <<'EOF' | envsubst '$HDD_ITEMS_PER_ROW $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 {
- 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);
- }
- } 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
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate hhd widget code" >&2
- exit 1
- fi
-}
-
-# Function to generate RAM widget
-generate_ram_widget() {
- #region ram widget heredoc
- # use subshell to allow variable expansion
- (
- export HELPERCTORPARAMS
- 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);
-
- 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 }) => {
- try {
- ramTemps.push(`${ramKey}: ${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') + '
';
- }
- },
-EOF
- )
- #endregion ram widget heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate ram widget code" >&2
- exit 1
- fi
-}
-
-# Function to generate UPS widget
-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 || {};
- }
- } 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
- // Returns a CSS color string for non-default states, or null for default (no inline color)
- function getStatusColor(status) {
- if (!status) return '#999';
- const statusUpper = status.toUpperCase();
- if (statusUpper.includes('OL')) return null; // default (no explicit color)
- 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
- // Returns null for default/good values so no inline style is emitted
- 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 null; // default (no explicit color)
- } else {
- // For battery charge
- if (num <= 20) return '#d9534f'; // Red for low charge
- if (num <= 50) return '#f0ad4e'; // Orange for medium charge
- return null; // default (no explicit color)
- }
- }
-
- // 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 (no explicit color for default)
- 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 = null; // default (no explicit color)
- } 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
- }
-
- 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'; // Red if less than half of low threshold
- else if (runtime <= runtimeLowThreshold) runtimeColor = '#f0ad4e'; // Orange if at low threshold
- 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);
-
- // Format the final output
- return '' + displayItems.join('
') + '
';
- }
- },
-EOF
- #endregion UPS widget heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate UPS widget code" >&2
- exit 1
- fi
-}
-
-# Function to generate GPU widget
-generate_gpu_widget() {
- #region gpu widget heredoc
- # use subshell to allow variable expansion
- (
- export CPU_ITEMS_PER_ROW
- export CPU_TEMP_TARGET
- export HELPERCTORPARAMS
-
- cat <<'EOF' | envsubst '$CPU_ITEMS_PER_ROW $CPU_TEMP_TARGET $HELPERCTORPARAMS' > "$1"
- {
- itemId: 'gpu',
- colspan: 2,
- iconCls: 'fa fa-desktop',
- title: gettext('GPU(s)'),
- 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 += `
`;
- });
-
- // todo add NVIDIA
-
- // todo add NVIDIA
-
- return html;
- },
- },
-EOF
- )
- #endregion cpu widget heredoc
- if [[ $? -ne 0 ]]; then
- echo "Error: Failed to generate cpu widget code" >&2
- exit 1
- fi
-}
-#endregion widget generation functions
# Function to uninstall the modification
function uninstall_mod {
@@ -1714,7 +654,7 @@ function uninstall_mod {
check_root_privileges
- if [[ -z $(grep -e "$res->{sensorsOutput}" "$NODES_PM_FILE") ]] && [[ -z $(grep -e "$res->{systemInfo}" "$NODES_PM_FILE") ]]; then
+ if [[ -z $(grep -e "\$res->{sensorsOutput}" "$NODES_PM_FILE") ]] && [[ -z $(grep -e "\$res->{systemInfo}" "$NODES_PM_FILE") ]]; then
err "Mod is not installed."
fi
@@ -1725,7 +665,7 @@ function uninstall_mod {
local latest_nodes_pm=$(find "$BACKUP_DIR" -name "Nodes.pm.*" -type f -printf '%T+ %p\n' 2>/dev/null | sort -r | head -n 1 | awk '{print $2}')
if [ -n "$latest_nodes_pm" ]; then
- # Remove the latest Nodes.pm file
+ # Restore the latest Nodes.pm file
msgb "Restoring latest Nodes.pm from backup: $latest_nodes_pm to \"$NODES_PM_FILE\"."
cp "$latest_nodes_pm" "$NODES_PM_FILE"
info "Restored Nodes.pm successfully."
@@ -1733,11 +673,11 @@ function uninstall_mod {
warn "No Nodes.pm backup files found."
fi
- # Find the latest pvemanagerlib.js file using the find command
+ # Restore original pvemanagerlib.js (uncomment the StatusView definition and remove loader)
local latest_pvemanagerlibjs=$(find "$BACKUP_DIR" -name "pvemanagerlib.js.*" -type f -printf '%T+ %p\n' 2>/dev/null | sort -r | head -n 1 | awk '{print $2}')
if [ -n "$latest_pvemanagerlibjs" ]; then
- # Remove the latest pvemanagerlib.js file
+ # Restore the latest pvemanagerlib.js file
msgb "Restoring latest pvemanagerlib.js from backup: $latest_pvemanagerlibjs to \"$PVE_MANAGER_LIB_JS_FILE\"."
cp "$latest_pvemanagerlibjs" "$PVE_MANAGER_LIB_JS_FILE"
info "Restored pvemanagerlib.js successfully."
@@ -1745,25 +685,34 @@ function uninstall_mod {
warn "No pvemanagerlib.js backup files found."
fi
- # Find the latest GPUMonitor.pm file using the find command
- local latest_gpumonitor_pm=$(find "$BACKUP_DIR" -name "GPUMonitor.pm.*" -type f -printf '%T+ %p\n' 2>/dev/null | sort -r | head -n 1 | awk '{print $2}')
-
- if [ -n "$latest_gpumonitor_pm" ]; then
- # Restore the latest GPUMonitor.pm file
- msgb "Restoring latest GPUMonitor.pm from backup: $latest_gpumonitor_pm to \"$PVE_SENSOR_INFO_MOD_FILE\"."
- cp "$latest_gpumonitor_pm" "$PVE_SENSOR_INFO_MOD_FILE"
- info "Restored GPUMonitor.pm successfully."
- elif [ -f "$PVE_SENSOR_INFO_MOD_FILE" ]; then
- # No backup found but file exists, remove it
- msgb "No GPUMonitor.pm backup found. Removing installed module: $PVE_SENSOR_INFO_MOD_FILE"
- rm "$PVE_SENSOR_INFO_MOD_FILE"
- info "Removed GPUMonitor.pm successfully."
+ # Remove UI module files
+ if [ -f "$PVE_MOD_JS_TARGET_FILE" ]; then
+ msgb "Removing UI module: $PVE_MOD_JS_TARGET_FILE"
+ rm "$PVE_MOD_JS_TARGET_FILE"
+ info "Removed UI module successfully."
else
- warn "No GPUMonitor.pm backup files found and module not installed."
+ warn "UI module file not found: $PVE_MOD_JS_TARGET_FILE"
fi
- if [ -n "$latest_nodes_pm" ] || [ -n "$latest_pvemanagerlibjs" ] || [ -n "$latest_gpumonitor_pm" ] || [ -f "$PVE_SENSOR_INFO_MOD_FILE" ]; then
- # At least one of the variables is not empty, restart the proxy
+ # Remove Sensor Info Perl module
+ local latest_sensor_info_pm=$(find "$BACKUP_DIR" -name "PveMod_SensorInfo.pm.*" -type f -printf '%T+ %p\n' 2>/dev/null | sort -r | head -n 1 | awk '{print $2}')
+
+ if [ -n "$latest_sensor_info_pm" ]; then
+ # Restore the latest PveMod_SensorInfo.pm file (if there's a backup)
+ msgb "Restoring latest PveMod_SensorInfo.pm from backup: $latest_sensor_info_pm to \"$PVE_SENSOR_INFO_MOD_FILE\"."
+ cp "$latest_sensor_info_pm" "$PVE_SENSOR_INFO_MOD_FILE"
+ info "Restored PveMod_SensorInfo.pm successfully."
+ elif [ -f "$PVE_SENSOR_INFO_MOD_FILE" ]; then
+ # No backup found but file exists, remove it
+ msgb "No PveMod_SensorInfo.pm backup found. Removing installed module: $PVE_SENSOR_INFO_MOD_FILE"
+ rm "$PVE_SENSOR_INFO_MOD_FILE"
+ info "Removed PveMod_SensorInfo.pm successfully."
+ else
+ warn "No PveMod_SensorInfo.pm backup files found and module not installed."
+ fi
+
+ if [ -n "$latest_nodes_pm" ] || [ -n "$latest_pvemanagerlibjs" ] || [ -f "$PVE_MOD_JS_TARGET_FILE" ] || [ -n "$latest_sensor_info_pm" ] || [ -f "$PVE_SENSOR_INFO_MOD_FILE" ]; then
+ # At least one file was modified, restart the proxy
restart_proxy
fi
@@ -1772,11 +721,12 @@ function uninstall_mod {
# Function to check if the modification is installed
check_mod_installation() {
- if [[ -n $(grep -F 'use PVE::API2::GPUMonitor' "$NODES_PM_FILE") ]] || \
+ if [[ -n $(grep -F 'use PVE::API2::PveMod_SensorInfo' "$NODES_PM_FILE") ]] || \
+ [[ -n $(grep -F 'use PVE::API2::GPUMonitor' "$NODES_PM_FILE") ]] || \
[[ -n $(grep -F '$res->{sensorsJSONOutput}' "$NODES_PM_FILE") ]] || \
[[ -n $(grep -F '$res->{sensorsOutput}' "$NODES_PM_FILE") ]] || \
- [[ -n $(grep -F '$res->{systemInfo}' "$NODES_PM_FILE") ]] && \
- [[ -n $(grep -E "itemId: 'thermal[[:alnum:]]*'" "$PVE_MANAGER_LIB_JS_FILE") ]]; then
+ [[ -n $(grep -F '$res->{systemInfo}' "$NODES_PM_FILE") ]] || \
+ [[ -f "$PVE_MOD_JS_TARGET_FILE" ]]; then
err "Mod is already installed. Uninstall existing before installing."
fi
}
@@ -1887,7 +837,7 @@ function perform_backup {
create_file_backup "$NODES_PM_FILE" "$timestamp"
create_file_backup "$PVE_MANAGER_LIB_JS_FILE" "$timestamp"
- # Backup GPU Monitor module if it exists
+ # Backup Sensor Info module if it exists
if [[ -f "$PVE_SENSOR_INFO_MOD_FILE" ]]; then
create_file_backup "$PVE_SENSOR_INFO_MOD_FILE" "$timestamp"
fi