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(/🌡️ 温度 / 🌀 风扇';if(j.temps&&j.temps.length){const cpu=j.temps.filter(t=>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)=>`
`).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'){