diff --git a/README.md b/README.md index 22ca5e1..aebb138 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Content-Type: application/json {"updates":{"led":{"service_b":"96"},"api":{"port":"6001"}}} ``` -服务监控页面会列出本机 systemd 服务,并标识“已监控/未监控”。添加、编辑或删除监控使用 `PUT /api/v1/services`;启动或停止服务使用 `PUT /api/v1/services/control`,两者均要求 Bearer Token。SSH、向日葵、Tailscale 和 PIGWay 自身服务不能从 Web 停止,以免中断远程管理或控制进程。监控备注保存在配置文件的 `[service_notes]`,不会参与硬件 Agent 的监控判断。 +服务监控页面会列出本机 systemd 服务,并标识“已监控/未监控”。添加、编辑或删除监控使用 `PUT /api/v1/services`;启动或停止服务使用 `PUT /api/v1/services/control`,两者均要求 Bearer Token。API 服务不能通过自己的请求停止自身,其他服务由用户自行管理。监控备注保存在配置文件的 `[service_notes]`,不会参与硬件 Agent 的监控判断。 读取 token: diff --git a/SHA256SUMS b/SHA256SUMS index 1372f47..d433416 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,10 +1,10 @@ -5442d660875ec28909b1183e4e2aee604e9f2e82d229c3806498392e7a4546e3 README.md +8e3d96c25cf6d6cd7d6dbbdf22829226ae29db5844a6c6d5867d81863538b3b0 README.md 72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin d71d949643d31461f2e62ded3818e1a0f1711d1420756ad0cc32eb04cde0fad3 app/pigway_pi_control.py -bf52a12d3208b5330f3e2980187e70330b930e950cfd5165b4e6605febfef65d app/pigway_pi_control_api.py +7dc6c796febc012fa02e3ba9d66f151f585d8f77d4dabf9ca62c2b82423f92da app/pigway_pi_control_api.py 75fa662ed21138a8e394de83e46281dab039433cc0d7a8f787e1a049f4b47ec8 config/pigway-pi-control.conf f34eac1df180a3da3a6998d650c06435fd89728c10b090f6162df82e53986dd0 install.sh 4f6e8db3698a62f57823d3298a0e6814593de6fa3d31e4751b7ea4a76e553e35 systemd/pigway-pi-control.service aa21cef9a06593371ccff005335b6c0c826fbf4b5c48e4255b5f4b75ce6e9217 systemd/pigway-pi-control-api.service 9f59e7313ee58e687ae6dd29d175c67082674254ac5c5f54168b2735b64c20a4 uninstall.sh -50ff3f73a16747233aa8400201f55b3b3eb579767e39af49d2fd0f6433718360 web/index.html +08f3e276b44d615c18c394bf0508d7134f69d272e9f0e48ce5572cb5d4515dcd web/index.html diff --git a/app/pigway_pi_control_api.py b/app/pigway_pi_control_api.py index b890dad..b41f7ca 100644 --- a/app/pigway_pi_control_api.py +++ b/app/pigway_pi_control_api.py @@ -17,8 +17,7 @@ RGB_KEY_RE=re.compile(r"^(cpu|power|memory|storage|network|service|normal)_[rgb] FLASH_KEY_RE=re.compile(r"^(warning|critical|emergency|normal)_flash_(on|off)_ms$") NORMAL_MODES={"off","solid","flash","flow","breathe","marquee","rainbow","colorful"} EFFECT_COLORS={"red","green","blue","yellow","purple","cyan","white"} -PROTECTED_UNITS={"ssh.service","sshd.service","runawesun.service","tailscaled.service", - "pigway-pi-control.service","pigway-pi-control-api.service"} +PROTECTED_UNITS={"pigway-pi-control-api.service"} def load_cfg(): cfg=configparser.ConfigParser(); cfg.read(CFG) diff --git a/web/index.html b/web/index.html index 1a6b057..a66352c 100644 --- a/web/index.html +++ b/web/index.html @@ -4,7 +4,7 @@ PIGWay Pi Control
@@ -33,14 +33,14 @@ function rgbGroup(values,prefix,title){const r=Number(values[`${prefix}_r`]||0), function normalGroup(v){return`

正常运行灯效

没有告警时显示;告警出现后自动让位。
${field('led','normal_mode',v.normal_mode,'span2')}
${rgbGroup(v,'normal','自定义颜色')}
${field('led','normal_flash_on_ms',v.normal_flash_on_ms)}
${field('led','normal_flash_off_ms',v.normal_flash_off_ms)}
${field('led','normal_effect_speed',v.normal_effect_speed)}
${field('led','normal_effect_color',v.normal_effect_color)}
`} function renderLed(v){const flash=['warning','critical','emergency'].map((x,i)=>`

