feat: add system load average, uptime, and process count to dashboard with responsive detail cards and iconify indicators

Add os.getloadavg() to system_metrics() with (0,0,0) fallback for AttributeError/OSError. Add /proc/uptime parsing for uptime_seconds with float conversion and 0 fallback. Add /proc process counting by iterating numeric entries with OSError exception handling. Add load/uptime_seconds/processes fields to /api/system response with rounded values.

Add DOMContentLoaded script to inject #
This commit is contained in:
2026-08-16 12:32:08 +02:00
parent c15cc17142
commit cc7d719465
2 changed files with 15 additions and 1 deletions
+2
View File
@@ -1,4 +1,6 @@
<!doctype html>
<script>document.addEventListener('DOMContentLoaded',()=>{const panel=document.querySelector('.system-panel'),diskList=document.querySelector('#diskList');if(!panel||!diskList)return;const extra=document.createElement('div');extra.id='systemDetails';extra.className='system-details';diskList.before(extra);const formatUptime=seconds=>{const days=Math.floor(seconds/86400),hours=Math.floor(seconds%86400/3600),minutes=Math.floor(seconds%3600/60);return `${days?days+' T ':''}${hours} Std. ${minutes} Min.`};const update=async()=>{try{const data=await (await fetch('/api/system')).json();const load=data.load||[];extra.innerHTML=`<div class="system-detail"><span class="system-detail-icon"><img src="https://api.iconify.design/mdi:chart-line.svg?color=%2360a9e6" alt="" aria-hidden="true"></span><div><small>Load Average</small><strong>${load.map(value=>Number(value).toFixed(2)).join(' · ')||'—'}</strong><em>1 / 5 / 15 Minuten</em></div></div><div class="system-detail"><span class="system-detail-icon"><img src="https://api.iconify.design/mdi:clock-outline.svg?color=%23d7adf3" alt="" aria-hidden="true"></span><div><small>Uptime</small><strong>${formatUptime(Number(data.uptime_seconds)||0)}</strong><em>Container-Laufzeit</em></div></div><div class="system-detail"><span class="system-detail-icon"><img src="https://api.iconify.design/mdi:application-cog-outline.svg?color=%23f4d267" alt="" aria-hidden="true"></span><div><small>Prozesse</small><strong>${Number(data.processes||0).toLocaleString('de-DE')}</strong><em>Aktive Prozesse</em></div></div>`}catch(error){extra.innerHTML='<div class="system-detail muted">Systemdetails momentan nicht verfügbar</div>'}};update();setInterval(update,5000)});</script>
<style>.system-details{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:2px 0 17px}.system-detail{display:flex;align-items:center;gap:9px;padding:10px 11px;border:1px solid rgba(255,255,255,.07);border-radius:9px;background:rgba(40,43,49,.58)}.system-detail-icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:8px;background:rgba(96,169,230,.14)}.system-detail-icon img{width:17px;height:17px}.system-detail small,.system-detail em{display:block}.system-detail small{color:#9da5b2;font-size:10px}.system-detail strong{display:block;margin-top:2px;color:#f1f5fa;font-size:13px}.system-detail em{margin-top:2px;color:#7f8996;font-size:9px;font-style:normal}@media(max-width:700px){.system-details{grid-template-columns:1fr 1fr}.system-detail:last-child{grid-column:1 / -1}}</style>
<script>document.addEventListener('DOMContentLoaded',()=>{const icons={overview:['mdi:movie-open-outline','mdi:television-classic','mdi:alert-outline','mdi:download-box-outline'],insight:['mdi:movie-check-outline','mdi:playlist-check','mdi:database-check-outline','mdi:monitor-eye']};const apply=(selector,keys)=>{const colors=selector.includes('overview')?['%238bc9f2','%23d7adf3','%23ff9e9e','%236ae29a']:['%238bc9f2','%23d7adf3','%23f4d267','%236ae29a'];document.querySelectorAll(selector).forEach((node,index)=>{const key=keys[index%keys.length];if(!key||node.dataset.iconApplied===key)return;const image=document.createElement('img');image.src=`https://api.iconify.design/${key}.svg?color=${colors[index%colors.length]}`;image.alt='';image.setAttribute('aria-hidden','true');node.textContent='';node.appendChild(image);node.dataset.iconApplied=key})};const update=()=>{apply('.overview-icon',icons.overview);apply('.insight-icon',icons.insight)};update();new MutationObserver(update).observe(document.body,{childList:true,subtree:true})});</script>
<style>.overview-icon img{width:23px;height:23px;object-fit:contain}.insight-icon img{width:17px;height:17px;object-fit:contain}</style>
<script>document.addEventListener('DOMContentLoaded',()=>{const badge=document.querySelector('#raidStatusBadge');if(!badge)return;const side=document.createElement('div');side.className='raid-status-side';const countdown=document.createElement('small');countdown.id='raidRefreshCountdown';countdown.textContent='Update in 5s';badge.replaceWith(side);side.append(badge,countdown);const interval=5000;let next=Date.now()+interval;const reset=()=>{next=Date.now()+interval};window.resetRaidRefreshCountdown=reset;const update=()=>{const seconds=Math.max(0,Math.ceil((next-Date.now())/1000));countdown.textContent=`Update in ${seconds}s`;if(seconds===0)reset()};update();setInterval(update,250);const updated=document.querySelector('#datastoreUpdated');if(updated)new MutationObserver(reset).observe(updated,{childList:true,characterData:true,subtree:true})});</script>