Files
language-dashboard/templates/index.html
T

151 lines
25 KiB
HTML

{% macro icon(name, class='') -%}
<svg class="icon {{ class }}" aria-hidden="true"><use href="#icon-{{ name }}"/></svg>
{%- endmacro %}
{% macro tr(de, en) -%}<span data-de="{{ de }}" data-en="{{ en }}">{{ de }}</span>{%- endmacro %}
{% macro languages(items) -%}
<div class="badges">{% for l in items %}<span class="badge {{ 'unknown' if l.code == 'und' else '' }}" data-language="{{ l.name }}" title="{{ 'Quelle: Radarr/Sonarr-Dateimetadaten (nicht direkt bestätigt)' if l.source == 'arr' else 'Quelle: Mediendatei / Spurtitel' }}">{{ l.flag }} {{ l.name }}{% if l.source == 'arr' %}<sup>*</sup>{% endif %}</span>{% else %}<span class="muted">—</span>{% endfor %}</div>
{%- endmacro %}
{% macro media_table(items, id='', episode=false, review=false) -%}
<div class="table-scroll"><table class="media-table" {% if id %}id="{{ id }}"{% endif %}>
<thead><tr><th class="col-title">{{ tr('Episode' if episode else 'Film', 'Episode' if episode else 'Movie') }}</th><th>{{ tr('Qualität', 'Quality') }}</th><th>{{ tr('Status', 'Status') }}</th><th class="col-languages">Audio</th><th class="col-languages">{{ tr('Untertitel', 'Subtitles') }}</th><th>{{ tr('Video', 'Video') }}</th><th>{{ tr('Größe', 'Size') }}</th></tr></thead>
<tbody>{% for r in items %}
<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 }}" data-season="{{ r.season if episode else '' }}">
<td><span class="title" title="{{ r.title }}">{{ r.title }}</span>{% if r.year %}<span class="year">{{ r.year }}</span>{% endif %}{% if r.error %}<span class="error-hint" title="{{ r.error }}">{{ icon('alert') }}</span>{% endif %}</td>
<td><span class="badge {{ 'quality' if not r.missing else '' }}">{{ r.quality }}</span></td>
<td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td>
<td>{{ languages(r.audio_languages) }}</td><td>{{ languages(r.subtitle_languages) }}</td><td class="muted">{{ r.video_codec }}</td><td class="nowrap">{{ r.size }}</td>
</tr>{% else %}<tr class="empty-row"><td colspan="7" class="empty-state">{{ tr('Keine Einträge vorhanden.', 'No entries available.') }}</td></tr>{% endfor %}</tbody>
</table></div>
{%- endmacro %}
{% macro filters(prefix, series=false) -%}
<div class="toolbar" data-filter="{{ prefix }}">
<label class="search-field">{{ icon('search') }}<input type="search" id="{{ prefix }}Search" aria-label="{{ 'Serie' if series else 'Film' }} suchen" data-placeholder-de="{{ 'Serie' if series else 'Film' }} suchen …" data-placeholder-en="Search {{ 'series' if series else 'movies' }} …" placeholder="{{ 'Serie' if series else 'Film' }} suchen …"></label>
<select id="{{ prefix }}Audio" aria-label="Audio-Sprache"><option value="" data-de="Alle Audio-Sprachen" data-en="All audio languages">Alle Audio-Sprachen</option>{% for name in language_names %}<option>{{ name }}</option>{% endfor %}</select>
<select id="{{ prefix }}Subs" aria-label="Untertitel-Sprache"><option value="" data-de="Alle Untertitel" data-en="All subtitles">Alle Untertitel</option>{% for name in language_names %}<option>{{ name }}</option>{% endfor %}</select>
<select id="{{ prefix }}State" aria-label="Status"><option value="" data-de="Alle Status" data-en="All statuses">Alle Status</option>{% for name in ['DE + EN', 'Deutsch', 'English only', 'Andere', 'Sprache prüfen', 'Scan ausstehend', 'Fehler', 'Downloading'] %}<option>{{ name }}</option>{% endfor %}</select>
</div>
{%- endmacro %}
{% macro nav_button(view, symbol, de, en, count_id='') -%}
<button type="button" data-view="{{ view }}" {{ 'class=active' if view == 'dashboard' else '' }}>{{ icon(symbol) }}{{ tr(de, en) }}{% if count_id %}<span id="{{ count_id }}" class="nav-count">0</span>{% endif %}</button>
{%- endmacro %}
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#101113"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes">
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}"><link rel="apple-touch-icon" href="{{ url_for('static', filename='media-mark-192.png') }}"><link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='media-mark.svg') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='dashboard.css') }}?v=8"><title>Media Max</title>
<script src="{{ url_for('static', filename='dashboard.js') }}?v=8" defer></script>
</head>
<body>
<svg class="svg-definitions" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<symbol id="icon-dashboard" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></symbol>
<symbol id="icon-movie" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M8 4l3 5m4-5 3 5"/></symbol>
<symbol id="icon-series" viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="14" rx="2"/><path d="m8 2 4 4 4-4M8 23h8"/></symbol>
<symbol id="icon-alert" viewBox="0 0 24 24"><path d="m10.3 4-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.7-3l-8-14a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4m0 4h.01"/></symbol>
<symbol id="icon-download" viewBox="0 0 24 24"><path d="M12 3v12m-5-5 5 5 5-5M4 15v5h16v-5"/></symbol>
<symbol id="icon-disk" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="12" cy="10" r="4"/><path d="m12 10 5 5M6 18h.01"/></symbol>
<symbol id="icon-chip" viewBox="0 0 24 24"><rect x="5" y="5" width="14" height="14" rx="2"/><rect x="9" y="9" width="6" height="6" rx="1"/><path d="M9 1v4m6-4v4M9 19v4m6-4v4M1 9h4m-4 6h4m14-6h4m-4 6h4"/></symbol>
<symbol id="icon-settings" viewBox="0 0 24 24"><path d="m9 3-1 3-3 1-2 5 2 5 3 1 1 3h6l1-3 3-1 2-5-2-5-3-1-1-3Z"/><circle cx="12" cy="12" r="3"/></symbol>
<symbol id="icon-search" viewBox="0 0 24 24"><circle cx="10" cy="10" r="6"/><path d="m15 15 6 6"/></symbol>
<symbol id="icon-refresh" viewBox="0 0 24 24"><path d="M20 8a8 8 0 1 0 0 8M20 3v5h-5"/></symbol>
<symbol id="icon-menu" viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></symbol>
<symbol id="icon-close" viewBox="0 0 24 24"><path d="m6 6 12 12M6 18 18 6"/></symbol>
<symbol id="icon-activity" viewBox="0 0 24 24"><path d="M2 12h5l3-8 4 16 3-8h5"/></symbol>
<symbol id="icon-server" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="7" rx="2"/><rect x="3" y="14" width="18" height="7" rx="2"/><path d="M7 6.5h.01M7 17.5h.01M12 6.5h5M12 17.5h5"/></symbol>
<symbol id="icon-check" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6"/></symbol>
<symbol id="icon-language" viewBox="0 0 24 24"><path d="M3 5h12M9 2v3M5 5c1 5 4 8 8 10M4 15c4-2 7-5 8-10m2 16 4-10 4 10m-6-4h4"/></symbol>
</svg>
<a class="skip-link" href="#main">Zum Inhalt</a>
<button id="sidebarShade" class="sidebar-shade" aria-label="Navigation schließen" tabindex="-1"></button>
<aside class="sidebar" id="sidebar" aria-label="Hauptnavigation">
<a class="brand" href="/">{{ icon('activity', 'brand-icon') }}<span>Media<span class="brand-accent"> Max</span></span></a>
<div class="workspace">{{ icon('server') }}<div><strong>Media Stack</strong><small>{{ tr('Bibliothek & Monitoring', 'Library & monitoring') }}</small></div><span class="status-dot"></span></div>
<nav class="nav">
<div class="nav-label">{{ tr('Übersicht', 'Overview') }}</div>
{{ nav_button('dashboard', 'dashboard', 'Dashboard', 'Dashboard') }}
<div class="nav-label">{{ tr('Bibliothek', 'Library') }}</div>
{{ nav_button('radarr', 'movie', 'Filme', 'Movies') }}
{% if sonarr_enabled %}{{ nav_button('sonarr', 'series', 'Serien', 'Series') }}{% endif %}
{{ nav_button('missing', 'language', 'Ohne Deutsch', 'Without German', 'missingNavCount') }}
{% if sab_enabled %}{{ nav_button('downloads', 'download', 'Downloads', 'Downloads', 'downloadNavCount') }}{% endif %}
<div class="nav-label">{{ tr('System', 'System') }}</div>
{{ nav_button('datastore', 'disk', 'Speicher & RAID', 'Storage & RAID') }}
{{ nav_button('graphics', 'chip', 'Graphics', 'Graphics') }}
</nav>
<div class="sidebar-bottom"><button id="settingsOpen">{{ icon('settings') }}{{ tr('Einstellungen', 'Settings') }}</button><div class="sidebar-footer"><span class="status-dot"></span> Media Max <span>Self-hosted</span></div></div>
</aside>
<div class="app-shell">
<header class="topbar">
<button id="menuToggle" class="icon-button menu-toggle" aria-label="Navigation öffnen" aria-controls="sidebar" aria-expanded="false">{{ icon('menu') }}</button>
<div class="breadcrumb">Media Max <span>/</span> <strong id="pageTitle">Dashboard</strong></div>
<div class="actions">
<button id="languageToggle" class="icon-button" aria-label="Sprache wechseln">EN</button>
<div class="scan-control">
<select id="scanSource" aria-label="Scan-Quelle">
<option value="all" data-de="Alle Quellen" data-en="All sources">Alle Quellen</option>
<option value="radarr">Radarr</option>
{% if sonarr_enabled %}<option value="sonarr">Sonarr</option>{% endif %}
<option value="missing" data-de="Ohne Deutsch" data-en="Without German">Ohne Deutsch</option>
</select>
<button id="rescan" class="primary-button">{{ icon('refresh') }}{{ tr('Neu scannen', 'Rescan') }}</button>
</div>
</div>
</header>
<main id="main">
{% if error %}<div class="banner">{{ icon('alert') }}{{ error }}</div>{% endif %}
{% if not library_ready %}<div class="notice">{{ tr('Bibliothek wird synchronisiert. Diese Ansicht aktualisiert sich automatisch.', 'Library is syncing. This view will update automatically.') }}</div>{% endif %}
<div id="actionError" class="banner hidden" role="alert"></div>
<section id="dashboard" class="view active">
<div class="page-heading"><div><h1>{{ tr('Dashboard', 'Dashboard') }}</h1><p>{{ tr('Deine Medien. Deine Sprachen. Dein System.', 'Your media. Your languages. Your system.') }}</p></div><span class="live-label"><span class="status-dot"></span>{{ tr('Live-Monitoring', 'Live monitoring') }}</span></div>
<div class="summary-grid">
<button class="summary-card" data-go="radarr"><span class="summary-icon">{{ icon('movie') }}</span><div><strong>{{ rows|length }}</strong><span>{{ tr('Filme', 'Movies') }}</span><small>Radarr · {{ dashboard_stats.movie_files }} {{ tr('vorhanden', 'available') }}</small></div></button>
<button class="summary-card" {% if sonarr_enabled %}data-go="sonarr"{% endif %}><span class="summary-icon purple">{{ icon('series') }}</span><div><strong>{{ series|length }}</strong><span>{{ tr('Serien', 'Series') }}</span><small>{{ dashboard_stats.episode_files }} {{ tr('Episoden vorhanden', 'episodes available') }}</small></div></button>
<button class="summary-card" data-go="missing"><span class="summary-icon amber">{{ icon('language') }}</span><div><strong id="dashboardMissing">0</strong><span>{{ tr('Ohne Deutsch', 'Without German') }}</span><small>{{ tr('Audio & Untertitel prüfen', 'Review audio & subtitles') }}</small></div></button>
<button class="summary-card" {% if sab_enabled %}data-go="downloads"{% endif %}><span class="summary-icon green">{{ icon('download') }}</span><div><strong id="dashboardDownloads">{{ '—' if sab_enabled else '0' }}</strong><span>{{ tr('Downloads', 'Downloads') }}</span><small id="dashboardRemaining">{{ 'SABnzbd …' if sab_enabled else 'SABnzbd nicht konfiguriert' }}</small></div></button>
</div>
<div class="section-title">{{ icon('movie') }}<h2>{{ tr('Bibliotheksübersicht', 'Library overview') }}</h2></div>
<div class="dashboard-grid">
<article class="panel library-panel"><div class="panel-heading"><h3>{{ tr('Medienbestand', 'Media collection') }}</h3><span class="muted">Radarr / Sonarr</span></div>
{% for de, en, files, total, missing in [('Filme', 'Movies', dashboard_stats.movie_files, dashboard_stats.movie_total, dashboard_stats.movie_missing), ('Episoden', 'Episodes', dashboard_stats.episode_files, dashboard_stats.episode_total, dashboard_stats.episode_missing)] %}
<div class="coverage-row"><div class="metric-line"><span>{{ tr(de, en) }}</span><strong>{{ files }} <span class="muted">/ {{ total }}</span></strong></div><div class="track"><span style="width:{{ files / total * 100 if total else 0 }}%"></span></div><small class="muted">{{ missing }} {{ tr('ohne Datei', 'without a file') }}</small></div>{% endfor %}
<div class="library-footer">{{ icon('refresh') }}<span>{{ tr('Automatischer Sync', 'Automatic sync') }} · {{ (sync_interval_seconds / 60)|round|int if sync_interval_seconds else '—' }} {{ tr('Minuten', 'minutes') }}</span></div>
</article>
<article class="panel"><div class="panel-heading"><h3>{{ tr('Spracherkennung', 'Language detection') }}</h3>{{ icon('language') }}</div><div class="language-metrics"><div><strong class="text-green">{{ dashboard_stats.german_tracks }}</strong><small>{{ tr('Mit Deutsch', 'With German') }}</small></div><div><strong class="text-amber">{{ dashboard_stats.unknown_audio }}</strong><small>{{ tr('Audio unbekannt', 'Unknown audio') }}</small></div><div><strong>{{ dashboard_stats.pending }}</strong><small>{{ tr('Scan ausstehend', 'Scan pending') }}</small></div><div><strong class="text-red">{{ dashboard_stats.scan_errors }}</strong><small>{{ tr('Scanfehler', 'Scan errors') }}</small></div></div>
<p class="panel-note">{{ tr('Deutsch im Audio oder in Untertiteln zählt als verfügbar. Nicht beschriftete Spuren werden nicht geraten.', 'German audio or subtitles count as available. Unlabelled tracks are not guessed.') }}</p>
{% if language_review %}<div class="review-list">{% for r in language_review %}<div><span class="title" title="{{ r.title }}">{{ r.title }}</span><span class="badge unknown">{{ 'Scanfehler' if r.error else 'Unbekannt' }}</span></div>{% endfor %}</div>{% endif %}
</article>
</div>
<div class="section-title">{{ icon('activity') }}<h2>{{ tr('System & Dienste', 'System & services') }}</h2><span id="systemUpdated" class="muted">—</span></div>
<div class="dashboard-grid system-grid">
<article class="panel"><div class="panel-heading"><h3>{{ tr('Systemauslastung', 'System resources') }}</h3>{{ icon('chip') }}</div><div class="system-meters"><div><div class="metric-line"><span>CPU</span><strong id="cpuValue">—</strong></div><div class="track"><span id="cpuBar"></span></div></div><div><div class="metric-line"><span>RAM</span><strong id="ramValue">—</strong></div><div class="track purple-track"><span id="ramBar"></span></div><small id="ramDetail" class="muted">—</small></div></div><div id="systemDetails" class="system-details"></div><div id="diskList" class="disk-list"></div></article>
<article class="panel"><div class="panel-heading"><h3>{{ tr('Verbundene Dienste', 'Connected services') }}</h3><span id="serviceSummary" class="badge">—</span></div><div id="serviceList" class="service-list"><div class="empty-state">{{ tr('Dienste werden geprüft …', 'Checking services …') }}</div></div></article>
</div>
</section>
<section id="radarr" class="view"><div class="page-heading"><div><h1>{{ tr('Filme', 'Movies') }}</h1><p>{{ tr('Qualität, Audio und Untertitel deiner Radarr-Bibliothek.', 'Quality, audio and subtitles in your Radarr library.') }}</p></div><span class="source-label">Radarr</span></div>{{ filters('movie') }}<div class="stats"><span id="movieCount"></span><span id="radarrStatus" class="scan-status">—</span></div><div class="panel table-panel">{{ media_table(rows, 'movies') }}</div><p class="metadata-note">* {{ tr('Sprache aus Radarr/Sonarr-Dateimetadaten, nicht direkt in der Datei bestätigt.', 'Language from Radarr/Sonarr file metadata, not directly confirmed in the file.') }}</p><div id="movieNoResults" class="empty-state hidden">{{ tr('Keine Filme für diese Filter.', 'No movies match these filters.') }}</div></section>
{% if sonarr_enabled %}<section id="sonarr" class="view"><div class="page-heading"><div><h1>{{ tr('Serien', 'Series') }}</h1><p>{{ tr('Deine Serien, nach Kategorien und Staffeln geordnet.', 'Your shows, grouped by category and season.') }}</p></div><span class="source-label">Sonarr</span></div>{{ filters('series', true) }}<div class="stats"><span id="seriesCount"></span><span id="sonarrStatus" class="scan-status">—</span></div><div id="seriesList">{% for category in series_categories %}{% if category.series %}<section class="series-category"><div class="category-heading"><h2>{{ category.label }}</h2><span class="muted">{{ category.series|length }}</span></div>{% for group in category.series %}<details class="series" data-title="{{ group.title|lower }}"><summary>{{ icon('series') }}<strong>{{ group.title }}</strong><span class="muted">{{ group.year or '' }} · {{ group.episodes|length }} {{ tr('Episoden', 'episodes') }}</span></summary>{{ media_table(group.episodes, episode=true) }}</details>{% endfor %}</section>{% endif %}{% endfor %}</div><p class="metadata-note">* {{ tr('Sprache aus Radarr/Sonarr-Dateimetadaten, nicht direkt in der Datei bestätigt.', 'Language from Radarr/Sonarr file metadata, not directly confirmed in the file.') }}</p><div id="seriesNoResults" class="empty-state hidden">{{ tr('Keine Serien für diese Filter.', 'No series match these filters.') }}</div></section>{% endif %}
<section id="missing" class="view"><div class="page-heading"><div><h1>{{ tr('Ohne Deutsch', 'Without German') }}</h1><p>{{ tr('Keine deutsche Audio- oder Untertitelspur erkannt. Unbekannte Spuren bitte prüfen.', 'No German audio or subtitles detected. Please review unknown tracks.') }}</p></div>{{ icon('language') }}</div><div class="stats"><span id="missingCount">—</span><span id="missingStatus" class="scan-status">—</span><span id="seasonZeroHint" class="muted"></span></div><div id="missingList">{% if missing_german_movies %}<h2 class="list-heading">{{ tr('Filme', 'Movies') }}</h2><div class="panel table-panel">{{ media_table(missing_german_movies, 'missingMovies', review=true) }}</div>{% endif %}{% if missing_german_series %}<h2 class="list-heading">{{ tr('Serien', 'Series') }}</h2>{% for group in missing_german_series %}<details class="series missing-series"><summary>{{ icon('series') }}<strong>{{ group.title }}</strong><span class="muted">{{ group.episodes|length }} {{ tr('Episoden', 'episodes') }}</span></summary>{{ media_table(group.episodes, episode=true, review=true) }}</details>{% endfor %}{% endif %}</div><div id="missingEmpty" class="panel empty-state hidden">{{ icon('check') }}{{ tr('Keine Einträge ohne Deutsch in der aktuellen Ansicht.', 'No entries without German in the current view.') }}</div></section>
{% if sab_enabled %}<section id="downloads" class="view"><div class="page-heading"><div><h1>{{ tr('Downloads', 'Downloads') }}</h1><p>{{ tr('Aktuelle Queue und zuletzt abgeschlossene Downloads.', 'Current queue and recently completed downloads.') }}</p></div><span id="sabQueueStatus" class="source-label">SABnzbd</span></div><div class="summary-grid"><article class="summary-card"><span class="summary-icon">{{ icon('download') }}</span><div><strong id="sabRemaining">—</strong><span>{{ tr('Noch zu laden', 'Remaining') }}</span></div></article><article class="summary-card"><span class="summary-icon">{{ icon('movie') }}</span><div><strong id="sabActive">—</strong><span>{{ tr('Queue-Einträge', 'Queue entries') }}</span></div></article><article class="summary-card"><span class="summary-icon">{{ icon('activity') }}</span><div><strong id="sabSpeed">—</strong><span>{{ tr('Geschwindigkeit', 'Speed') }}</span></div></article><article class="summary-card"><span class="summary-icon green">{{ icon('check') }}</span><div><strong id="sabHistory">—</strong><span>{{ tr('Im Verlauf', 'In history') }}</span></div></article></div><div id="downloadError" class="banner hidden"></div>{% for id, de, en in [('activeDownloads', 'Aktive Downloads', 'Active downloads'), ('otherDownloads', 'Queue & Verlauf', 'Queue & history')] %}<div class="section-title">{{ icon('download') }}<h2>{{ tr(de, en) }}</h2><span id="{{ id }}Count" class="badge">0</span></div><div class="panel table-panel table-scroll"><table class="download-table"><thead><tr><th class="col-title">Download</th><th>Status</th><th>{{ tr('Fortschritt', 'Progress') }}</th><th>{{ tr('Größe / Rest', 'Size / remaining') }}</th><th>{{ tr('Restzeit', 'Time left') }}</th><th>{{ tr('Quelle', 'Source') }}</th><th>{{ tr('Sprache', 'Language') }}</th></tr></thead><tbody id="{{ id }}Body"></tbody></table></div>{% endfor %}</section>{% endif %}
<section id="datastore" class="view"><div class="page-heading"><div><h1>{{ tr('Speicher & RAID', 'Storage & RAID') }}</h1><p>{{ tr('Speicherbelegung, Medienkategorien und Zustand deiner Laufwerke.', 'Storage usage, media categories and drive health.') }}</p></div><span id="datastoreUpdated" class="muted">—</span></div><div id="storageWarning" class="banner hidden"></div><div class="summary-grid">{% for id, de, en in [('Total', 'Gesamtspeicher', 'Total storage'), ('Used', 'Belegt', 'Used'), ('Free', 'Verfügbar', 'Available'), ('Percent', 'Auslastung', 'Usage')] %}<article class="summary-card"><span class="summary-icon">{{ icon('disk') }}</span><div><strong id="datastore{{ id }}">—</strong><span>{{ tr(de, en) }}</span></div></article>{% endfor %}</div><div class="dashboard-grid"><article class="panel"><div class="panel-heading"><h3>{{ tr('Medienkategorien', 'Media categories') }}</h3><code id="datastorePath">—</code></div><div class="coverage-row"><div id="datastoreCapacityLabel" class="muted">—</div><div class="track"><span id="datastoreCapacityBar"></span></div></div><div id="datastoreCategories"></div></article><div class="panel-stack"><article class="panel"><div class="panel-heading"><h3>I/O Pressure</h3><strong id="ioPressureLabel">—</strong></div><p class="panel-note">{{ tr('Festplatten-Druck aus Linux PSI · letzte 10 Sekunden', 'Disk pressure from Linux PSI · last 10 seconds') }}</p>{% for name in ['Some', 'Full'] %}<div class="pressure-row"><span>{{ name }}</span><strong id="ioPressure{{ name }}">—</strong><div class="track"><span id="ioPressure{{ name }}Bar"></span></div></div>{% endfor %}</article><article class="panel raid-panel"><div class="panel-heading"><div><h3>RAID</h3><code id="raidDevice">—</code></div><span id="raidStatusBadge" class="badge">—</span></div><div id="raidWarning" class="banner hidden"></div><div id="raidSummary" class="system-details"></div><div id="raidRecovery" class="recovery-box hidden"><div class="metric-line"><strong>Rebuild / Resync</strong><strong id="raidRecoveryPercent">—</strong></div><div class="track"><span id="raidRecoveryBar"></span></div><div class="metric-line muted"><span id="raidRecoverySpeed">—</span><span id="raidRecoveryFinish">—</span></div></div><div id="raidDisks" class="raid-disks"></div></article></div></div></section>
<section id="graphics" class="view"><div class="page-heading"><div><h1>NVIDIA Graphics</h1><p>{{ tr('GPU-Auslastung, Temperatur, VRAM und Transcoding-Prozesse.', 'GPU usage, temperature, VRAM and transcoding processes.') }}</p></div><span id="graphicsStatus" class="source-label">—</span></div><div id="graphicsSummary" class="summary-grid"></div><div id="graphicsCards" class="graphics-cards"></div><div class="section-title">{{ icon('chip') }}<h2>{{ tr('GPU-Prozesse', 'GPU processes') }}</h2></div><div id="graphicsProcesses" class="panel"></div></section>
</main>
</div>
<dialog id="settingsModal" class="settings-modal" aria-labelledby="settingsTitle">
<form method="dialog">
<div class="panel-heading">
<div><h2 id="settingsTitle">{{ tr('Einstellungen', 'Settings') }}</h2><p class="muted">{{ tr('Anzeige- und Sprachpräferenzen', 'Display and language preferences') }}</p></div>
<button class="icon-button" aria-label="Schließen">{{ icon('close') }}</button>
</div>
{% for id, de, en in [('preferredAudio', 'Bevorzugte Audio-Sprache', 'Preferred audio language'), ('preferredSubs', 'Bevorzugte Untertitel-Sprache', 'Preferred subtitle language')] %}
<label for="{{ id }}">{{ tr(de, en) }}</label>
<select id="{{ id }}"><option value="" data-de="Keine Präferenz" data-en="No preference">Keine Präferenz</option>{% for name in language_names %}<option>{{ name }}</option>{% endfor %}</select>
{% endfor %}
<label class="setting-toggle"><input type="checkbox" id="showSeasonZero">{{ tr('Staffel 0 anzeigen', 'Show season 0') }}</label>
<p class="panel-note">{{ tr('Gilt für Serien und die Liste Ohne Deutsch. Automatisches Scanning läuft serverseitig.', 'Applies to series and the Without German list. Automatic scanning runs on the server.') }}</p>
<div class="settings-actions"><button value="cancel">{{ tr('Abbrechen', 'Cancel') }}</button><button id="settingsSave" type="button" class="primary-button">{{ tr('Speichern', 'Save') }}</button></div>
</form>
</dialog>
<script id="dashboardConfig" type="application/json">{{ {'sabEnabled': sab_enabled, 'libraryUpdatedAt': library_updated_at, 'libraryReady': library_ready}|tojson }}</script>
</body>
</html>