${['普通告警','严重告警','紧急告警'][i]}闪烁节奏

${field('led',`${x}_flash_on_ms`,v[`${x}_flash_on_ms`])}${field('led',`${x}_flash_off_ms`,v[`${x}_flash_off_ms`])}
`).join(''),advanced=v.write_delay_ms===undefined?'':`

硬件高级参数

${field('led','write_delay_ms',v.write_delay_ms)}
`;return normalGroup(v)+`

告警对象颜色

告警对象决定颜色,严重程度决定闪烁速度。
${alertColorGroups.map(([p,t])=>rgbGroup(v,p,t)).join('')}
`+flash+advanced} function renderGeneral(s,v){const [t,d]=sectionMeta[s]||[s,''];return`

${t}

${d}
${Object.entries(v).map(([k,x])=>field(s,k,x)).join('')||'
暂无配置项
'}
`} -function renderServices(){return`

当前系统服务

点击“加入监控”会自动填写下方表单;也可以手动输入尚未安装的未来服务。
正在读取 systemd 服务…

已监控服务

可启动或停止非保护服务、编辑备注或删除监控。删除监控不会删除 systemd 服务。

新建服务监控

`} +function renderServices(){return`

当前系统服务

点击“加入监控”会自动填写下方表单;也可以手动输入尚未安装的未来服务。
正在读取 systemd 服务…

已监控服务

可启动、停止、编辑或删除监控;只有当前 API 服务不能在这里停止。

新建服务监控

`} function updateConditional(){const mode=document.querySelector('[data-section="led"][data-key="normal_mode"]')?.value||'off';document.querySelectorAll('.normal-field').forEach(x=>x.classList.toggle('hidden',!x.dataset.modes.split(' ').includes(mode)));updateSwatches()}function updateSwatches(){document.querySelectorAll('[data-swatch]').forEach(s=>{const p=s.dataset.swatch,read=c=>Math.max(0,Math.min(255,Number(document.querySelector(`[data-section="led"][data-key="${p}_${c}"]`)?.value||0))),hex='#'+['r','g','b'].map(c=>Math.round(read(c)).toString(16).padStart(2,'0')).join('');s.style.background=hex;const picker=document.querySelector(`[data-color-prefix="${p}"]`);if(picker)picker.value=hex})} function renderConfig(){const ss=sectionOrder.filter(s=>original[s]);$('configMenu').innerHTML=ss.map((s,i)=>``).join('');$('configRows').innerHTML=ss.map((s,i)=>`

${sectionMeta[s]?.[0]||s}

${sectionMeta[s]?.[1]||''}

${s==='led'?renderLed(original[s]):s==='services'?renderServices():renderGeneral(s,original[s])}
`).join('');$('configMenu').onclick=e=>{const b=e.target.closest('button');if(!b)return;document.querySelectorAll('[data-config-section]').forEach(x=>x.classList.toggle('active',x===b));document.querySelectorAll('[data-config-page]').forEach(x=>x.classList.toggle('active',x.dataset.configPage===b.dataset.configSection));if(b.dataset.configSection==='services')loadServices()};$('configRows').oninput=e=>{if(e.target.matches('[data-color-prefix]')){const p=e.target.dataset.colorPrefix,v=e.target.value;['r','g','b'].forEach((c,i)=>{document.querySelector(`[data-section="led"][data-key="${p}_${c}"]`).value=parseInt(v.slice(1+i*2,3+i*2),16)})}if(e.target.matches('[data-section="led"],[data-color-prefix]'))updateConditional();if(e.target.matches('[data-section]'))markChanged()};$('refreshServices')?.addEventListener('click',loadServices);$('serviceSearch')?.addEventListener('input',renderServiceLists);$('saveMonitor')?.addEventListener('click',saveMonitor);$('resetMonitor')?.addEventListener('click',resetMonitorForm);updateConditional()} function markChanged(){let n=0;document.querySelectorAll('#configRows [data-section]').forEach(i=>{if(i.value!==original[i.dataset.section][i.dataset.key])n++});$('saveState').textContent=n?`有 ${n} 项尚未保存`:'尚未修改';$('saveState').className=n?'':'muted'} tabs.forEach(b=>b.onclick=()=>{tabs.forEach(x=>x.classList.toggle('active',x===b));['overview','logs','config'].forEach(id=>$(id).classList.toggle('hidden',id!==b.dataset.tab));if(b.dataset.tab==='logs')loadLogs();if(b.dataset.tab==='config')loadConfig()});async function get(url,opt){const r=await fetch(url,opt),text=await r.text();let j;try{j=JSON.parse(text)}catch{throw Error(`服务器返回异常 (${r.status})`)}if(!r.ok)throw Error(j.error||`请求失败 (${r.status})`);return j}function rows(target,items,empty='暂无数据'){$(target).innerHTML=items.length?items.join(''):`
${empty}
`} let serviceCache=[],editingServiceName='';function tokenHeaders(){const token=$('token').value.trim();localStorage.setItem('pigwayToken',token);return{'Content-Type':'application/json','Authorization':`Bearer ${token}`}} async function loadServices(){if(!$('systemServices'))return;try{const d=await get('/api/v1/services');serviceCache=d.services;renderServiceLists()}catch(e){$('systemServices').innerHTML=`
${esc(e.message)}
`}} -function renderServiceLists(){if(!$('systemServices'))return;const q=$('serviceSearch').value.trim().toLowerCase(),filtered=serviceCache.filter(s=>!q||`${s.unit} ${s.description} ${s.note}`.toLowerCase().includes(q));rows('systemServices',filtered.map(s=>`
${esc(s.unit)}
${esc(s.description||'暂无服务说明')} · ${esc(s.active==='active'?'正在运行':'未运行')}
${s.monitored?'已监控':'未监控'}
`),'没有匹配的服务');const monitored=serviceCache.filter(s=>s.monitored);rows('monitoredServices',monitored.map(s=>`
${esc(s.monitor_name)} ${s.active==='active'?'运行中':'已停止'}
${esc(s.unit)}${s.note?` · ${esc(s.note)}`:''}
`),'尚未监控任何服务');document.querySelectorAll('[data-select-service]').forEach(b=>b.onclick=()=>selectService(b.dataset.selectService));document.querySelectorAll('[data-service-action]').forEach(b=>b.onclick=()=>serviceAction(b.dataset.unit,b.dataset.serviceAction));document.querySelectorAll('[data-delete-monitor]').forEach(b=>b.onclick=()=>deleteMonitor(b.dataset.deleteMonitor))} +function renderServiceLists(){if(!$('systemServices'))return;const q=$('serviceSearch').value.trim().toLowerCase(),filtered=serviceCache.filter(s=>!q||`${s.unit} ${s.description} ${s.note}`.toLowerCase().includes(q));rows('systemServices',filtered.map(s=>`
${esc(s.unit)}
${esc(s.description||'暂无服务说明')} · ${esc(s.active==='active'?'正在运行':'未运行')}
${s.monitored?'已监控':'未监控'}
`),'没有匹配的服务');const monitored=serviceCache.filter(s=>s.monitored);rows('monitoredServices',monitored.map(s=>`
${esc(s.monitor_name)} ${s.active==='active'?'运行中':'已停止'}
${esc(s.unit)}
备注:${esc(s.note||'未填写')}
`),'尚未监控任何服务');document.querySelectorAll('[data-select-service]').forEach(b=>b.onclick=()=>selectService(b.dataset.selectService));document.querySelectorAll('[data-service-action]').forEach(b=>b.onclick=()=>serviceAction(b.dataset.unit,b.dataset.serviceAction));document.querySelectorAll('[data-delete-monitor]').forEach(b=>b.onclick=()=>deleteMonitor(b.dataset.deleteMonitor))} function selectService(unit){const s=serviceCache.find(x=>x.unit===unit);if(!s)return;editingServiceName=s.monitor_name||'';$('monitorTarget').value=s.unit;$('monitorName').value=s.monitor_name||s.unit.replace(/\.service$/,'').replace(/[^A-Za-z0-9_.-]/g,'_').slice(0,16).toUpperCase();$('monitorNote').value=s.note||s.description||'';$('serviceFormTitle').textContent=s.monitored?'编辑服务监控':'新建服务监控';$('serviceMessage').textContent='已自动填写,可继续修改。';$('monitorName').scrollIntoView({behavior:'smooth',block:'center'})} function resetMonitorForm(){editingServiceName='';$('monitorName').value='';$('monitorTarget').value='';$('monitorNote').value='';$('serviceFormTitle').textContent='新建服务监控';$('serviceMessage').textContent='';$('serviceMessage').className='muted'} async function saveMonitor(){const body={operation:'save',name:$('monitorName').value.trim(),target:$('monitorTarget').value.trim(),note:$('monitorNote').value.trim(),previous_name:editingServiceName};try{$('serviceMessage').textContent='正在保存…';await get('/api/v1/services',{method:'PUT',headers:tokenHeaders(),body:JSON.stringify(body)});resetMonitorForm();await loadServices();$('serviceMessage').textContent='保存成功';$('serviceMessage').className='success'}catch(e){$('serviceMessage').textContent=e.message;$('serviceMessage').className='error'}}