diff --git a/SHA256SUMS b/SHA256SUMS index a5d72a8..3257526 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -7,5 +7,5 @@ ffd1790c882ca5dbcaebdfadc8f24c504159900213eebaa22e61fcdbf3facfe0 README.md 3da8b5d94e2206bffc19e1c4fa493b31880c9a90fd6a02546461fe54a7255dbd config/pigway-pi-control.conf 20c584762cae3a50a498df25e11272c3c191a62c9ce823e63f8224d14637b1d4 systemd/pigway-pi-control-api.service 35ec68e1c3040e7affad43961fbb77e9e29f3474b92aed1005502629f9acb549 systemd/pigway-pi-control.service -95b258d06fdf22a6bd2112ea2208b87be7a61e8eb3c68dcb29fe2b4e5e7dd6a3 web/index.html +7dbb498b137148c0bbc77f31c1344ea4a2954814b35f190e10e3f8151fe04603 web/index.html fa7ab991a7a39cce72b218e407dcefbcede99c8d8eb3d9c161a5296d506c41a9 docs/HARDWARE_PLUGIN_API.md diff --git a/web/index.html b/web/index.html index fbf76b7..c4b3dd1 100644 --- a/web/index.html +++ b/web/index.html @@ -57,11 +57,11 @@ const ownerNames={NORMAL_HOME:'正常主页',ROOT_READONLY:'根文件系统只 function ownerName(id){const [type,suffix]=String(id||'').split('|',2),name=ownerNames[type]||type;return tr(name)+(suffix?` · ${suffix}`:'')}function rgbName(mode){if(rgbNames[mode])return tr(rgbNames[mode]);const p=String(mode||'').split('_'),groups={CPU:'处理器',POWER:'供电',MEMORY:'内存',STORAGE:'存储',NETWORK:'网络',SERVICE:'服务'},levels={WARNING:'普通告警',CRITICAL:'严重告警',EMERGENCY:'紧急告警'};return`${tr(groups[p[0]]||p[0])} · ${tr(levels[p[1]]||p[1])}${p[2]==='BREATHE'?(LANG==='zh'?'呼吸':' breathing'):(LANG==='zh'?'闪烁':' flash')}`}function eventName(id){return tr(eventNames[id]||(LANG==='zh'?`未知事件(${id})`:String(id).toLowerCase().replaceAll('_',' ')))} 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=``;else{const input=``;control=unit?`
${pluginText('仅发现插件不会接管灯光。接入后发送监控状态;暂停或断开不会停止本地温控。','Discovery does not take over lighting. Connecting sends monitoring state; pausing or disconnecting leaves autonomous cooling running.')}
${pluginText('仅发现插件不会接管灯光。接入后发送监控状态;暂停或断开不会停止本地温控。','Discovery does not take over lighting. Connecting sends monitoring state; pausing or disconnecting leaves autonomous cooling running.')}
${pluginText('尚未接入硬件插件,风扇状态不可用。独立安装并启动插件后,可在插件管理中接入。','No hardware plugin connected; fan status is unavailable. Install and start a plugin independently, then connect it in plugin management.')}
${esc(p.error)}
`:''}${p.status?.errors&&Object.keys(p.status.errors).length?`${esc(JSON.stringify(p.status.errors))}
`:''}${Object.keys(p.descriptor?.capabilities||{}).map(k=>esc(({fan:pluginText('风扇','Fan'),display:pluginText('屏幕','Display'),rgb:pluginText('灯光','Lighting')})[k]||k)).join(' · ')}
${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}} +async function loadPlugins(){const list=$('pluginList');if(!list)return;const button=$('refreshPlugins'),feedback=$('pluginRefreshState');if(button?.disabled)return;if(button)button.disabled=true;if(feedback)feedback.textContent=pluginText('正在刷新…','Refreshing…');list.textContent=pluginText('正在发现本机插件…','Discovering local plugins…');try{const data=await get('/api/v1/plugins?host='+encodeURIComponent(currentConfigHost));pluginRows=data.plugins;if(feedback)feedback.textContent=pluginText('刷新完成,发现 '+pluginRows.length+' 个插件 · ','Refreshed, '+pluginRows.length+' plugins found · ')+new Date().toLocaleTimeString();list.innerHTML=pluginRows.map(p=>`${esc(p.error)}
`:''}${p.status?.errors&&Object.keys(p.status.errors).length?`${esc(JSON.stringify(p.status.errors))}
`:''}${Object.keys(p.descriptor?.capabilities||{}).map(k=>esc(({fan:pluginText('风扇','Fan'),display:pluginText('屏幕','Display'),rgb:pluginText('灯光','Lighting')})[k]||k)).join(' · ')}
${pluginText('未发现正在运行的硬件插件。刷新只查询插件,不会安装或启动服务。若已安装,请先在目标机器启动插件,再刷新并接入;未接入不影响监控。','No running hardware plugins found. Refresh only queries plugins; it does not install or start services. Start the installed plugin on the target host, then refresh and connect. Monitoring works independently.')}
`;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;if(feedback)feedback.textContent=pluginText('刷新失败','Refresh failed')}finally{if(button)button.disabled=false}} async function pluginPut(path,payload){const local=fleetHosts.find(h=>h.local);saveHostToken(currentConfigHost,$('token').value.trim());return get(path,{method:'PUT',headers:{'Content-Type':'application/json','Authorization':`Bearer ${hostToken(local?.id||'')}`},body:JSON.stringify({...payload,host:currentConfigHost,target_token:hostToken(currentConfigHost)})})} async function pluginAction(id,operation){try{await pluginPut('/api/v1/plugins',{id,operation});await loadPlugins()}catch(e){alert(e.message)}} function capabilityField(section,key,value,spec){const unit=LANG==='zh'?({s:'秒',ms:'毫秒'}[spec.unit]||spec.unit):spec.unit,label=spec.label?.[LANG]||spec.label?.en||key,id=`${section}.${key}`,vis=spec.visible?` data-cap-visible="${esc(JSON.stringify(spec.visible))}"`:'';return ``}