diff --git a/app.py b/app.py index 1986dca..69e17a9 100644 --- a/app.py +++ b/app.py @@ -635,6 +635,66 @@ def api_sys_optimize(): "freed": max(0, after_mem - before_mem), "available": after_mem}) +@app.route("/api/sensors") +def api_sensors(): + """CPU温度/风扇转速: 优先读宿主 /host/sys hwmon,读不到返回空+提示""" + temps, fans = [], [] + HWMON_LABEL = {"k10temp": "CPU", "zenpower": "CPU", "coretemp": "CPU", + "amdgpu": "核显", "radeon": "核显", "nct6775": "主板", + "nct6683": "主板", "nct6686": "主板", "nct6687": "主板", + "drivetemp": "硬盘", "nvme": "硬盘", "iwlwifi": "无线", + "mt7921_phy0": "无线", "r8169_0_2a00:00": "网卡", "r8169": "网卡"} + try: + for base in ("/host/sys/class/hwmon", "/sys/class/hwmon"): + if not os.path.isdir(base): + continue + for h in sorted(os.listdir(base)): + d = os.path.join(base, h) + if not os.path.isdir(d): + continue + try: + with open(os.path.join(d, "name")) as f: + chip = f.read().strip() + except Exception: + chip = h + label = HWMON_LABEL.get(chip, chip) + for i in (1, 2, 3, 4, 5): + tf = os.path.join(d, "temp%d_input" % i) + if not os.path.isfile(tf): + continue + try: + with open(tf) as f: + v = float(f.read().strip()) / 1000.0 + if -40 < v < 130: + lf = os.path.join(d, "temp%d_label" % i) + nm = label + try: + if os.path.isfile(lf): + with open(lf) as f: + nm = "%s(%s)" % (label, f.read().strip()) + except Exception: + pass + temps.append({"name": nm, "value": round(v, 1)}) + except Exception: + pass + for i in (1, 2, 3, 4, 5, 6): + ff = os.path.join(d, "fan%d_input" % i) + if not os.path.isfile(ff): + continue + try: + with open(ff) as f: + v = int(float(f.read().strip())) + if v > 0: + fans.append({"name": "%s 风扇%d" % (label, i), "value": v}) + except Exception: + pass + if temps or fans: + break + except Exception: + pass + note = "" if (temps or fans) else "宿主未暴露温度/转速传感器(容器读不到属正常,见 README 说明)" + return jsonify({"temps": temps, "fans": fans, "note": note}) + @app.route("/api/mem/drop", methods=["POST"]) def api_mem_drop(): import subprocess diff --git a/public/index.html b/public/index.html index c947157..30ac7cf 100644 --- a/public/index.html +++ b/public/index.html @@ -56,7 +56,7 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
磁盘
网络
-v1.3.3 +v1.3.4 资享导航站 @@ -65,6 +65,7 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
+ @@ -79,13 +80,14 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi let cur='cpu'; let sortState={key:null,dir:-1}; let curData=null; -document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));t.classList.add('active');cur=t.dataset.t;document.getElementById('disks').innerHTML='';const ci=document.getElementById('cpuinfo');if(ci)ci.style.display='none';const ca=document.getElementById('cpuadvise');if(ca)ca.style.display='none';const mi2=document.getElementById('meminfo');if(mi2)mi2.style.display='none';const di=document.getElementById('diskinfo');if(di)di.style.display='none';const ni=document.getElementById('netinfo');if(ni)ni.style.display='none';load(true)}); +document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));t.classList.add('active');cur=t.dataset.t;document.getElementById('disks').innerHTML='';const ci=document.getElementById('cpuinfo');if(ci)ci.style.display='none';const cs=document.getElementById('cpusensor');if(cs)cs.style.display='none';const ca=document.getElementById('cpuadvise');if(ca)ca.style.display='none';const mi2=document.getElementById('meminfo');if(mi2)mi2.style.display='none';const di=document.getElementById('diskinfo');if(di)di.style.display='none';const ni=document.getElementById('netinfo');if(ni)ni.style.display='none';load(true)}); function fmtB(b){if(b==null)return '-';const u=['B','KB','MB','GB','TB'];let i=0;while(b>=1024&&i<4){b/=1024;i++}return b.toFixed(b>=100?0:1)+u[i]} function fmtR(b){return fmtB(b)+'/s'} function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/t.name.indexOf('CPU')===0);const oth=j.temps.filter(t=>t.name.indexOf('CPU')!==0);const chip=t=>{const c=t.value>=85?'#f00':(t.value>=70?'#ff9900':'#20a53a');return `${esc(t.name)} ${t.value}°C`};if(cpu.length)h+='
'+cpu.map(chip).join('')+'
';if(oth.length)h+='
'+oth.map(chip).join('')+'
'}if(j.fans&&j.fans.length){h+='
'+j.fans.map(f=>`🌀 ${esc(f.name)} ${f.value} RPM`).join('')+'
'}else{h+='
🌀 风扇转速:宿主未暴露(家用 NAS/主板多无此传感器,属正常)
'}if(j.note&&!j.temps.length)h+=`
${esc(j.note)}
`;el.innerHTML=h}catch(e){el.style.display='';el.innerHTML='
🌡️ 温度读取失败
'}} async function cpuAdvise(){const el=document.getElementById('cpuadvise');const now=Date.now();if(now-lastAdvise<15000){if(el.innerHTML)el.style.display='';return;}lastAdvise=now;try{const r=await fetch('/api/cpu/advise');const j=await r.json();const col=j.level==='轻松'||j.level==='正常'?'#20a53a':(j.level==='偏高'?'#ff9900':'#f00');document.getElementById('cpuadvise').style.display='';document.getElementById('cpuadvise').innerHTML=`
📊 负载诊断:${j.level} (1分钟负载 ${j.load[0]} ÷ ${j.logical}核 = 单核 ${j.per_core})
${esc(j.verdict)}
如何更流畅:
${j.tips.map(t=>`
· ${esc(t)}
`).join('')}
`}catch(e){}} async function kill(pid,name){if(!confirm('结束进程 '+name+' (PID '+pid+') ?'))return;const r=await fetch('/api/kill',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({pid})});const j=await r.json();alert(j.ok?'已发送结束信号':('失败: '+j.error));load(true)} function cmpVal(p,key){ @@ -147,7 +149,7 @@ const I=j.info||{},P=j.percpu||[]; document.getElementById('cpuinfo').style.display=''; document.getElementById('cpuinfo').innerHTML=`
${esc(I.model||'未知CPU')} ${esc(I.arch||'')}
物理 ${I.physical||'-'} 核 / 逻辑 ${I.logical||'-'} 核 · ${I.sockets?('共 '+I.sockets+' 路 × '+I.cores_per_socket+'核 × '+I.threads_per_core+'线程'):''} · 当前 ${I.freq_cur||'-'} MHz(${I.freq_min||'-'} ~ ${I.freq_max||'-'} MHz)
L1d ${esc(I.l1d||'-')} · L1i ${esc(I.l1i||'-')} · L2 ${esc(I.l2||'-')} · L3 ${esc(I.l3||'-')}
${P.map((v,i)=>`
#${i} ${v}%
`).join('')}
`; document.getElementById('sub').textContent=`共 ${j.total} 个进程,显示占用最高的前 ${j.items.length} 个(进程CPU为单进程占用,单核满=100%,16核满=1600%,进程之和÷16≈整机占用)`; -cpuAdvise(); +cpuAdvise();cpuSensor(); renderHeader(); B.innerHTML=j.items.map(p=>`${esc(p.name)}${p.cpu}${p.threads}${p.pid}${esc(p.desc)}${esc(p.user)}${p.status}`).join(''); }else if(cur==='mem'){