PVE概览界面增加CPU频率,温度显示

PVE安装后初始是不带CPU频率和各温度监控器显示的,本篇教程将带大家修改pve的文件,以实现自定义频率和温度显示!

一、安装sensors温度检测工具

1.1.安装

首先我们需要SSH连接PVE后,安装sensors温度检测工具

apt update -y && apt install sensors -y

1.2.初始化设置

安装好后,使用sensors-detect进行初始化设置

sensors-detect

这里通常情况全部ENTER回车键确认即可(即直接使用默认值)

image-20240124173238727

1.3.测试温度传感器

这里我们使用命令 sensors 查看一下温度传感器是否正常获取到温度值了

image-20240124173448211

可以看到温度获取正常,但是为了方便处理数据,我们后续使用的命令是:sensors -j

-j 表示用 JSON 格式输出传感器数据

image-20240124173615330

二、修改PVE文件,添加修改代码

2.1 编辑api文件代码

文件路径:/usr/share/perl5/PVE/API2/Nodes.pm

可以使用sftp等工具copy到自己电脑上方便修改,或者使用vi和nano直接打开,我这里直接copy后使用notepad++打开修改

定位编辑位置,查找内容:$res->{ksm} = {

....
$res->{ksm} = {
        shared => $meminfo->{memshared},
    };
$res->{cpure} = `cat /proc/cpuinfo | grep -i  "cpu mhz"`; #cpu频率
$res->{sensinfo} = `sensors -j`; #JSON格式输出传感器数据
$res->{hddtemp} = `hddtemp /dev/sd?`;  #硬盘温度
$res->{apcaccess} = `apcaccess`; #ups信息
...

参考上面示例,添加代码:

$res->{cpure} = cat /proc/cpuinfo | grep -i "cpu mhz"; #cpu频率

$res->{sensinfo} = sensors -j; #JSON格式输出传感器数据

$res->{hddtemp} = hddtemp /dev/sd?; #硬盘温度

image-20240124174635346

2.2 编辑界面js文件代码

文件路径:/usr/share/pve-manager/js/pvemanagerlib.js

定位编辑位置,查找内容:Proxmox.Utils.render_cpu_model,

{
        itemId: 'cpus',
        colspan: 2,
        printBar: false,
        title: gettext('CPU(s)'),
        textField: 'cpuinfo',
        renderer: Proxmox.Utils.render_cpu_model,
        value: '',
    },
//在这后面添加以下内容
    {
        itemId: 'cpus',
        colspan: 2,
        printBar: false,
        title: gettext('CPU(s)'),
        textField: 'cpuinfo',
        renderer: Proxmox.Utils.render_cpu_model,
        value: '',
    },
    {
        itemId: 'cpumhz',
        colspan: 2,
        printBar: false,
        title: gettext('CPU频率'),
        textField: 'cpure',
        renderer: function(value) {
            const m = value.match(/(?<=:\s+)(\d+)/g);
            if (!m) {
                return 'CPUMHZ: 未知';
            }
            const defaultVal = '未知';
            return `CPUMHZ: ${m[0] || defaultVal} | ${m[1] || defaultVal} | ${m[2] || defaultVal} | ${m[3] || defaultVal}`;
        }
    },
    {
        itemId: 'sensinfo',
        colspan: 2,
        printBar: false,
        title: gettext('温度传感器'),
        textField: 'sensinfo',
        renderer: function(value) {
            let parsedValue;
            try {
                parsedValue = JSON.parse(value.replaceAll('Â', ''));
            } catch (error) {
                return '温度信息解析错误';
            }

            const getTemperature = (path) => {
                let current = parsedValue;
                for (const part of path) {
                    if (!current || !current.hasOwnProperty(part)) {
                        return '未知';
                    }
                    current = current[part];
                }
                return typeof current === 'number' ? current.toFixed(1) : '未知';
            };

            const cp = getTemperature(['coretemp-isa-0000', 'Package id 0', 'temp1_input']);
            const c0 = getTemperature(['coretemp-isa-0000', 'Core 0', 'temp2_input']);
            const c1 = getTemperature(['coretemp-isa-0000', 'Core 1', 'temp3_input']);

        //const c2 = value['coretemp-isa-0000']['Core 2']['temp4_input'].toFixed(1);

        //const c3 = value['coretemp-isa-0000']['Core 3']['temp5_input'].toFixed(1);

        //const f1 = value['it8786-isa-0a40']['fan1']['fan1_input'].toFixed(1);
            const pch = getTemperature(['pch_skylake-virtual-0', 'temp1', 'temp1_input']);

            const z1 = getTemperature(['acpitz-acpi-0', 'temp1', 'temp1_input']);
            const z2 = getTemperature(['acpitz-acpi-0', 'temp2', 'temp2_input']);

            const n0 = getTemperature(['nvme-pci-0500', 'Composite', 'temp1_input']);
            return `CPU温度: ${cp}℃ (${c0}℃,${c1}℃) <br> pch温度: ${pch}℃ <br> 主板温度:${z1}℃ | ${z2}℃ <br> 固态硬盘温度:${n0}℃`;
        }
    },
    {
        itemId: 'hddtemp',
        colspan: 2,
        printBar: false,
        title: gettext('HDD温度'),
        textField: 'hddtemp',
        renderer: function(value) {
            // 检查 value 是否为有效字符串
            if (typeof value !== 'string') {
                return '';
            }
            // 替换 Â 字符
            value = value.replace(/Â/g, '');
            // 替换换行符为 HTML 换行标签
            return value.replace(/\n/g, '<br>');
        }
    },  
    {
        itemId: 'apcaccess',
        colspan: 2,
        printBar: false,
        title: gettext('UPS信息'),
        textField: 'apcaccess',
        renderer: function(inputValue) { // 修改参数名避免冲突
            let result = {};
            const lines = inputValue.split(/\r?\n/); // 根据换行符分割字符串

            lines.forEach(line => {
                const parts = line.trim().split(/:\s*/); // 尝试按冒号和空格分割
                if (parts.length > 1) {
                    const key = parts[0].trim();
                    let lineValue = parts.slice(1).join(': ').trim(); // 修改变量名避免冲突

                    // 特殊处理 DATE 字段,假设它可能包含空格分隔的年、月、日、时、分、秒和时区
                    if (key === 'DATE') {
                        // 使用正则表达式来匹配并提取完整的日期时间字符串
                        const dateMatch = lineValue.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}) ([+-]\d{4})$/);
                        if (dateMatch) {
                            // 重新组合日期时间字符串(如果需要的话,这里可以根据实际格式进行调整)
                            lineValue = `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]} ${dateMatch[4]}:${dateMatch[5]}:${dateMatch[6]} ${dateMatch[7]}`;
                        }
                    }

                    // 存储到对象中
                    result[key] = lineValue;
                }
            });

            return `UPS名称:${result.UPSNAME || '未知'} <br>
                    更新时间:${result.DATE || '未知'} <br>
                    状态:${result.STATUS || '未知'} <br>
                    输出电压:${result.OUTPUTV || '未知'} <br>
                    负载:${result.LOADPCT ? result.LOADPCT.replace(' Percent', '') : '未知'} % <br>
                    电池电量:${result.BCHARGE ? result.BCHARGE.replace(' Percent', '') : '未知'} % <br>
                    电池可供电时间:${result.TIMELEFT || '未知'}`;
        }
    },

