@@ -36,19 +36,20 @@ function rgbGroup(values,prefix,title){const r=Number(values[`${prefix}_r`]||0),
function normalGroup(v){return`<divclass="group"><divclass="group-head"><div><h3>正常运行灯效</h3><divclass="hint">没有告警时显示;告警出现后自动让位。</div></div></div><divclass="fields">${field('led','normal_mode',v.normal_mode,'span2')}<divclass="normal-field span2"data-modes="solid flash">${rgbGroup(v,'normal','自定义颜色')}</div><divclass="normal-field"data-modes="flash">${field('led','normal_flash_on_ms',v.normal_flash_on_ms)}</div><divclass="normal-field"data-modes="flash">${field('led','normal_flash_off_ms',v.normal_flash_off_ms)}</div><divclass="normal-field"data-modes="flow breathe marquee rainbow colorful">${field('led','normal_effect_speed',v.normal_effect_speed)}</div><divclass="normal-field"data-modes="flow breathe">${field('led','normal_effect_color',v.normal_effect_color)}</div></div></div>`}
function renderLed(v){const flash=['warning','critical','emergency'].map((x,i)=>`<divclass="group"><h3>${['普通告警','严重告警','紧急告警'][i]}闪烁节奏</h3><divclass="fields">${field('led',`${x}_flash_on_ms`,v[`${x}_flash_on_ms`])}${field('led',`${x}_flash_off_ms`,v[`${x}_flash_off_ms`])}</div></div>`).join(''),advanced=v.write_delay_ms===undefined?'':`<divclass="group"><h3>硬件高级参数</h3><divclass="fields">${field('led','write_delay_ms',v.write_delay_ms)}</div></div>`;return normalGroup(v)+`<divclass="group"><divclass="group-head"><div><h3>告警对象颜色</h3><divclass="hint">告警对象决定颜色,严重程度决定闪烁速度。</div></div></div>${alertColorGroups.map(([p,t])=>rgbGroup(v,p,t)).join('')}</div>`+flash+advanced}
function renderGeneral(s,v){const [t,d]=sectionMeta[s]||[s,''];return`<divclass="group"><divclass="group-head"><div><h3>${t}</h3><divclass="hint">${d}</div></div></div><divclass="fields">${Object.entries(v).map(([k,x])=>field(s,k,x)).join('')||'<divclass="muted">暂无配置项</div>'}</div></div>`}
function renderServices(){return`<divclass="group"><divclass="group-head"><div><h3>当前系统服务</h3><divclass="hint">点击“加入监控”会自动填写下方表单;也可以手动输入尚未安装的未来服务。</div></div><buttonid="refreshServices">刷新列表</button></div><inputclass="service-search"id="serviceSearch"placeholder="搜索服务名称或说明"><divclass="service-list rows"id="systemServices"><divclass="muted">正在读取 systemd 服务…</div></div></div><divclass="group"><h3>已监控服务</h3><divclass="hint">可启动、停止、编辑或删除监控;只有当前 API 服务不能在这里停止。</div><divclass="rows"id="monitoredServices"></div></div><divclass="group"><h3id="serviceFormTitle">新建服务监控</h3><divclass="service-form"><label>OLED 显示名称<inputid="monitorName"maxlength="64"placeholder="例如:DOCKER"></label><label>systemd 服务名称<inputid="monitorTarget"maxlength="128"placeholder="例如:docker.service"></label><labelclass="full">备注说明<inputid="monitorNote"maxlength="120"placeholder="例如:家庭相册容器服务"></label><divclass="full"><buttonid="saveMonitor">保存监控</button><buttonid="resetMonitor">清空</button><spanid="serviceMessage"class="muted"></span></div></div></div>`}
function renderServices(){return`<divclass="group"><divclass="group-head"><div><h3>当前系统服务</h3><divclass="hint">点击“加入监控”会自动填写下方表单;也可以手动输入尚未安装的未来服务。</div></div><buttonid="refreshServices">刷新列表</button></div><inputclass="service-search"id="serviceSearch"placeholder="搜索服务名称或说明"><divclass="service-list rows"id="systemServices"><divclass="muted">正在读取 systemd 服务…</div></div></div><divclass="group"><h3>已监控服务</h3><divclass="hint">“暂停监控”只停止 PIGWay 检查,不影响服务运行;“停止服务”才会操作 systemd。</div><divclass="rows"id="monitoredServices"></div></div><divclass="group"><h3id="serviceFormTitle">新建服务监控</h3><divclass="service-form"><label>OLED 显示名称<inputid="monitorName"maxlength="64"placeholder="例如:DOCKER"></label><label>systemd 服务名称<inputid="monitorTarget"maxlength="128"placeholder="例如:docker.service"></label><labelclass="full">备注说明<inputid="monitorNote"maxlength="120"placeholder="例如:家庭相册容器服务"></label><divclass="full"><buttonid="saveMonitor">保存监控</button><buttonid="resetMonitor">清空</button><spanid="serviceMessage"class="muted"></span></div></div></div>`}
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)=>`<buttonclass="${i===0?'active':''}"data-config-section="${s}">${sectionMeta[s]?.[0]||s}</button>`).join('');$('configRows').innerHTML=ss.map((s,i)=>`<sectionclass="config-page ${i===0?'active':''}"data-config-page="${s}"><h2>${sectionMeta[s]?.[0]||s}</h2><pclass="muted">${sectionMeta[s]?.[1]||''}</p>${s==='led'?renderLed(original[s]):s==='services'?renderServices():renderGeneral(s,original[s])}</section>`).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'}
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=`<divclass="error">${esc(e.message)}</div>`}}
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'}}
async function deleteMonitor(unit){const s=serviceCache.find(x=>x.unit===unit);if(!s||!confirm(`确定删除 ${s.monitor_name} 的监控吗?\n不会删除或停止 ${unit}。`))return;try{await get('/api/v1/services',{method:'PUT',headers:tokenHeaders(),body:JSON.stringify({operation:'delete',name:s.monitor_name,target:s.unit,note:''})});await loadConfig();document.querySelector('[data-config-section="services"]')?.click()}catch(e){alert(e.message)}}
async function serviceAction(unit,action){if(!confirm(`确定${action==='start'?'启动':'停止'} ${unit} 吗?`))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)}}
async function monitorAction(unit,action){const s=serviceCache.find(x=>x.unit===unit);if(!s)return;try{await get('/api/v1/services',{method:'PUT',headers:tokenHeaders(),body:JSON.stringify({operation:action,name:s.monitor_name,target:s.unit,note:s.note||''})});await loadServices()}catch(e){alert(e.message)}}
async function serviceAction(unit,action){if(!confirm(`确定${action==='start'?'启动服务':'停止服务'} ${unit} 吗?\n这会直接执行 systemctl ${action}。`))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 localDateValue(d){return`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`}
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.