style: align fan status with CPU frequency
This commit is contained in:
+2
-2
@@ -66,7 +66,7 @@ function ownerName(id){const [type,suffix]=String(id||'').split('|',2),name=owne
|
||||
function field(section,key,value,extra=''){const id=`${section}.${key}`,opts=options[id],rule=numberRules[id],label=tr(labels[id]||key),help=tr(hints[id]||''),unit=tr(units[id]||'');let control;if(opts)control=`<select data-section="${esc(section)}" data-key="${esc(key)}">${opts.map(([v,l])=>`<option value="${v}" ${String(value)===v?'selected':''}>${tr(l)}</option>`).join('')}</select>`;else{const input=`<input ${rule?'type="number"':''} ${rule?`min="${rule[0]}" max="${rule[1]}" step="${rule[2]}"`:''} data-section="${esc(section)}" data-key="${esc(key)}" value="${esc(value)}">`;control=unit?`<div class="input-unit">${input}<span class="unit">${unit}</span></div>`:input}return`<div class="field ${extra}"><label>${esc(label)}${control}</label>${help?`<div class="hint">${esc(help)}</div>`:''}</div>`}
|
||||
function pluginText(zh,en){return LANG==='zh'?zh:en}
|
||||
function renderPlugins(){return `<div class="group"><p>${pluginText('仅发现插件不会接管插件的设备能力。接入后发送监控状态;暂停或断开联动不会停止插件的本地功能。','Discovery does not take control of plugin device capabilities. Connecting sends monitoring state; pausing or disconnecting the link does not stop the plugin’s local functions.')}</p><div class="action-row"><button type="button" id="refreshPlugins" onclick="loadPlugins()">${tr('刷新列表')}</button> <span id="pluginRefreshState" class="hint" role="status" aria-live="polite"></span></div><div id="pluginList"></div></div><div id="pluginEditor"></div>`}
|
||||
function fanMetric(plugins){const fans=Object.entries(plugins).filter(([,p])=>p.online&&p.capabilities?.fan&&p.status?.available!==false).map(([id,p])=>{const st=p.status;let value='';if(p.capabilities.fan.rpm&&typeof st.fan_rpm==='number'&&Number.isFinite(st.fan_rpm)&&st.fan_rpm>=0)value=st.fan_rpm+' RPM';else if(st.fan_name&&st.fan_name!=='UNAVAILABLE')value=pluginText('档位 ','level ')+(st.fan_name==='OFF'?pluginText('关闭','Off'):st.fan_name);return value?{name:p.name||id,value}:null}).filter(Boolean);return fans.length?`<div class="muted">${fans.map(f=>esc(pluginText('风扇','Fan')+(fans.length>1?' '+f.name:'')+' · '+f.value)).join('<br>')}</div>`:''}
|
||||
function fanMetric(plugins){const fans=Object.entries(plugins).filter(([,p])=>p.online&&p.capabilities?.fan&&p.status?.available!==false).map(([id,p])=>{const st=p.status;let value='';if(p.capabilities.fan.rpm&&typeof st.fan_rpm==='number'&&Number.isFinite(st.fan_rpm)&&st.fan_rpm>=0)value=st.fan_rpm+' RPM';else if(st.fan_name&&st.fan_name!=='UNAVAILABLE')value=pluginText('档位 ','level ')+(st.fan_name==='OFF'?pluginText('关闭','Off'):st.fan_name);return value?{name:p.name||id,value}:null}).filter(Boolean);return fans.length?`<span> · ${fans.map(f=>esc(pluginText('风扇','Fan')+(fans.length>1?' '+f.name:'')+' · '+f.value)).join(' · ')}</span>`:''}
|
||||
function pluginSummary(plugins,host){if(!Object.keys(plugins).length)return `<div class="group"><strong>${pluginText('风扇与硬件插件','Fan and hardware plugins')}</strong><p class="muted">${pluginText('尚未接入硬件插件,风扇状态不可用。独立安装并启动插件后,可在插件管理中接入。','No hardware plugin connected; fan status is unavailable. Install and start a plugin independently, then connect it in plugin management.')}</p><button type="button" data-manage-plugin-host="${esc(host)}">${pluginText('管理插件','Manage plugin')}</button></div>`;return Object.entries(plugins).map(([id,p])=>{const st=p.status||{},cap=p.capabilities||{},details=[];if(p.online){if(cap.display)details.push('OLED '+esc(ownerName(st.oled_page||'')));if(cap.rgb)details.push('RGB '+esc(rgbName(st.rgb_mode||'')))}return `<div class="group action-row"><span><strong>${esc(p.name||id)}</strong> · ${p.online?tr('在线'):tr('离线')} ${details.join(' · ')}</span><button type="button" data-manage-plugin-host="${esc(host)}">${pluginText('管理插件','Manage plugin')}</button></div>`}).join('')}
|
||||
|
||||
let pluginRows=[],pluginDraft=null,pluginSelected='';
|
||||
@@ -110,7 +110,7 @@ async function monitorAction(unit,action){const s=serviceCache.find(x=>x.unit===
|
||||
async function serviceAction(unit,action){if(!confirm(LANG==='zh'?`确定${action==='start'?'启动服务':'停止服务'} ${unit} 吗?\n这会直接执行 systemctl ${action}。`:`${action==='start'?'Start':'Stop'} ${unit}?\nThis runs systemctl ${action} directly.`))return;try{await get('/api/v1/services/control',{method:'PUT',headers:tokenHeaders(),body:JSON.stringify({unit,action})});setTimeout(loadServices,700)}catch(e){alert(e.message)}}
|
||||
function bytes(value){const n=Number(value||0);if(!n)return'--';const units=['B','KB','MB','GB','TB'];let x=n,i=0;while(x>=1024&&i<units.length-1){x/=1024;i++}return`${x>=100||i===0?x.toFixed(0):x.toFixed(1)} ${units[i]}`}
|
||||
function meter(percent){const p=Math.max(0,Math.min(100,Number(percent||0)));return`<div class="progress"><span style="width:${p}%"></span></div>`}
|
||||
function hostCard(entry){const h=entry.host;if(!entry.online)return`<article class="panel host-panel offline-card"><div class="host-head"><div><h2>${esc(h.name||h.id)}</h2><div class="device-details"><span>ID ${esc(h.id)}</span><span>${esc(h.url||tr('本机'))}</span></div></div><span class="badge bad">${tr('离线')}</span></div><div class="error">${esc(entry.error||tr('连接失败'))}</div></article>`;const s=entry.status,y=s.system||{},n=s.network||{},d=s.display||{},v=s.device||{},m=s.machine||h,signal=n.signal_percent,memUsed=y.memory_used_bytes,memTotal=y.memory_total_bytes,diskUsed=y.disk_used_bytes,diskTotal=y.disk_total_bytes;return`<article class="panel host-panel"><div class="host-head"><div><h2>${esc(m.name||h.name||m.id)}</h2><div class="device-details"><span>ID ${esc(m.id||h.id)}</span><span>${LANG==='zh'?'主机名':'Hostname'} ${esc(m.hostname||v.hostname||'--')}</span><span>${esc(v.manufacturer||'')} ${esc(v.model||'--')}</span><span>${LANG==='zh'?'序列号':'Serial'} ${esc(v.serial||'--')}</span><span>${LANG==='zh'?'硬件版本':'Revision'} ${esc(v.revision||'--')}</span><span>${esc(v.os||'--')}</span><span>${LANG==='zh'?'内核':'Kernel'} ${esc(v.kernel||'--')}</span><span>${esc(v.architecture||'--')} · CPU ${esc(v.cpu_count||'--')}</span><span>${LANG==='zh'?'程序版本':'Agent'} ${esc(s.version||'--')}</span></div></div><span class="badge good">${tr('在线')}</span></div><div class="host-grid"><div class="metric"><div class="muted">${tr('处理器负载')}</div><div class="value">${esc(y.cpu_percent)}%</div>${meter(y.cpu_percent)}<div class="muted">${LANG==='zh'?'频率':'Frequency'} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C</div>${fanMetric(s.plugins||{})}</div><div class="metric"><div class="muted">${tr('内存占用')}</div><div class="value">${esc(y.memory_percent)}%</div>${meter(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${LANG==='zh'?'可用':'Available'} ${bytes(y.memory_available_bytes)}</div></div><div class="metric"><div class="muted">${tr('磁盘占用')}</div><div class="value">${esc(y.disk_percent)}%</div>${meter(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${LANG==='zh'?'剩余':'Free'} ${bytes(y.disk_free_bytes)}</div></div><div class="metric"><div class="muted">${tr('主网络')}</div><div class="value">${n.kind==='WIF'&&signal!==null&&signal!==undefined?`${esc(signal)}%`:esc(n.kind||'--')}</div>${n.kind==='WIF'?meter(signal):''}<div class="muted">${esc(n.interface||'--')} · ${n.kind==='WIF'?`${esc(n.metric)} dBm · `:''}${esc(n.ip_label)} ${esc(n.ip)} · ${n.internet?(LANG==='zh'?'互联网正常':'Internet online'):(LANG==='zh'?'互联网断开':'Internet down')}</div></div><div class="metric"><div class="muted">${tr('显示状态')}</div><div class="value">${esc(ownerName(d.owner))}</div><div class="muted">${LANG==='zh'?'告警状态独立于硬件插件':'Alert state is independent of hardware plugins'}</div></div><div class="metric"><div class="muted">${tr('当前告警')}</div><div class="value">${(d.alerts||[]).length}</div><div class="muted">${(d.alerts||[]).map(a=>esc(ownerName(a.id))).join(' · ')||tr('无告警')}</div></div></div>${pluginSummary(s.plugins||{},m.id||h.id)}<div class="group"><h3>${tr('服务与进程监控')}</h3><div class="rows">${(s.watches||[]).map(w=>`<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span><span class="badge ${w.up?'good':'bad'}">${tr(w.up?'正常':'停止')}</span></div>`).join('')||`<div class="muted">${tr('暂无数据')}</div>`}</div></div></article>`}
|
||||
function hostCard(entry){const h=entry.host;if(!entry.online)return`<article class="panel host-panel offline-card"><div class="host-head"><div><h2>${esc(h.name||h.id)}</h2><div class="device-details"><span>ID ${esc(h.id)}</span><span>${esc(h.url||tr('本机'))}</span></div></div><span class="badge bad">${tr('离线')}</span></div><div class="error">${esc(entry.error||tr('连接失败'))}</div></article>`;const s=entry.status,y=s.system||{},n=s.network||{},d=s.display||{},v=s.device||{},m=s.machine||h,signal=n.signal_percent,memUsed=y.memory_used_bytes,memTotal=y.memory_total_bytes,diskUsed=y.disk_used_bytes,diskTotal=y.disk_total_bytes;return`<article class="panel host-panel"><div class="host-head"><div><h2>${esc(m.name||h.name||m.id)}</h2><div class="device-details"><span>ID ${esc(m.id||h.id)}</span><span>${LANG==='zh'?'主机名':'Hostname'} ${esc(m.hostname||v.hostname||'--')}</span><span>${esc(v.manufacturer||'')} ${esc(v.model||'--')}</span><span>${LANG==='zh'?'序列号':'Serial'} ${esc(v.serial||'--')}</span><span>${LANG==='zh'?'硬件版本':'Revision'} ${esc(v.revision||'--')}</span><span>${esc(v.os||'--')}</span><span>${LANG==='zh'?'内核':'Kernel'} ${esc(v.kernel||'--')}</span><span>${esc(v.architecture||'--')} · CPU ${esc(v.cpu_count||'--')}</span><span>${LANG==='zh'?'程序版本':'Agent'} ${esc(s.version||'--')}</span></div></div><span class="badge good">${tr('在线')}</span></div><div class="host-grid"><div class="metric"><div class="muted">${tr('处理器负载')}</div><div class="value">${esc(y.cpu_percent)}%</div>${meter(y.cpu_percent)}<div class="muted">${LANG==='zh'?'频率':'Frequency'} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins||{})}</div></div><div class="metric"><div class="muted">${tr('内存占用')}</div><div class="value">${esc(y.memory_percent)}%</div>${meter(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${LANG==='zh'?'可用':'Available'} ${bytes(y.memory_available_bytes)}</div></div><div class="metric"><div class="muted">${tr('磁盘占用')}</div><div class="value">${esc(y.disk_percent)}%</div>${meter(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${LANG==='zh'?'剩余':'Free'} ${bytes(y.disk_free_bytes)}</div></div><div class="metric"><div class="muted">${tr('主网络')}</div><div class="value">${n.kind==='WIF'&&signal!==null&&signal!==undefined?`${esc(signal)}%`:esc(n.kind||'--')}</div>${n.kind==='WIF'?meter(signal):''}<div class="muted">${esc(n.interface||'--')} · ${n.kind==='WIF'?`${esc(n.metric)} dBm · `:''}${esc(n.ip_label)} ${esc(n.ip)} · ${n.internet?(LANG==='zh'?'互联网正常':'Internet online'):(LANG==='zh'?'互联网断开':'Internet down')}</div></div><div class="metric"><div class="muted">${tr('显示状态')}</div><div class="value">${esc(ownerName(d.owner))}</div><div class="muted">${LANG==='zh'?'告警状态独立于硬件插件':'Alert state is independent of hardware plugins'}</div></div><div class="metric"><div class="muted">${tr('当前告警')}</div><div class="value">${(d.alerts||[]).length}</div><div class="muted">${(d.alerts||[]).map(a=>esc(ownerName(a.id))).join(' · ')||tr('无告警')}</div></div></div>${pluginSummary(s.plugins||{},m.id||h.id)}<div class="group"><h3>${tr('服务与进程监控')}</h3><div class="rows">${(s.watches||[]).map(w=>`<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span><span class="badge ${w.up?'good':'bad'}">${tr(w.up?'正常':'停止')}</span></div>`).join('')||`<div class="muted">${tr('暂无数据')}</div>`}</div></div></article>`}
|
||||
function renderHostCards(entries){const root=$('hostCards'),seen=new Set;entries.forEach((entry,index)=>{const id=String(entry.host.id),template=document.createElement('template');template.innerHTML=hostCard(entry);const next=template.content.firstElementChild;next.dataset.hostId=id;let current=[...root.children].find(x=>x.dataset.hostId===id);if(current){current.className=next.className;current.innerHTML=next.innerHTML;if(root.children[index]!==current)root.insertBefore(current,root.children[index]||null)}else root.insertBefore(next,root.children[index]||null);seen.add(id)});[...root.children].forEach(x=>{if(!seen.has(x.dataset.hostId))x.remove()})}
|
||||
async function loadStatus(){try{const fleet=await get('/api/v1/fleet/status'),online=fleet.hosts.filter(x=>x.online);$('dot').classList.toggle('ok',online.length>0);$('liveText').textContent=LANG==='zh'?`${online.length}/${fleet.hosts.length} 在线`:`${online.length}/${fleet.hosts.length} online`;$('identity').textContent=LANG==='zh'?`多机监控 · ${fleet.hosts.length} 台设备 · ${new Date(fleet.timestamp*1000).toLocaleTimeString()}`:`Fleet monitoring · ${fleet.hosts.length} hosts · ${new Date(fleet.timestamp*1000).toLocaleTimeString()}`;renderHostCards(fleet.hosts);fleetHosts=fleet.hosts.map(x=>x.host);refreshHostSelectors()}catch(e){$('dot').classList.remove('ok');$('liveText').textContent=tr('离线');$('identity').textContent=e.message}}
|
||||
const severityNames={DEBUG:'调试',INFO:'信息',NOTICE:'注意',WARN:'警告',ERROR:'错误',CRITICAL:'严重',ALERT:'紧急',EMERGENCY:'紧急'};let logState={sort:'timestamp',order:'desc',offset:0,limit:100,total:0};
|
||||
|
||||
Reference in New Issue
Block a user