代码解释:

回到文章前用 sensors -j 输出传感器参数那段

image-20240124175402062

"coretemp-isa-0000" 是 CPU 传感器,其中 "Core 0" 是核心编号,"temp2_input": 56.000 是核心温度

"Package id 0" 是封装传感器,对应的 "temp1_input": xx.000 是封装温度,具体看代码

"acpitz-acpi-0" 是主板传感器,对应的参数大伙看自己的代码

nvme-pci-0500是硬盘温度传感器

PS:由于我的主板是技嘉z270n主板,直接biso接管机箱风扇控制,故风扇代码使用“//”注释了;cpu为双核4线程的G5500,注释了剩下两个核心温度

转速在 "it8786-isa-0a40" 模块中的 "fan1" 或者 "fan2",看具体插在 CPU 或者 SYS 针脚上

return 输出显示详解:

获取数据格式为【const xxxx = value['xxxx']['xxxx']['xxxx_input'].toFixed(1)】

【result】输出字符串的格式根据实际情况或者个人需求进行修改

2.3 修改布局显示

还是这个js文件:/usr/share/pve-manager/js/pvemanagerlib.js

定位编辑位置。

查找内容:Ext.create('Ext.window.Window', {

image-20240124180608781

继续查找内容跳转:widget.pveNodeStatus

image-20240124180629501

两处 height 的值需按情况修改,每多一行数据增加 20。根据实际情况修改,比如不想看到订阅,可适当调整

三、重启 PVE WEB 服务

输入代码:systemctl restart pveproxy

等待几秒钟后,刷新PVE管理网页或者重新打开PVE管理页面


参考资料:

是一名喜欢每天折腾的咸鱼!
也是一名半退役的算竞摸鱼选手,参与过icpc,天梯赛,蓝桥等比赛.
---------------------------------------------------
百度 飞桨领航团-团长
Datawhale -鲸英助教团成员
上海人工智能实验室 书生·浦语实战营- 助教
---------------------------------------------------
认证类:
华为 Harmony OS应用开发者高级认证,
NISP 一级认证,
H3C NE-RS网络工程师认证
---------------------------------------------------
荣获奖项荣誉:
第十八届“挑战杯”全国大学生课外学术科技作品竞赛 “揭榜挂帅”专项赛-全国特等奖、
“美亚杯”第八届中国电子取证大赛 三等奖、
“蓝桥杯”国优、
中国高校计算机大赛-团体程序天梯赛 省高校一等奖、
“蓝桥杯”省一等奖、
H3C新华三杯 省三等奖、
中国移动“梧桐杯”大数据创新大赛 省三等奖、
百度 飞桨领航团 金牌团长
最后更新于 2025-02-04