`}
function pluginText(zh,en){return LANG==='zh'?zh:en}
function renderPlugins(){return `
${pluginText('仅发现插件不会接管灯光。接入后发送监控状态;暂停或断开不会停止本地温控。','Discovery does not take over lighting. Connecting sends monitoring state; pausing or disconnecting leaves autonomous cooling running.')}
${pluginText('风扇与硬件插件','Fan and hardware plugins')}
${pluginText('尚未接入硬件插件,风扇状态不可用。独立安装并启动插件后,可在插件管理中接入。','No hardware plugin connected; fan status is unavailable. Install and start a plugin independently, then connect it in plugin management.')}
`}).join('')}
let pluginRows=[],pluginDraft=null,pluginSelected='';
async function loadPlugins(){const list=$('pluginList');if(!list)return;list.textContent=pluginText('正在发现本机插件…','Discovering local plugins…');try{const data=await get('/api/v1/plugins?host='+encodeURIComponent(currentConfigHost));pluginRows=data.plugins;list.innerHTML=pluginRows.map(p=>`
${pluginText('未发现硬件插件。请在目标机器上独立安装并启动所需插件;没有散热板也不影响监控。','No hardware plugins found. Install and start the required plugin on the target host. Monitoring does not require a cooling board.')}
`;list.querySelectorAll('[data-plugin-op]').forEach(b=>b.onclick=()=>pluginAction(b.dataset.pluginId,b.dataset.pluginOp));list.querySelectorAll('[data-plugin-edit]').forEach(b=>b.onclick=()=>editPlugin(b.dataset.pluginEdit))}catch(e){list.textContent=e.message}}
@@ -94,7 +94,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=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`
ID ${esc(m.id||h.id)}${LANG==='zh'?'主机名':'Hostname'} ${esc(m.hostname||v.hostname||'--')}${esc(v.model||'--')}${LANG==='zh'?'序列号':'Serial'} ${esc(v.serial||'--')}${LANG==='zh'?'硬件版本':'Revision'} ${esc(v.revision||'--')}${esc(v.os||'--')}${LANG==='zh'?'内核':'Kernel'} ${esc(v.kernel||'--')}${esc(v.architecture||'--')} · CPU ${esc(v.cpu_count||'--')}${LANG==='zh'?'程序版本':'Agent'} ${esc(s.version||'--')}
ID ${esc(m.id||h.id)}${LANG==='zh'?'主机名':'Hostname'} ${esc(m.hostname||v.hostname||'--')}${esc(v.model||'--')}${LANG==='zh'?'序列号':'Serial'} ${esc(v.serial||'--')}${LANG==='zh'?'硬件版本':'Revision'} ${esc(v.revision||'--')}${esc(v.os||'--')}${LANG==='zh'?'内核':'Kernel'} ${esc(v.kernel||'--')}${esc(v.architecture||'--')} · CPU ${esc(v.cpu_count||'--')}${LANG==='zh'?'程序版本':'Agent'} ${esc(s.version||'--')}