feat: add nvidia-smi pmon integration to detect NVENC/NVDEC processes and display GPU process activity with auto-refresh status
Add graphics_process_log_state global to track GPU process changes. Query nvidia-smi pmon with 1-count sample to detect video encode/decode processes missed by compute-apps query. Parse pmon output for SM/ENC/DEC utilization percentages and process types. Merge pmon results with compute-apps by PID, preferring pmon data when available. Log GPU process detection with PID/name
This commit is contained in:
1 parent
8c8e1be0db
commit
7313eb96c3
2 files changed
+48
-7
No files matched your search
@@ -88,6 +88,7 @@ raid_refresh_lock = threading.Lock()
|
||||
datastore_cache = {"stored_at": 0.0, "snapshot": None, "raid": None, "raid_at": 0.0}
|
||||
raid_log_state = None
|
||||
graphics_log_state = None
|
||||
graphics_process_log_state = None
|
||||
hardware_log_once = set()
|
||||
|
||||
LANG_NAMES = {"de":"Deutsch", "deu":"Deutsch", "ger":"Deutsch", "en":"Englisch", "eng":"Englisch", "ja":"Japanisch", "jpn":"Japanisch", "ko":"Koreanisch", "kor":"Koreanisch", "fr":"Französisch", "fra":"Französisch", "fre":"Französisch", "es":"Spanisch", "spa":"Spanisch", "it":"Italienisch", "ita":"Italienisch", "ru":"Russisch", "rus":"Russisch", "zh":"Chinesisch", "zho":"Chinesisch", "chi":"Chinesisch", "und":"Unbekannt", "":"Unbekannt"}
|
||||
@@ -357,7 +358,7 @@ def io_pressure():
|
||||
|
||||
def graphics_snapshot():
|
||||
"""Read NVIDIA GPU details with short, non-blocking subprocess timeouts."""
|
||||
global graphics_log_state
|
||||
global graphics_log_state, graphics_process_log_state
|
||||
query = "index,name,uuid,serial,pci.bus_id,driver_version,pstate,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.used,memory.free,power.draw,power.limit,fan.speed,clocks.current.graphics,clocks.current.memory,clocks.max.graphics,clocks.max.memory,compute_mode"
|
||||
try:
|
||||
result = subprocess.run([NVIDIA_SMI_BIN, f"--query-gpu={query}", "--format=csv,noheader,nounits"], capture_output=True, text=True, timeout=5, check=False)
|
||||
@@ -388,15 +389,49 @@ def graphics_snapshot():
|
||||
try: gpu[key] = round(float(gpu[key]), 1)
|
||||
except (TypeError, ValueError): gpu[key] = None
|
||||
gpus.append(gpu)
|
||||
processes = []
|
||||
processes_by_pid = {}
|
||||
try:
|
||||
proc = subprocess.run([NVIDIA_SMI_BIN, "--query-compute-apps=pid,process_name,used_gpu_memory", "--format=csv,noheader,nounits"], capture_output=True, text=True, timeout=5, check=False)
|
||||
for line in proc.stdout.splitlines():
|
||||
values = [value.strip() for value in line.split(",")]
|
||||
if len(values) >= 3:
|
||||
processes.append({"pid": values[0], "name": values[1], "memory": values[2]})
|
||||
if proc.returncode == 0:
|
||||
for line in proc.stdout.splitlines():
|
||||
values = [value.strip() for value in line.split(",")]
|
||||
if len(values) >= 3 and values[0].isdigit():
|
||||
processes_by_pid[values[0]] = {"pid": values[0], "name": values[1], "memory": values[2], "type": "compute"}
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
pass
|
||||
pmon_error = ""
|
||||
try:
|
||||
# Compute-apps misses NVENC/NVDEC. pmon also reports video/graphics
|
||||
# processes such as Jellyfin while a hardware transcode is running.
|
||||
pmon = subprocess.run([NVIDIA_SMI_BIN, "pmon", "-c", "1", "-s", "um"], capture_output=True, text=True, timeout=5, check=False)
|
||||
if pmon.returncode == 0:
|
||||
for line in pmon.stdout.splitlines():
|
||||
values = line.split()
|
||||
if not values or values[0].startswith("#") or len(values) < 8 or not values[1].isdigit():
|
||||
continue
|
||||
pid, process_type = values[1], values[2]
|
||||
command = " ".join(values[7:]) or "GPU-Prozess"
|
||||
processes_by_pid[pid] = {
|
||||
"pid": pid,
|
||||
"name": command,
|
||||
"memory": f"SM {values[3]}% · ENC {values[5]}% · DEC {values[6]}%",
|
||||
"type": process_type,
|
||||
"sm": values[3], "enc": values[5], "dec": values[6],
|
||||
}
|
||||
else:
|
||||
pmon_error = " ".join((pmon.stderr or "pmon ohne Ausgabe").split())[:180]
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
pmon_error = error_summary(exc)
|
||||
processes = list(processes_by_pid.values())
|
||||
process_signature = tuple((item["pid"], item["name"], item.get("type")) for item in processes)
|
||||
if process_signature != graphics_process_log_state:
|
||||
if processes:
|
||||
log.info("GPU-Prozesse erkannt: %s", ", ".join(f"{item['name']} (PID {item['pid']})" for item in processes))
|
||||
elif pmon_error:
|
||||
log.warning("GPU-Prozessabfrage fehlgeschlagen: %s", pmon_error)
|
||||
else:
|
||||
log.info("Keine GPU-Prozesse erkannt")
|
||||
graphics_process_log_state = process_signature
|
||||
signature = ("ok", tuple((gpu.get("index"), gpu.get("name"), gpu.get("temperature.gpu"), gpu.get("utilization.gpu")) for gpu in gpus))
|
||||
if signature != graphics_log_state:
|
||||
log.info("GPU-Status verfügbar: %s NVIDIA-GPU(s) via %s", len(gpus), NVIDIA_SMI_BIN)
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded',()=>{const root=document.querySelector('#datastore');if(!root)return;const grid=root.querySelector('.datastore-grid');if(grid&&!document.querySelector('#ioPressurePanel')){const panel=document.createElement('section');panel.id='ioPressurePanel';panel.className='datastore-panel io-pressure-panel';panel.innerHTML='<div class="datastore-panel-head"><div><h3>I/O-Auslastung</h3><p>Festplatten-Druck aus Linux PSI</p></div><strong id="ioPressureLabel">Wird geladen …</strong></div><div class="io-pressure-bars"><div><span>Some</span><b id="ioPressureSome">—</b><div class="datastore-progress"><span id="ioPressureSomeBar"></span></div></div><div><span>Full</span><b id="ioPressureFull">—</b><div class="datastore-progress"><span id="ioPressureFullBar"></span></div></div></div>';grid.before(panel)}const pressure=async()=>{try{const data=await (await fetch('/api/datastore')).json();const io=data.io_pressure||{};const some=Number(io.some?.avg10)||0,full=Number(io.full?.avg10)||0;const level=Math.max(some,full),label=level>=20?'Hoch':level>=5?'Beobachten':'Normal';const set=(id,value)=>{const node=document.querySelector(id);if(node)node.textContent=value};set('#ioPressureLabel',io.available?`${label} · ${level.toFixed(1)}%`:'Nicht verfügbar');set('#ioPressureSome',`${some.toFixed(1)}%`);set('#ioPressureFull',`${full.toFixed(1)}%`);const a=document.querySelector('#ioPressureSomeBar'),b=document.querySelector('#ioPressureFullBar');if(a)a.style.width=`${Math.min(100,some)}%`;if(b)b.style.width=`${Math.min(100,full)}%`}catch(error){}};pressure();setInterval(pressure,5000);const disks=document.querySelector('#raidDisks');if(disks){const observer=new MutationObserver(()=>{if([...disks.querySelectorAll('.raid-disk')].some(row=>!row.querySelector('.raid-temperature')))setTimeout(pressure,0)});observer.observe(disks,{childList:true,subtree:true})}});
|
||||
</script>
|
||||
<style>
|
||||
.graphics-live{display:flex;flex-direction:column;align-items:center;gap:2px;text-align:center}.graphics-live small{font-size:9px;color:#a99ad0;letter-spacing:.02em}
|
||||
</style>
|
||||
<script>
|
||||
(function(){function init(){const status=document.querySelector('#graphicsStatus');if(!status)return;let seconds=5;function ensureRefreshLabel(){let label=document.querySelector('#graphicsRefreshStatus');if(!label){label=document.createElement('small');label.id='graphicsRefreshStatus';status.appendChild(label)}label.textContent=seconds>0?'Auto-Update in '+seconds+'s':'Aktualisiere …'}ensureRefreshLabel();setInterval(function(){seconds=seconds<=0?5:seconds-1;ensureRefreshLabel()},1000);setInterval(function(){seconds=5},5000)}if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init()})();
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded',()=>{const root=document.querySelector('#datastore'),grid=root?.querySelector('.datastore-grid'),capacity=root?.querySelector('.datastore-capacity'),pressure=root?.querySelector('#ioPressurePanel');if(!grid||!capacity||!pressure)return;const categories=grid.children[0],raid=grid.children[1];if(categories&&!categories.contains(capacity))categories.prepend(capacity);if(raid&&!raid.contains(pressure))raid.prepend(pressure)});
|
||||
</script>
|
||||
@@ -60,7 +66,7 @@ if ('serviceWorker' in navigator) {
|
||||
.settings-modal .setting-help{display:block;margin:-3px 0 12px;color:var(--muted);font-size:11px}.settings-modal .setting-select{width:100%;height:38px}.pwa-install{display:none}
|
||||
@media(max-width:700px){body{font-size:12px;padding-bottom:env(safe-area-inset-bottom)}header{height:auto;min-height:62px;padding:9px 12px;gap:9px;align-items:center}.brand-mark,.brand-mark svg{width:30px;height:30px}.brand-name{font-size:14px}.head-title{font-size:12px}.head-sub{font-size:10px}.actions{gap:4px}.actions button{height:34px;padding:0 8px}.settings-button{font-size:15px}.actions #rescan{font-size:0}.actions #rescan::after{content:'↻';font-size:16px}main{padding:10px 10px 24px}.nav{margin-bottom:10px;padding:4px;overflow-x:auto;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}.nav button{flex:0 0 auto;white-space:nowrap;padding:8px 10px;font-size:11px}.toolbar{grid-template-columns:1fr;gap:7px;padding:8px}.toolbar input,.toolbar select{width:100%;height:38px}.stats{gap:6px}.stat{padding:6px 8px}.tablewrap{overflow-x:auto;border-radius:8px}table{min-width:860px}.dashboard-hero{margin-bottom:10px;padding:18px;border-radius:11px}.dashboard-hero h1{font-size:21px}.dashboard-hero p{font-size:11px;line-height:1.4}.overview-grid{grid-template-columns:1fr 1fr;gap:7px}.overview-card{min-height:84px;padding:12px 10px;gap:8px}.overview-icon{width:32px;height:32px;font-size:18px}.overview-card strong{font-size:18px}.overview-card span:not(.overview-icon){font-size:9px}.overview-card small{font-size:9px}.dashboard-columns{gap:8px}.system-panel,.service-panel{padding:14px;border-radius:10px}.system-meters{gap:12px}.panel-heading{margin-bottom:13px}.panel-heading h2{font-size:14px}.panel-heading p{font-size:10px}.disk-row{grid-template-columns:82px 1fr auto;gap:7px}.disk-values{font-size:10px}.download-hero{padding:18px;margin-bottom:10px}.download-hero h2{font-size:19px}.download-hero p{font-size:11px}.download-stats{grid-template-columns:1fr 1fr;gap:7px}.download-card{min-height:78px;padding:10px;gap:8px}.card-icon{width:30px;height:30px;font-size:17px}.card-label{font-size:9px}.download-card strong{font-size:17px}.download-card small{font-size:9px}.settings-modal{max-height:calc(100dvh - 24px);overflow:auto;padding:18px}.settings-head{margin-bottom:15px}.settings-modal label{margin-top:13px}}
|
||||
@media(max-width:390px){.brand-name{display:none}.head-sub{display:none}.overview-grid{grid-template-columns:1fr}.dashboard-hero{padding:15px}.download-stats{grid-template-columns:1fr}}
|
||||
</style><style>.scan-control{display:flex;align-items:center;gap:5px}.scan-control select{height:36px;padding:0 8px;font-size:11px}.scan-control button{white-space:nowrap}.scan-running{position:relative;overflow:hidden;background:linear-gradient(90deg,rgba(244,196,48,.14) var(--scan-progress,0%),rgba(244,196,48,.04) var(--scan-progress,0%))!important}.scan-running::after{content:'';position:absolute;left:0;bottom:0;width:var(--scan-progress,0%);height:2px;background:#f4c430;transition:width .35s ease}@media(max-width:700px){.scan-control select{max-width:108px;font-size:10px}.scan-control button{font-size:0}.scan-control button::after{content:'↻';font-size:16px}}</style><style>.graphics-hero{display:flex;justify-content:space-between;align-items:center;gap:18px;margin-bottom:14px;padding:22px 26px;border:1px solid rgba(167,139,250,.24);border-radius:14px;background:radial-gradient(circle at 88% 20%,rgba(167,139,250,.16),transparent 32%),linear-gradient(135deg,rgba(45,40,65,.96),rgba(27,30,36,.96));box-shadow:0 14px 34px rgba(0,0,0,.2)}.graphics-hero h2{margin:7px 0 4px;font-size:22px}.graphics-hero p{margin:0;color:#9da5b2}.graphics-live{padding:8px 12px;border:1px solid rgba(167,139,250,.28);border-radius:999px;color:#dfd2ff;background:rgba(125,91,190,.15);font-size:11px;white-space:nowrap}.graphics-summary{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}.graphics-summary-card{display:flex;align-items:center;gap:11px;min-height:82px;padding:14px;border:1px solid rgba(255,255,255,.08);border-radius:11px;background:linear-gradient(145deg,rgba(43,47,55,.92),rgba(29,31,36,.92));box-shadow:0 8px 22px rgba(0,0,0,.16)}.graphics-icon{display:grid;place-items:center;width:35px;height:35px;border-radius:10px;background:rgba(167,139,250,.18);color:#d7c4ff;font-weight:800}.graphics-summary-card small{display:block;color:#9da5b2;font-size:10px;text-transform:uppercase;letter-spacing:.06em}.graphics-summary-card strong{display:block;margin-top:3px;color:#f1f5fa;font-size:18px}.graphics-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-bottom:12px}.graphics-card,.graphics-panel{padding:18px;border:1px solid rgba(255,255,255,.08);border-radius:12px;background:rgba(31,33,38,.84);box-shadow:0 12px 30px rgba(0,0,0,.18)}.graphics-card-head,.graphics-card-line,.graphics-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px}.graphics-card-head h3,.graphics-panel-head h3{margin:3px 0 0;font-size:16px}.graphics-gpu-index{color:#a78bfa;font-size:10px;text-transform:uppercase;letter-spacing:.07em}.graphics-meta-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.graphics-meta-grid div{padding:9px;border:1px solid rgba(255,255,255,.07);border-radius:8px;background:rgba(10,14,20,.2)}.graphics-meta-grid small{display:block;color:#7f8996;font-size:10px}.graphics-meta-grid strong{display:block;margin-top:4px;color:#e8eef5;font-size:13px}.graphics-card-line{font-size:11px;color:#9da5b2}.graphics-card-line strong{color:#e8eef5}.graphics-track{height:9px;margin-top:7px;border-radius:99px;overflow:hidden;background:#363b44}.graphics-track span{display:block;height:100%;border-radius:99px;background:linear-gradient(90deg,#60a9e6,#a78bfa);transition:width .5s ease}.graphics-details{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:14px;color:#7f8996;font-size:10px}.graphics-empty{padding:20px;text-align:center;color:#7f8996;font-size:11px}.graphics-panel-head{margin-bottom:12px}.graphics-panel-head h3{margin:0}.graphics-panel-head p{margin:4px 0 0;color:#7f8996;font-size:11px}.graphics-processes{display:grid;gap:6px}.graphics-process{display:grid;grid-template-columns:1fr auto auto;gap:14px;padding:9px 10px;border:1px solid rgba(255,255,255,.07);border-radius:8px;background:rgba(40,43,49,.65);font-size:11px}.graphics-process span{color:#9da5b2}@media(max-width:900px){.graphics-summary{grid-template-columns:repeat(2,1fr)}.graphics-cards{grid-template-columns:1fr}}@media(max-width:600px){.graphics-hero{align-items:flex-start;flex-direction:column;padding:18px}.graphics-live{align-self:stretch;text-align:center}.graphics-summary{gap:7px}.graphics-summary-card{min-height:72px;padding:10px}.graphics-summary-card strong{font-size:16px}.graphics-card,.graphics-panel{padding:14px}.graphics-meta-grid{grid-template-columns:repeat(2,1fr)}.graphics-process{grid-template-columns:1fr;gap:3px}}</style></head><body>
|
||||
</style><style>.scan-control{display:flex;align-items:center;gap:5px}.scan-control select{height:36px;padding:0 8px;font-size:11px}.scan-control button{white-space:nowrap}.scan-running{position:relative;overflow:hidden;background:linear-gradient(90deg,rgba(244,196,48,.14) var(--scan-progress,0%),rgba(244,196,48,.04) var(--scan-progress,0%))!important}.scan-running::after{content:'';position:absolute;left:0;bottom:0;width:var(--scan-progress,0%);height:2px;background:#f4c430;transition:width .35s ease}@media(max-width:700px){.scan-control select{max-width:108px;font-size:10px}.scan-control button{font-size:0}.scan-control button::after{content:'↻';font-size:16px}}</style><style>.graphics-hero{display:flex;justify-content:space-between;align-items:center;gap:18px;margin-bottom:14px;padding:22px 26px;border:1px solid rgba(167,139,250,.24);border-radius:14px;background:radial-gradient(circle at 88% 20%,rgba(167,139,250,.16),transparent 32%),linear-gradient(135deg,rgba(45,40,65,.96),rgba(27,30,36,.96));box-shadow:0 14px 34px rgba(0,0,0,.2)}.graphics-hero h2{margin:7px 0 4px;font-size:22px}.graphics-hero p{margin:0;color:#9da5b2}.graphics-live{padding:8px 12px;border:1px solid rgba(167,139,250,.28);border-radius:999px;color:#dfd2ff;background:rgba(125,91,190,.15);font-size:11px;white-space:nowrap}.graphics-summary{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}.graphics-summary-card{display:flex;align-items:center;gap:11px;min-height:82px;padding:14px;border:1px solid rgba(255,255,255,.08);border-radius:11px;background:linear-gradient(145deg,rgba(43,47,55,.92),rgba(29,31,36,.92));box-shadow:0 8px 22px rgba(0,0,0,.16)}.graphics-icon{display:grid;place-items:center;width:35px;height:35px;border-radius:10px;background:rgba(167,139,250,.18);color:#d7c4ff;font-weight:800}.graphics-summary-card small{display:block;color:#9da5b2;font-size:10px;text-transform:uppercase;letter-spacing:.06em}.graphics-summary-card strong{display:block;margin-top:3px;color:#f1f5fa;font-size:18px}.graphics-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-bottom:12px}.graphics-card,.graphics-panel{padding:18px;border:1px solid rgba(255,255,255,.08);border-radius:12px;background:rgba(31,33,38,.84);box-shadow:0 12px 30px rgba(0,0,0,.18)}.graphics-card-head,.graphics-card-line,.graphics-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px}.graphics-card-head h3,.graphics-panel-head h3{margin:3px 0 0;font-size:16px}.graphics-gpu-index{color:#a78bfa;font-size:10px;text-transform:uppercase;letter-spacing:.07em}.graphics-meta-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.graphics-meta-grid div{padding:9px;border:1px solid rgba(255,255,255,.07);border-radius:8px;background:rgba(10,14,20,.2)}.graphics-meta-grid small{display:block;color:#7f8996;font-size:10px}.graphics-meta-grid strong{display:block;margin-top:4px;color:#e8eef5;font-size:13px}.graphics-card-line{font-size:11px;color:#9da5b2}.graphics-card-line strong{color:#e8eef5}.graphics-track{height:9px;margin-top:7px;border-radius:99px;overflow:hidden;background:#363b44}.graphics-track span{display:block;height:100%;border-radius:99px;background:linear-gradient(90deg,#60a9e6,#a78bfa);transition:width .5s ease}.graphics-details{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:14px;color:#7f8996;font-size:10px}.graphics-empty{padding:20px;text-align:center;color:#7f8996;font-size:11px}.graphics-panel-head{margin-bottom:12px}.graphics-panel-head h3{margin:0}.graphics-panel-head p{margin:4px 0 0;color:#7f8996;font-size:11px}.graphics-processes{display:grid;gap:6px}.graphics-process{display:grid;grid-template-columns:1fr auto auto;gap:14px;padding:9px 10px;border:1px solid rgba(255,255,255,.07);border-radius:8px;background:rgba(40,43,49,.65);font-size:11px}.graphics-process span{color:#9da5b2}@media(max-width:900px){.graphics-summary{grid-template-columns:repeat(2,1fr)}.graphics-cards{grid-template-columns:1fr}}@media(max-width:600px){.graphics-hero{align-items:flex-start;flex-direction:column;padding:18px}.graphics-live{align-self:stretch;text-align:center}.graphics-summary{gap:7px}.graphics-summary-card{min-height:72px;padding:10px}.graphics-summary-card strong{font-size:16px}.graphics-card,.graphics-panel{padding:14px}.graphics-meta-grid{grid-template-columns:repeat(2,1fr)}.graphics-process{grid-template-columns:1fr;gap:3px}}</style><style>.graphics-cards{grid-template-columns:repeat(auto-fit,minmax(520px,1fr));align-items:stretch}.graphics-card{position:relative;overflow:hidden;background:radial-gradient(circle at 100% 0,rgba(167,139,250,.09),transparent 34%),rgba(31,33,38,.9);border-color:rgba(167,139,250,.18)}.graphics-card::before{content:"";position:absolute;inset:0 0 auto;height:3px;background:linear-gradient(90deg,#60a9e6,#a78bfa,#f4c430);opacity:.8}.graphics-card-head{padding-bottom:14px;border-bottom:1px solid rgba(255,255,255,.07)}.graphics-card-head h3{font-Line truncated
|
||||
<header><div class="brand"><div class="brand-mark" aria-label="Media Max"><svg viewBox="0 0 48 48" role="img"><path d="M10 8h28a4 4 0 0 1 4 4v24a4 4 0 0 1-4 4H10a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4Z" fill="#20252d" stroke="#60a9e6" stroke-width="2"/><path d="M14 17h20M14 24h13M14 31h20" stroke="#f4c430" stroke-width="3" stroke-linecap="round"/><circle cx="34" cy="24" r="5" fill="#159447" stroke="#c9ffe0" stroke-width="1.5"/></svg></div><div class="brand-name">MEDIA</div></div><div><div class="head-title" data-de="Media Max" data-en="Media Max">Media Max</div><div class="head-sub" data-de="Audio- und Untertitelspuren aus den Mediendateien" data-en="Audio and subtitle tracks from media files">Audio- und Untertitelspuren aus den Mediendateien</div></div><div class="actions"><button class="lang-switch" id="languageToggle" title="Sprache / Language">DE</button><button class="settings-button" id="settingsOpen" title="Einstellungen">⚙</button><div class="scan-control"><select id="scanSource" aria-label="Scan-Quelle"><option value="all">Alles scannen</option><option value="radarr">Nur Radarr</option><option value="sonarr">Nur Sonarr</option><option value="missing">Nur Ohne Deutsch</option></select><button id="rescan" data-de="↻ Neu scannen" data-en="↻ Rescan">↻ Neu scannen</button></div></div></header>
|
||||
<main>
|
||||
<nav class="nav"><button class="active" data-view="dashboard"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:view-dashboard-outline.svg?color=%238bc9f2" alt="" aria-hidden="true">Dashboard</button><button data-view="radarr"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:movie-open-outline.svg?color=%23cc7b19" alt="" aria-hidden="true">RADARR · Filme</button>{% if sonarr_enabled %}<button data-view="sonarr"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:television-classic.svg?color=%2335c5f4" alt="" aria-hidden="true">SONARR · Serien</button>{% endif %}<button data-view="missing"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:alert-outline.svg?color=%23ff9e9e" alt="" aria-hidden="true">Ohne Deutsch <span class="nav-count" id="missingNavCount">{{ missing_german_movies|length + (missing_german_series|map(attribute='episodes')|map('length')|sum) }}</span></button>{% if sab_enabled %}<button data-view="downloads"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:download-box-outline.svg?color=%23f4c430" alt="" aria-hidden="true">SABNZBD · Downloads <span class="nav-count download-nav-count hidden" id="downloadNavCount">0</span></button>{% endif %}<button data-view="datastore"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:harddisk.svg?color=%2360a9e6" alt="" aria-hidden="true">Datastore</button><button data-view="graphics"><img class="nav-tab-icon" src="https://api.iconify.design/mdi:expansion-card.svg?color=%23a78bfa" alt="" aria-hidden="true">Graphics</button></nav>
|
||||
|
||||
Reference in new issue
Block a user