feat: add series filtering toolbar with search/audio/subtitle/state filters and data attributes for episode rows
Add toolbar with seriesSearch input and seriesAudioFilter/seriesSubFilter/seriesStateFilter selects to #sonarr section. Add data-title/data-audio/data-subs/data-state attributes to episode table rows including series title in data-title. Implement applySeries() function to filter episodes by search query, audio language, subtitle language, and state with series/category hiding when no
This commit is contained in:
@@ -71,8 +71,8 @@ if ('serviceWorker' in navigator) {
|
|||||||
<div class="tablewrap"><table id="movies"><thead><tr><th class="col-title">Film</th><th class="col-quality">Quality</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>
|
<div class="tablewrap"><table id="movies"><thead><tr><th class="col-title">Film</th><th class="col-quality">Quality</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>
|
||||||
{% for r in rows %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or ''))|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span>{% if r.error %} ⚠{% endif %}</td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}
|
{% for r in rows %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or ''))|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span>{% if r.error %} ⚠{% endif %}</td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}
|
||||||
</tbody></table></div></section>
|
</tbody></table></div></section>
|
||||||
{% if sonarr_enabled %}<section id="sonarr" class="view" data-source="sonarr"><div class="stats"><span class="stat" id="seriesCount">{{ series|length }} Serien</span><span class="stat scan" id="sonarrStatus">Scan wird vorbereitet …</span><span class="stat">Dashboard: {{ local_series_path }}</span></div><div class="tablewrap" id="seriesList">
|
{% if sonarr_enabled %}<section id="sonarr" class="view" data-source="sonarr"><div class="toolbar series-toolbar"><input id="seriesSearch" type="search" placeholder="Serie suchen …"><select id="seriesAudioFilter"><option value="">Alle Audio-Sprachen</option><option>Deutsch</option><option>Englisch</option><option>Japanisch</option><option>Koreanisch</option></select><select id="seriesSubFilter"><option value="">Alle Untertitel</option><option>Deutsch</option><option>Englisch</option><option>Japanisch</option><option>Koreanisch</option></select><select id="seriesStateFilter"><option value="">Alle Status</option><option>DE + EN</option><option>Deutsch</option><option>English only</option></select></div><div class="stats"><span class="stat" id="seriesCount">{{ series|length }} Serien</span><span class="stat scan" id="sonarrStatus">Scan wird vorbereitet …</span><span class="stat">Dashboard: {{ local_series_path }}</span></div><div class="tablewrap" id="seriesList">
|
||||||
{% for category in series_categories %}{% if category.series %}<section class="series-category"><div class="series-category-heading"><h2>{{ category.label }}</h2><span>{{ category.series|length }} Serien</span></div>{% for group in category.series %}<details class="series" open><summary>{{ group.title }} <span class="muted">({{ group.year or '—' }}) · {{ group.episodes|length }} Episoden</span></summary><table><thead><tr><th class="col-title">Episode</th><th class="col-quality">Quality</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in group.episodes %}<tr><td><span class="title">{{ r.title }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table></details>{% endfor %}</section>{% endif %}{% endfor %}
|
{% for category in series_categories %}{% if category.series %}<section class="series-category"><div class="series-category-heading"><h2>{{ category.label }}</h2><span>{{ category.series|length }} Serien</span></div>{% for group in category.series %}<details class="series" open><summary>{{ group.title }} <span class="muted">({{ group.year or '—' }}) · {{ group.episodes|length }} Episoden</span></summary><table><thead><tr><th class="col-title">Episode</th><th class="col-quality">Quality</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in group.episodes %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or '') ~ ' ' ~ group.title)|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table></details>{% endfor %}</section>{% endif %}{% endfor %}
|
||||||
</div></section>{% endif %}
|
</div></section>{% endif %}
|
||||||
<section id="missing" class="view" data-source="missing"><div class="stats"><span class="stat" id="missingCount">{{ missing_german_movies|length + (missing_german_series|map(attribute='episodes')|map('length')|sum) }} Einträge ohne Deutsch</span><span class="stat scan" id="missingStatus">Scan wird vorbereitet …</span><span class="stat scan" id="seasonZeroHint">Vorhanden oder wird gerade geladen · Staffel 0 ausgeblendet</span></div><div class="tablewrap missing-table">
|
<section id="missing" class="view" data-source="missing"><div class="stats"><span class="stat" id="missingCount">{{ missing_german_movies|length + (missing_german_series|map(attribute='episodes')|map('length')|sum) }} Einträge ohne Deutsch</span><span class="stat scan" id="missingStatus">Scan wird vorbereitet …</span><span class="stat scan" id="seasonZeroHint">Vorhanden oder wird gerade geladen · Staffel 0 ausgeblendet</span></div><div class="tablewrap missing-table">
|
||||||
<table id="missingMovies"><thead><tr><th class="col-title">Film</th><th class="col-quality">Qualität</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in missing_german_movies %}<tr><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state bad">Ohne Deutsch</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table>
|
<table id="missingMovies"><thead><tr><th class="col-title">Film</th><th class="col-quality">Qualität</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in missing_german_movies %}<tr><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state bad">Ohne Deutsch</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table>
|
||||||
@@ -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=`<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 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 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 navigation=document.querySelector('.nav');if(navigation&&!navigation.querySelector('[data-view="datastore"]')){const datastoreButton=document.createElement('button');datastoreButton.dataset.view='datastore';datastoreButton.innerHTML='<img class="nav-icon" src="https://api.iconify.design/mdi:harddisk.svg?color=%2360a9e6" alt="" aria-hidden="true"><span data-de="Datastore" data-en="Datastore">Datastore</span>';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');
|
const navigation=document.querySelector('.nav');if(navigation&&!navigation.querySelector('[data-view="datastore"]')){const datastoreButton=document.createElement('button');datastoreButton.dataset.view='datastore';datastoreButton.innerHTML='<img class="nav-icon" src="https://api.iconify.design/mdi:harddisk.svg?color=%2360a9e6" alt="" aria-hidden="true"><span data-de="Datastore" data-en="Datastore">Datastore</span>';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();
|
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();
|
||||||
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();
|
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 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')}
|
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')}
|
||||||
@@ -170,12 +170,12 @@ html,body{min-height:100%;background:#151619}body{min-height:100vh;background:ra
|
|||||||
header{height:64px;background:rgba(31,33,38,.9);backdrop-filter:blur(18px);border-bottom:1px solid var(--border-soft);box-shadow:0 8px 30px rgba(0,0,0,.2);position:sticky;top:0;z-index:20}.logo{font-size:23px;text-shadow:0 0 18px rgba(244,196,48,.24)}.head-title{font-size:14px;letter-spacing:.01em}.head-sub{color:#9da5b2}
|
header{height:64px;background:rgba(31,33,38,.9);backdrop-filter:blur(18px);border-bottom:1px solid var(--border-soft);box-shadow:0 8px 30px rgba(0,0,0,.2);position:sticky;top:0;z-index:20}.logo{font-size:23px;text-shadow:0 0 18px rgba(244,196,48,.24)}.head-title{font-size:14px;letter-spacing:.01em}.head-sub{color:#9da5b2}
|
||||||
main{max-width:1800px;margin:0 auto;padding:18px 20px 42px}.nav{gap:6px;margin:0 0 16px;padding:4px;border:1px solid var(--border-soft);border-radius:9px;background:rgba(31,33,38,.72);box-shadow:0 8px 24px rgba(0,0,0,.12)}.nav button{padding:9px 14px;border-radius:6px;color:#aeb5c1}.nav button.active{background:linear-gradient(135deg,#3a3f48,#2d3139);box-shadow:inset 0 1px rgba(255,255,255,.08),0 4px 12px rgba(0,0,0,.18);color:#fff}.nav button.active::after{content:"";display:block;height:2px;margin:6px 2px -4px;border-radius:2px;background:var(--accent-blue)}
|
main{max-width:1800px;margin:0 auto;padding:18px 20px 42px}.nav{gap:6px;margin:0 0 16px;padding:4px;border:1px solid var(--border-soft);border-radius:9px;background:rgba(31,33,38,.72);box-shadow:0 8px 24px rgba(0,0,0,.12)}.nav button{padding:9px 14px;border-radius:6px;color:#aeb5c1}.nav button.active{background:linear-gradient(135deg,#3a3f48,#2d3139);box-shadow:inset 0 1px rgba(255,255,255,.08),0 4px 12px rgba(0,0,0,.18);color:#fff}.nav button.active::after{content:"";display:block;height:2px;margin:6px 2px -4px;border-radius:2px;background:var(--accent-blue)}
|
||||||
.toolbar{padding:1px 10px;border:1px solid var(--border-soft);border-radius:8px;background:rgba(35,37,42,.72);box-shadow:0 8px 20px rgba(0,0,0,.12);gap:8px}.toolbar input,.toolbar select{height:30px;padding:5px 9px;background:#2b2e34;border-color:#474d57;transition:border-color .2s,box-shadow .2s,background .2s}.toolbar input:focus,.toolbar select:focus{outline:none;background:#30343b;border-color:var(--accent-blue);box-shadow:0 0 0 3px rgba(96,169,230,.14)}
|
.toolbar{padding:1px 10px;border:1px solid var(--border-soft);border-radius:8px;background:rgba(35,37,42,.72);box-shadow:0 8px 20px rgba(0,0,0,.12);gap:8px}.toolbar input,.toolbar select{height:30px;padding:5px 9px;background:#2b2e34;border-color:#474d57;transition:border-color .2s,box-shadow .2s,background .2s}.toolbar input:focus,.toolbar select:focus{outline:none;background:#30343b;border-color:var(--accent-blue);box-shadow:0 0 0 3px rgba(96,169,230,.14)}
|
||||||
.view#radarr{display:none}.view#radarr.active{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:10px;align-items:start}.view#radarr>.toolbar{grid-column:1;grid-row:1;height:34px;margin-bottom:0}.view#radarr>.stats{grid-column:2;grid-row:1;height:34px;margin:0;align-self:stretch;align-items:stretch;flex-wrap:nowrap;white-space:nowrap}.view#radarr>.stats>.stat{height:34px;display:flex;align-items:center}.view#radarr>.tablewrap{grid-column:1/-1;grid-row:2}
|
.view#sonarr{display:none}.view#sonarr.active{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:10px;align-items:start}.view#sonarr>.series-toolbar{grid-column:1;grid-row:1;height:34px;margin-bottom:0}.view#sonarr>.stats{grid-column:2;grid-row:1;height:34px;margin:0;align-self:stretch;align-items:stretch;flex-wrap:nowrap;white-space:nowrap}.view#sonarr>.stats>.stat{height:34px;display:flex;align-items:center}.view#sonarr>#seriesList{grid-column:1/-1;grid-row:2}.view#radarr{display:none}.view#radarr.active{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:10px;align-items:start}.view#radarr>.toolbar{grid-column:1;grid-row:1;height:34px;margin-bottom:0}.view#radarr>.stats{grid-column:2;grid-row:1;height:34px;margin:0;align-self:stretch;align-items:stretch;flex-wrap:nowrap;white-space:nowrap}.view#radarr>.stats>.stat{height:34px;display:flex;align-items:center}.view#radarr>.tablewrap{grid-column:1/-1;grid-row:2}
|
||||||
.stats{gap:9px;margin:12px 0}.stat{background:linear-gradient(135deg,rgba(48,52,60,.9),rgba(34,36,41,.9));border:1px solid var(--border-soft);border-radius:8px;padding:7px 11px;box-shadow:0 5px 15px rgba(0,0,0,.12)}.stat:first-child{color:#fff;font-weight:700}.scan{color:#f4c430!important}
|
.stats{gap:9px;margin:12px 0}.stat{background:linear-gradient(135deg,rgba(48,52,60,.9),rgba(34,36,41,.9));border:1px solid var(--border-soft);border-radius:8px;padding:7px 11px;box-shadow:0 5px 15px rgba(0,0,0,.12)}.stat:first-child{color:#fff;font-weight:700}.scan{color:#f4c430!important}
|
||||||
.tablewrap{border:1px solid var(--border-soft);border-radius:10px;background:rgba(31,33,38,.84);box-shadow:0 14px 34px rgba(0,0,0,.2)}table{border-spacing:0}thead th{height:38px;background:linear-gradient(180deg,#30343b,#292c32);color:#bfc6d1;text-transform:uppercase;letter-spacing:.07em;font-size:10px}tbody td{height:42px;border-bottom-color:rgba(255,255,255,.075)}tbody tr{transition:background .2s,transform .2s}tbody tr:hover{background:rgba(96,169,230,.065);transform:translateX(1px)}.title{font-size:13px}.quality{box-shadow:0 0 12px rgba(21,148,71,.18)}
|
.tablewrap{border:1px solid var(--border-soft);border-radius:10px;background:rgba(31,33,38,.84);box-shadow:0 14px 34px rgba(0,0,0,.2)}table{border-spacing:0}thead th{height:38px;background:linear-gradient(180deg,#30343b,#292c32);color:#bfc6d1;text-transform:uppercase;letter-spacing:.07em;font-size:10px}tbody td{height:42px;border-bottom-color:rgba(255,255,255,.075)}tbody tr{transition:background .2s,transform .2s}tbody tr:hover{background:rgba(96,169,230,.065);transform:translateX(1px)}.title{font-size:13px}.quality{box-shadow:0 0 12px rgba(21,148,71,.18)}
|
||||||
#rescan{padding:9px 13px;background:linear-gradient(135deg,#343943,#292c32);border-color:#515865;box-shadow:0 5px 16px rgba(0,0,0,.18);transition:transform .2s,box-shadow .2s,border-color .2s}#rescan:hover{transform:translateY(-2px);border-color:var(--accent-blue);box-shadow:0 8px 20px rgba(0,0,0,.28)}
|
#rescan{padding:9px 13px;background:linear-gradient(135deg,#343943,#292c32);border-color:#515865;box-shadow:0 5px 16px rgba(0,0,0,.18);transition:transform .2s,box-shadow .2s,border-color .2s}#rescan:hover{transform:translateY(-2px);border-color:var(--accent-blue);box-shadow:0 8px 20px rgba(0,0,0,.28)}
|
||||||
.actions{display:flex;align-items:center;gap:7px}.lang-switch,.settings-button{height:36px;min-width:36px;padding:0 9px;background:rgba(48,52,60,.9);border-color:#515865;color:#d9dee7;font-weight:700}.settings-button{font-size:17px;padding:0}.lang-switch:hover,.settings-button:hover{border-color:var(--accent-blue);background:#363c47}.settings-backdrop{position:fixed;inset:0;z-index:200;display:grid;place-items:center;background:rgba(5,7,10,.68);backdrop-filter:blur(8px);animation:fadeIn .2s ease}.settings-backdrop.hidden{display:none}.settings-modal{width:min(440px,calc(100vw - 28px));padding:22px;border:1px solid #4a5362;border-radius:14px;background:linear-gradient(145deg,#292e36,#1f2228);box-shadow:0 24px 80px rgba(0,0,0,.55);animation:modalIn .28s cubic-bezier(.2,.8,.2,1)}.settings-head{display:flex;justify-content:space-between;gap:20px;margin-bottom:22px}.settings-head h2{margin:0 0 5px;font-size:19px}.settings-head p{margin:0;color:var(--muted);font-size:12px}.settings-close{padding:0;width:30px;height:30px;border:0;background:transparent;font-size:25px;color:#aeb5c1}.settings-modal label{display:block;margin:15px 0 7px;color:#cbd1db;font-size:12px;font-weight:700}.settings-modal select{width:100%;height:38px;background:#20242a;border-color:#505967}.settings-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}.settings-actions button{padding:9px 14px}.settings-save{background:#176f40;border-color:#23965b;color:#e4ffef}.settings-save:hover{background:#1c8850}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes modalIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:none}}
|
.actions{display:flex;align-items:center;gap:7px}.lang-switch,.settings-button{height:36px;min-width:36px;padding:0 9px;background:rgba(48,52,60,.9);border-color:#515865;color:#d9dee7;font-weight:700}.settings-button{font-size:17px;padding:0}.lang-switch:hover,.settings-button:hover{border-color:var(--accent-blue);background:#363c47}.settings-backdrop{position:fixed;inset:0;z-index:200;display:grid;place-items:center;background:rgba(5,7,10,.68);backdrop-filter:blur(8px);animation:fadeIn .2s ease}.settings-backdrop.hidden{display:none}.settings-modal{width:min(440px,calc(100vw - 28px));padding:22px;border:1px solid #4a5362;border-radius:14px;background:linear-gradient(145deg,#292e36,#1f2228);box-shadow:0 24px 80px rgba(0,0,0,.55);animation:modalIn .28s cubic-bezier(.2,.8,.2,1)}.settings-head{display:flex;justify-content:space-between;gap:20px;margin-bottom:22px}.settings-head h2{margin:0 0 5px;font-size:19px}.settings-head p{margin:0;color:var(--muted);font-size:12px}.settings-close{padding:0;width:30px;height:30px;border:0;background:transparent;font-size:25px;color:#aeb5c1}.settings-modal label{display:block;margin:15px 0 7px;color:#cbd1db;font-size:12px;font-weight:700}.settings-modal select{width:100%;height:38px;background:#20242a;border-color:#505967}.settings-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}.settings-actions button{padding:9px 14px}.settings-save{background:#176f40;border-color:#23965b;color:#e4ffef}.settings-save:hover{background:#1c8850}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes modalIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:none}}
|
||||||
@media(max-width:760px){header{padding:0 12px}.head-sub{display:none}main{padding:12px}.view#radarr.active{display:block}.view#radarr>.toolbar{height:auto;margin-bottom:10px}.view#radarr>.stats{height:auto;align-items:initial;flex-wrap:wrap}.view#radarr>.stats>.stat{height:auto}.toolbar{grid-template-columns:1fr}.actions{margin-left:auto}.nav{overflow-x:auto}.nav button{white-space:nowrap}.tablewrap{overflow-x:auto}table{min-width:900px}}
|
@media(max-width:760px){header{padding:0 12px}.head-sub{display:none}main{padding:12px}.view#radarr.active,.view#sonarr.active{display:block}.view#radarr>.toolbar,.view#sonarr>.series-toolbar{height:auto;margin-bottom:10px}.view#radarr>.stats,.view#sonarr>.stats{height:auto;align-items:initial;flex-wrap:wrap}.view#radarr>.stats>.stat,.view#sonarr>.stats>.stat{height:auto}.toolbar{grid-template-columns:1fr}.actions{margin-left:auto}.nav{overflow-x:auto}.nav button{white-space:nowrap}.tablewrap{overflow-x:auto}table{min-width:900px}}
|
||||||
main{min-height:calc(100vh - 64px)}
|
main{min-height:calc(100vh - 64px)}
|
||||||
.download-table{display:grid;gap:14px;border:0;background:transparent;box-shadow:none;overflow:visible}.download-section{border:1px solid rgba(255,255,255,.09);border-radius:12px;overflow:hidden;background:rgba(31,33,38,.84);box-shadow:0 12px 28px rgba(0,0,0,.18)}.download-section-head{display:flex;align-items:center;justify-content:space-between;padding:13px 15px;border-bottom:1px solid rgba(255,255,255,.08);background:linear-gradient(100deg,rgba(46,56,69,.9),rgba(36,39,45,.9))}.download-section-head h3{margin:0;color:#e8eef5;font-size:14px}.download-section-head span{min-width:24px;padding:3px 8px;border-radius:999px;background:rgba(96,169,230,.16);color:#9ed2f5;font-size:11px;text-align:center}.download-secondary .download-section-head{background:linear-gradient(100deg,rgba(53,47,47,.9),rgba(38,37,41,.9))}.download-secondary .download-section-head span{background:rgba(244,196,48,.15);color:#f4d267}.download-section table{background:transparent}.download-section tbody tr:hover{background:rgba(96,169,230,.07)}
|
.download-table{display:grid;gap:14px;border:0;background:transparent;box-shadow:none;overflow:visible}.download-section{border:1px solid rgba(255,255,255,.09);border-radius:12px;overflow:hidden;background:rgba(31,33,38,.84);box-shadow:0 12px 28px rgba(0,0,0,.18)}.download-section-head{display:flex;align-items:center;justify-content:space-between;padding:13px 15px;border-bottom:1px solid rgba(255,255,255,.08);background:linear-gradient(100deg,rgba(46,56,69,.9),rgba(36,39,45,.9))}.download-section-head h3{margin:0;color:#e8eef5;font-size:14px}.download-section-head span{min-width:24px;padding:3px 8px;border-radius:999px;background:rgba(96,169,230,.16);color:#9ed2f5;font-size:11px;text-align:center}.download-secondary .download-section-head{background:linear-gradient(100deg,rgba(53,47,47,.9),rgba(38,37,41,.9))}.download-secondary .download-section-head span{background:rgba(244,196,48,.15);color:#f4d267}.download-section table{background:transparent}.download-section tbody tr:hover{background:rgba(96,169,230,.07)}
|
||||||
</style>
|
</style>
|
||||||
@@ -224,7 +224,7 @@ function translateLayout(language){
|
|||||||
document.querySelectorAll('[data-de][data-en]').forEach(node=>{node.textContent=node.dataset[language]});
|
document.querySelectorAll('[data-de][data-en]').forEach(node=>{node.textContent=node.dataset[language]});
|
||||||
document.querySelector('#languageToggle').textContent=language==='de'?'EN':'DE';
|
document.querySelector('#languageToggle').textContent=language==='de'?'EN':'DE';
|
||||||
const navLabels={dashboard:['▦ Dashboard','▦ Dashboard'],radarr:['RADARR · Filme','RADARR · Movies'],sonarr:['SONARR · Serien','SONARR · Series'],missing:['⚠ Ohne Deutsch','⚠ Without German'],downloads:['SABNZBD · Downloads','SABNZBD · Downloads']};document.querySelectorAll('.nav button').forEach(button=>{const labels=navLabels[button.dataset.view];if(labels)button.childNodes[0].textContent=labels[language==='de'?0:1]});
|
const navLabels={dashboard:['▦ Dashboard','▦ Dashboard'],radarr:['RADARR · Filme','RADARR · Movies'],sonarr:['SONARR · Serien','SONARR · Series'],missing:['⚠ Ohne Deutsch','⚠ Without German'],downloads:['SABNZBD · Downloads','SABNZBD · Downloads']};document.querySelectorAll('.nav button').forEach(button=>{const labels=navLabels[button.dataset.view];if(labels)button.childNodes[0].textContent=labels[language==='de'?0:1]});
|
||||||
const searchInput=document.querySelector('#search');if(searchInput)searchInput.placeholder=t.searchMovie;
|
const searchInput=document.querySelector('#search');if(searchInput)searchInput.placeholder=t.searchMovie;const seriesSearchInput=document.querySelector('#seriesSearch');if(seriesSearchInput)seriesSearchInput.placeholder=t.searchSeries;
|
||||||
const filterOptions=[['#audioFilter',t.allAudio],['#subFilter',t.allSubs],['#stateFilter',t.allStatus]];filterOptions.forEach(([selector,label])=>{const first=document.querySelector(`${selector} option:first-child`);if(first)first.textContent=label});
|
const filterOptions=[['#audioFilter',t.allAudio],['#subFilter',t.allSubs],['#stateFilter',t.allStatus]];filterOptions.forEach(([selector,label])=>{const first=document.querySelector(`${selector} option:first-child`);if(first)first.textContent=label});
|
||||||
document.querySelectorAll('#movies thead th').forEach((th,i)=>{th.textContent=[t.film,t.quality,t.status,t.audio,t.subs,t.video,t.size][i]||th.textContent});
|
document.querySelectorAll('#movies thead th').forEach((th,i)=>{th.textContent=[t.film,t.quality,t.status,t.audio,t.subs,t.video,t.size][i]||th.textContent});
|
||||||
document.querySelectorAll('#seriesList thead th').forEach((th,i)=>{th.textContent=[t.episode,t.quality,t.status,t.audio,t.subs,t.video,t.size][i]||th.textContent});
|
document.querySelectorAll('#seriesList thead th').forEach((th,i)=>{th.textContent=[t.episode,t.quality,t.status,t.audio,t.subs,t.video,t.size][i]||th.textContent});
|
||||||
|
|||||||
Reference in New Issue
Block a user