feat: add service status monitoring to dashboard with parallel health checks for 14 services
Add SERVICE_DEFINITIONS list with Radarr, Sonarr, SABnzbd, Jellyfin, Seerr, Prowlarr, Easynews as Indexer, qBittorrent, Bazarr, Lingarr, LibreTranslate, Sonarr-Prowlarr Proxy, Radarr-Prowlarr Proxy, and Dashboard. Add check_service() to perform HTTP/API health checks with configurable timeout. Add refresh_services() to run checks in parallel using service_check_executor ThreadPoolExecutor with 16 workers.
This commit is contained in:
1 parent
a90b516f49
commit
d69f82d995
5 files changed
+132
-1
No files matched your search
@@ -33,6 +33,7 @@
|
||||
<div class="settings-backdrop hidden" id="settingsBackdrop"><section class="settings-modal" role="dialog" aria-modal="true" aria-labelledby="settingsTitle"><div class="settings-head"><div><h2 id="settingsTitle" data-de="Einstellungen" data-en="Settings">Einstellungen</h2><p data-de="Deine Anzeige- und Sprachpräferenzen" data-en="Your display and language preferences">Deine Anzeige- und Sprachpräferenzen</p></div><button class="settings-close" id="settingsClose" aria-label="Schließen">×</button></div><label for="preferredAudio" data-de="Bevorzugte Audio-Sprache" data-en="Preferred audio language">Bevorzugte Audio-Sprache</label><select id="preferredAudio"><option value="" data-de="Keine Präferenz" data-en="No preference">Keine Präferenz</option><option value="Deutsch" data-de="Deutsch" data-en="German">Deutsch</option><option value="Englisch" data-de="Englisch" data-en="English">Englisch</option><option value="Japanisch" data-de="Japanisch" data-en="Japanese">Japanisch</option><option value="Koreanisch" data-de="Koreanisch" data-en="Korean">Koreanisch</option></select><label for="preferredSubs" data-de="Bevorzugte Untertitel-Sprache" data-en="Preferred subtitle language">Bevorzugte Untertitel-Sprache</label><select id="preferredSubs"><option value="" data-de="Keine Präferenz" data-en="No preference">Keine Präferenz</option><option value="Deutsch" data-de="Deutsch" data-en="German">Deutsch</option><option value="Englisch" data-de="Englisch" data-en="English">Englisch</option><option value="Japanisch" data-de="Japanisch" data-en="Japanese">Japanisch</option><option value="Koreanisch" data-de="Koreanisch" data-en="Korean">Koreanisch</option></select><div class="settings-actions"><button id="settingsCancel" data-de="Abbrechen" data-en="Cancel">Abbrechen</button><button class="settings-save" id="settingsSave" data-de="Speichern" data-en="Save">Speichern</button></div></section></div>
|
||||
<script>
|
||||
const dashboardView=document.querySelector('#dashboard');if(dashboardView){dashboardView.classList.add('dashboard-view');const stats={{ dashboard_stats|tojson }};const insights=document.createElement('div');insights.className='dashboard-insights';insights.innerHTML=`<div class="insight-card"><span class="insight-icon">▣</span><div><strong>${stats.movie_files} von ${stats.movie_total} Filmen vorhanden</strong><small>${stats.movie_missing} fehlen · ${stats.movie_downloading} werden geladen</small></div></div><div class="insight-card"><span class="insight-icon">▤</span><div><strong>${stats.episode_files} von ${stats.episode_total} Episoden vorhanden</strong><small>${stats.episode_missing} fehlen in ${stats.series_total} Serien</small></div></div><div class="insight-card"><span class="insight-icon">◌</span><div><strong>Medienbestand geprüft</strong><small>Audio, Untertitel und Qualität werden überwacht</small></div></div><div class="insight-card"><span class="insight-icon">✓</span><div><strong>Live-Überwachung aktiv</strong><small>Systemwerte und Downloads aktualisieren sich automatisch</small></div></div>`;const columns=dashboardView.querySelector('.dashboard-columns');if(columns)dashboardView.insertBefore(insights,columns)}
|
||||
const servicePanel=document.querySelector('.service-panel');let serviceList=null;const serviceSummary=document.createElement('span');serviceSummary.className='service-summary';if(servicePanel){servicePanel.querySelectorAll('.service-row').forEach(row=>row.remove());serviceList=document.createElement('div');serviceList.className='service-list';servicePanel.appendChild(serviceList);const heading=servicePanel.querySelector('.panel-heading');if(heading)heading.appendChild(serviceSummary)}function renderServices(data){if(!serviceList)return;const values=data.services||[];serviceSummary.textContent=`${data.online||0} / ${data.total||values.length} online`;serviceList.innerHTML=values.map(service=>`<div class="service-row service-${service.status}"><span class="service-logo service-icon-${service.key}">${service.key==='dashboard'?'⌂':service.name.slice(0,1)}</span><div><strong>${html(service.name)}</strong><small>${html(service.detail||'Keine Antwort')}</small></div><span class="service-status ${service.status}">${html(service.label)}</span></div>`).join('')}async function fetchServices(){try{renderServices(await (await fetch('/api/services')).json())}catch(error){serviceSummary.textContent='Status nicht verfügbar'}}if(servicePanel){fetchServices();setInterval(fetchServices,30000)}
|
||||
const rows=[...document.querySelectorAll('#movies tbody tr')], search=document.querySelector('#search'), audio=document.querySelector('#audioFilter'), subs=document.querySelector('#subFilter'), state=document.querySelector('#stateFilter'), count=document.querySelector('#visibleCount');
|
||||
function apply(){const q=search.value.trim().toLowerCase();let n=0;rows.forEach(r=>{const ok=(!q||r.dataset.title.includes(q))&&(!audio.value||r.dataset.audio.includes(audio.value))&&(!subs.value||r.dataset.subs.includes(subs.value))&&(!state.value||r.dataset.state===state.value);r.classList.toggle('hidden',!ok);if(ok)n++});count.textContent=`${n} Filme`};[search,audio,subs,state].forEach(x=>x.addEventListener(x===search?'input':'change',apply));apply();
|
||||
document.querySelectorAll('.nav button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.nav button').forEach(x=>x.classList.remove('active'));document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));b.classList.add('active');document.querySelector('#'+b.dataset.view).classList.add('active');const rescan=document.querySelector('#rescan'),sourceSelect=document.querySelector('#scanSource'),canRescan=['radarr','sonarr'].includes(b.dataset.view);if(canRescan)sourceSelect.value=b.dataset.view;rescan.disabled=false;rescan.title='Bibliothek neu scannen';sessionStorage.setItem('dashboard-active-view',b.dataset.view)});const rememberedView=sessionStorage.getItem('dashboard-active-view');if(rememberedView)document.querySelector(`.nav button[data-view="${rememberedView}"]`)?.click();
|
||||
@@ -48,7 +49,7 @@ if('serviceWorker' in navigator)window.addEventListener('load',()=>navigator.ser
|
||||
</script>
|
||||
<style>
|
||||
#dashboard.view.active{display:flex}.view.dashboard-view:not(.active){display:none}.meter:first-child .meter-top span::before{content:'◔';display:inline-grid;place-items:center;width:20px;height:20px;margin-right:7px;border-radius:6px;background:rgba(96,169,230,.18);color:#8bc9f2;font-size:13px}.meter:nth-child(2) .meter-top span::before{content:'▤';display:inline-grid;place-items:center;width:20px;height:20px;margin-right:7px;border-radius:6px;background:rgba(146,87,194,.2);color:#d7adf3;font-size:13px}.disk-name::before{content:'▰';display:grid;grid-column:1;grid-row:1 / span 2;place-items:center;width:20px;height:20px;margin:0;border-radius:6px;background:rgba(244,196,48,.16);color:#f4c430;font-size:11px}.meter-top span,.disk-name{display:flex;align-items:center}.disk-name{display:grid;grid-template-columns:20px minmax(0,1fr);grid-template-rows:auto auto;column-gap:7px;align-items:center;min-width:92px;line-height:1.1}.disk-name:not(:has(.disk-path)){grid-template-rows:auto}.disk-name .disk-path{grid-column:2;grid-row:2;white-space:nowrap}.disk-name .disk-path:empty{display:none}
|
||||
</style><style>.nav-count.download-nav-count{background:#f4c430!important;border-color:#ffd95a!important;color:#17191d!important;box-shadow:0 0 10px rgba(244,196,48,.28)}</style></body></html>
|
||||
</style><style>.nav-count.download-nav-count{background:#f4c430!important;border-color:#ffd95a!important;color:#17191d!important;box-shadow:0 0 10px rgba(244,196,48,.28)}.service-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:20px}.service-summary{margin-left:auto;color:#9da5b2;font-size:12px;font-weight:700}.service-row{min-width:0}.service-row strong,.service-row small{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.service-status.timeout,.service-status.error{background:#806b25;color:#fff3bd}.service-status.offline{background:#7a3030;color:#ffd9d9}.service-timeout .service-logo,.service-error .service-logo{background:#806b25!important}.service-offline .service-logo{background:#7a3030!important}@media(max-width:700px){.service-list{grid-template-columns:1fr}.service-summary{font-size:10px}}</style></body></html>
|
||||
<style>
|
||||
details.series{margin-bottom:8px;border:1px solid var(--line);border-radius:6px;overflow:hidden;background:#222}
|
||||
details.series:last-child{margin-bottom:0}
|
||||
|
||||
Reference in new issue
Block a user