| Film | Qualität | Status | Audio | Untertitel | Video | Größe |
{% for r in missing_german_movies %}| {{ r.title }}{{ r.year or '' }} | {{ r.quality }} | Ohne Deutsch | {% for l in r.audio_languages %}{{ l.flag }} {{ l.name }}{% else %}—{% endfor %} | {% for l in r.subtitle_languages %}{{ l.flag }} {{ l.name }}{% else %}—{% endfor %} | {{ r.video_codec }} | {{ r.size }} |
{% endfor %}
@@ -86,7 +86,7 @@ if ('serviceWorker' in navigator) {
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=`
▣${stats.movie_files} von ${stats.movie_total} Filmen vorhanden${stats.movie_missing} fehlen · ${stats.movie_downloading} werden geladen
▤${stats.episode_files} von ${stats.episode_total} Episoden vorhanden${stats.episode_missing} fehlen in ${stats.series_total} Serien
◌Medienbestand geprüftAudio, Untertitel und Qualität werden überwacht
✓Live-Überwachung aktivSystemwerte und Downloads aktualisieren sich automatisch
`;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=>`
${service.key==='dashboard'?'⌂':service.name.slice(0,1)}${html(service.name)}${html(service.detail||'Keine Antwort')}
${html(service.label)} `).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 navigation=document.querySelector('.nav');if(navigation&&!navigation.querySelector('[data-view="datastore"]')){const datastoreButton=document.createElement('button');datastoreButton.dataset.view='datastore';datastoreButton.innerHTML='
Datastore';navigation.appendChild(datastoreButton)}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();const seriesRows=[...document.querySelectorAll('#seriesList details.series')],seriesSearch=document.querySelector('#seriesSearch'),seriesAudio=document.querySelector('#seriesAudioFilter'),seriesSubs=document.querySelector('#seriesSubFilter'),seriesState=document.querySelector('#seriesStateFilter'),seriesCount=document.querySelector('#seriesCount');function applySeries(){if(!seriesSearch||!seriesAudio||!seriesSubs||!seriesState)return;const q=seriesSearch.value.trim().toLowerCase();let visible=0;seriesRows.forEach(detail=>{const summary=detail.querySelector('summary')?.textContent.toLowerCase()||'';let matches=0;detail.querySelectorAll('tbody tr:not(.season-divider)').forEach(row=>{const ok=(!q||summary.includes(q)||row.dataset.title.includes(q))&&(!seriesAudio.value||row.dataset.audio.includes(seriesAudio.value))&&(!seriesSubs.value||row.dataset.subs.includes(seriesSubs.value))&&(!seriesState.value||row.dataset.state===seriesState.value);row.classList.toggle('hidden',!ok);if(ok)matches++});detail.classList.toggle('hidden',matches===0);if(matches)visible++});document.querySelectorAll('#seriesList .series-category').forEach(category=>{const hasVisible=[...category.querySelectorAll('details.series')].some(detail=>!detail.classList.contains('hidden'));category.classList.toggle('hidden',!hasVisible)});if(seriesCount)seriesCount.textContent=`${visible} Serien`};[seriesSearch,seriesAudio,seriesSubs,seriesState].forEach(x=>x?.addEventListener(x===seriesSearch?'input':'change',applySeries));applySeries();
+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();const seriesRows=[...document.querySelectorAll('#seriesList details.series')],seriesSearch=document.querySelector('#seriesSearch'),seriesAudio=document.querySelector('#seriesAudioFilter'),seriesSubs=document.querySelector('#seriesSubFilter'),seriesState=document.querySelector('#seriesStateFilter'),seriesCount=document.querySelector('#seriesCount');function applySeries(){if(!seriesSearch||!seriesAudio||!seriesSubs||!seriesState)return;const q=seriesSearch.value.trim().toLowerCase();let visible=0;seriesRows.forEach(detail=>{const summary=detail.dataset.title||detail.querySelector('summary')?.textContent.toLowerCase()||'';let matches=0;detail.querySelectorAll('tbody tr:not(.season-divider)').forEach(row=>{const ok=(!q||summary.includes(q)||row.dataset.title.includes(q))&&(!seriesAudio.value||row.dataset.audio.includes(seriesAudio.value))&&(!seriesSubs.value||row.dataset.subs.includes(seriesSubs.value))&&(!seriesState.value||row.dataset.state===seriesState.value);row.classList.toggle('hidden',!ok);row.style.display=ok?'':'none';if(ok)matches++});detail.classList.toggle('hidden',matches===0);detail.style.display=matches?'':'none';if(matches)visible++});document.querySelectorAll('#seriesList .series-category').forEach(category=>{const hasVisible=[...category.querySelectorAll('details.series')].some(detail=>!detail.classList.contains('hidden'));category.classList.toggle('hidden',!hasVisible)});if(seriesCount)seriesCount.textContent=`${visible} Serien`};[seriesSearch,seriesAudio,seriesSubs,seriesState].forEach(x=>x?.addEventListener(x===seriesSearch?'input':'change',applySeries));applySeries();
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','missing'].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();
function statusText(s){if(s.state==='running'){const pct=s.total?Math.round(s.done/s.total*100):0;return `Scan läuft · ${s.done}/${s.total||'?'} Dateien · ${pct}%`}if(s.state==='done')return 'Scan abgeschlossen ✓';if(s.state==='error')return 'Scanfehler: '+s.error;return 'Scan wird vorbereitet …'}
function updateScanStatus(selector,s){const el=document.querySelector(selector);if(!el)return;const pct=s.total?Math.min(100,Math.round(s.done/s.total*100)):0;el.textContent=statusText(s);el.style.setProperty('--scan-progress',`${pct}%`);el.classList.toggle('scan-running',s.state==='running')}