Files
2026-06-16 22:25:33 +01:00

362 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en" class="h-full bg-black">
<head>
<meta charset="UTF-8">
<title>HEVC Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
}
}
}
}
</script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;600;700&display=swap">
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="bg-black text-zinc-100 min-h-screen font-sans antialiased overflow-x-hidden selection:bg-emerald-500/30 selection:text-white">
<div class="dashboard-shell w-full mx-auto flex flex-col gap-2">
<!-- Header -->
<header class="surface flex flex-col xl:flex-row items-start xl:items-center justify-between gap-2 px-4 py-1.75">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-zinc-950 border border-zinc-800 flex items-center justify-center text-zinc-200 shadow-[0_0_0_1px_rgba(255,255,255,0.03)]">
<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<rect x="2" y="7" width="20" height="15" rx="2" ry="2"></rect>
<polyline points="17 2 12 7 7 2"></polyline>
</svg>
</div>
<div>
<h1 class="card-title text-sm font-bold tracking-[0.32em] text-white uppercase">HEVC ENCODER</h1>
</div>
</div>
<!-- Hardware Specs -->
<div class="hidden xl:flex items-center gap-6 text-xs border-l border-zinc-800/80 pl-6 mr-auto">
<div class="flex flex-col">
<span class="text-[9px] uppercase font-bold text-zinc-500 tracking-wider">Host CPU</span>
<span class="text-zinc-300 font-medium mt-0.5" id="spec-cpu">Detecting CPU...</span>
</div>
<div class="flex flex-col">
<span class="text-[9px] uppercase font-bold text-zinc-500 tracking-wider">Host GPU</span>
<span class="text-zinc-300 font-medium mt-0.5" id="spec-gpu">Detecting GPU...</span>
</div>
</div>
<div class="flex flex-wrap items-center gap-3">
<!-- Worker URL Panel -->
<div class="surface-soft rounded-2xl px-4 py-2 text-xs flex items-center gap-3">
<div class="flex flex-col">
<span class="text-[9px] uppercase font-bold text-zinc-500 tracking-wider">Worker API</span>
<span class="font-mono text-zinc-300 mt-0.5" id="worker-url-display">{{ worker_url }}</span>
</div>
<button class="text-zinc-500 hover:text-white transition-colors" id="btn-edit-url">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
</button>
<div id="url-editor" class="items-center gap-2 is-hidden">
<input type="text" id="worker-url-input" value="{{ worker_url }}" class="bg-black border border-zinc-800 rounded px-2 py-0.5 text-xs text-zinc-200 focus:outline-none">
<button class="bg-white hover:bg-zinc-200 text-black px-2 py-0.5 rounded text-[10px] font-bold uppercase transition-colors" id="btn-save-url">Save</button>
<button class="text-zinc-400 hover:text-white px-1 text-[10px]" id="btn-cancel-url">Cancel</button>
</div>
</div>
<!-- Status Badge -->
<div class="status-badge px-4 py-2 border border-zinc-800 text-zinc-400 bg-zinc-950/80 rounded-2xl text-xs font-bold tracking-wider flex items-center gap-2" id="status-indicator" data-state="idle">
<span class="w-2 h-2 rounded-full bg-current" id="status-dot"></span>
<span class="font-mono uppercase" id="status-text">IDLE</span>
</div>
</div>
</header>
<!-- Stats Grid -->
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-1.5">
<!-- Card 1: Total Scanned -->
<div class="surface stat-card flex flex-col justify-between hover:border-zinc-700/80 transition-all">
<span class="text-2xl font-bold text-white tracking-tight" id="stat-total">0</span>
<span class="text-[9px] uppercase font-bold tracking-widest text-zinc-500 mt-2">Scanned Files</span>
</div>
<!-- Card 2: Processed -->
<div class="surface stat-card flex flex-col justify-between hover:border-zinc-700/80 transition-all">
<span class="text-2xl font-bold text-white tracking-tight" id="stat-done">0</span>
<span class="text-[9px] uppercase font-bold tracking-widest text-zinc-500 mt-2">Processed</span>
</div>
<!-- Card 3: Ignored -->
<div class="surface stat-card flex flex-col justify-between hover:border-zinc-700/80 transition-all">
<span class="text-2xl font-bold text-white tracking-tight" id="stat-skipped">0</span>
<span class="text-[9px] uppercase font-bold tracking-widest text-zinc-500 mt-2">Ignored (Small)</span>
</div>
<!-- Card 4: Queue Remaining -->
<div class="surface stat-card flex flex-col justify-between hover:border-zinc-700/80 transition-all">
<span class="text-2xl font-bold text-white tracking-tight" id="stat-queued">0</span>
<span class="text-[9px] uppercase font-bold tracking-widest text-zinc-500 mt-2">Pending Process</span>
</div>
<!-- Card 5: Saved Space -->
<div class="surface stat-card flex flex-col justify-between hover:border-zinc-700/80 transition-all">
<span class="text-2xl font-bold text-emerald-500 tracking-tight" id="stat-saved">0 B</span>
<span class="text-[9px] uppercase font-bold tracking-widest text-zinc-500 mt-2">Storage Saved</span>
</div>
<!-- Card 6: Average Savings Ratio -->
<div class="surface stat-card flex flex-col justify-between hover:border-zinc-700/80 transition-all">
<span class="text-2xl font-bold text-emerald-500 tracking-tight" id="stat-ratio">0.0%</span>
<span class="text-[9px] uppercase font-bold tracking-widest text-zinc-500 mt-2">Avg. Compression</span>
</div>
</div>
<!-- Main Content Area -->
<div class="grid grid-cols-1 xl:grid-cols-[minmax(0,1.7fr)_minmax(380px,0.95fr)] gap-2 items-start">
<!-- Left Column: Encoding Queue, Finished Log, Live Terminal -->
<div class="flex flex-col gap-2">
<!-- Active encodes -->
<div class="surface surface-soft overflow-hidden flex flex-col">
<div class="panel-head border-b border-zinc-850 px-4 py-3 flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-[0.24em] text-zinc-400">Active Processing</span>
</div>
<div class="p-2.5 flex flex-col gap-2 min-h-[64px]" id="active-encodes-container">
<div class="text-zinc-500 py-6 text-center text-xs">No active processes</div>
</div>
</div>
<!-- Recent Finished -->
<div class="surface surface-soft overflow-hidden flex flex-col">
<div class="panel-head border-b border-zinc-850 px-4 py-3 flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-[0.24em] text-zinc-400">Recent Finished</span>
</div>
<div class="recent-pane p-2.5 flex flex-col gap-2 overflow-y-auto min-h-[54px]" id="recent-finished-container">
<div class="text-zinc-500 py-6 text-center text-xs">Nothing finished yet</div>
</div>
</div>
<!-- Terminal Logs -->
<div class="surface surface-soft overflow-hidden flex flex-col">
<div class="panel-head border-b border-zinc-850 px-4 py-3 flex items-center justify-between">
<span class="text-xs font-bold uppercase tracking-[0.24em] text-zinc-400">Live Logs</span>
<div class="flex items-center gap-3">
<span id="log-count" class="text-xs text-zinc-500 font-semibold">0 lines</span>
<button class="px-2.5 py-1 border border-zinc-800 hover:bg-zinc-900 text-zinc-400 hover:text-white rounded-lg text-[10px] font-bold uppercase transition-colors" id="btn-clear-logs">Clear</button>
</div>
</div>
<div class="logs-pane p-2.5 bg-black/90 border-t border-zinc-900 overflow-y-auto flex flex-col gap-1 text-[11px] font-mono leading-relaxed" id="logs-container">
<!-- Logs stream in here -->
</div>
</div>
</div>
<!-- Right Column: Settings & Actions Panel -->
<div class="surface config-panel p-2.5 flex flex-col gap-1.5 xl:max-h-[calc(100vh-19rem)] xl:overflow-hidden">
<div class="border-b border-zinc-850 pb-2 flex justify-between items-center">
<span class="text-xs font-bold uppercase tracking-[0.24em] text-zinc-400">Configuration</span>
</div>
<div class="config-grid grid grid-cols-1 sm:grid-cols-2 gap-1">
<div class="flex flex-col gap-1 sm:col-span-2">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Media Directory</label>
<div class="flex gap-2">
<input type="text" id="media-dir" value="{{ media_root }}" placeholder="Click Browse to select folder..." class="flex-1">
<button class="px-3 bg-zinc-900 border border-zinc-800 hover:bg-zinc-800 text-zinc-300 rounded-2xl text-xs font-semibold transition-colors flex items-center gap-1" id="btn-browse">
<svg class="w-3.5 h-3.5 text-zinc-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
Browse
</button>
</div>
</div>
<div class="flex flex-col gap-1 sm:col-span-2">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Temporary Directory</label>
<div class="flex gap-2">
<input type="text" id="temp-dir" value="{{ temp_dir }}" placeholder="Click Browse to select folder..." class="flex-1">
<button class="px-3 bg-zinc-900 border border-zinc-800 hover:bg-zinc-800 text-zinc-300 rounded-2xl text-xs font-semibold transition-colors flex items-center gap-1" id="btn-browse-temp">
<svg class="w-3.5 h-3.5 text-zinc-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
Browse
</button>
</div>
</div>
<div class="flex flex-col gap-1 sm:col-span-2">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Excluded Folder Tags</label>
<div class="flex gap-2">
<div class="exclude-shell flex-1" id="excl-tags">
<input type="text" id="excl-input" placeholder="Folder..." class="exclude-shell-input flex-1 min-w-[70px] bg-transparent border-none text-xs text-zinc-200 outline-none p-1">
</div>
<button class="panel-button panel-button-small" id="btn-browse-excl">
<svg class="w-3.5 h-3.5 text-zinc-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
Add
</button>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Min Size (MB)</label>
<input type="number" id="min-size" value="0">
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Threads</label>
<div class="relative select-shell">
<select id="threads">
<!-- Injected dynamically by app.js -->
</select>
<div class="select-chevron absolute inset-y-0 right-3 flex items-center pointer-events-none text-zinc-500">
<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7"></path></svg>
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Preset</label>
<div class="relative select-shell">
<select id="preset">
<option value="fast">fast</option>
<option value="medium" selected>medium</option>
<option value="slow">slow</option>
</select>
<div class="select-chevron absolute inset-y-0 right-3 flex items-center pointer-events-none text-zinc-500">
<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7"></path></svg>
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Video Encoder</label>
<div class="relative select-shell">
<select id="encoder">
<option value="libx265" {% if encoder == 'libx265' %}selected{% endif %}>CPU (libx265)</option>
<option value="hevc_nvenc" {% if encoder == 'hevc_nvenc' %}selected{% endif %}>NVIDIA GPU (hevc_nvenc)</option>
<option value="hevc_amf" {% if encoder == 'hevc_amf' %}selected{% endif %}>AMD GPU (hevc_amf)</option>
<option value="hevc_qsv" {% if encoder == 'hevc_qsv' %}selected{% endif %}>Intel GPU (hevc_qsv)</option>
</select>
<div class="select-chevron absolute inset-y-0 right-3 flex items-center pointer-events-none text-zinc-500">
<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7"></path></svg>
</div>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Audio Codec</label>
<div class="relative select-shell">
<select id="audio-codec">
<option value="auto" {% if audio_codec == 'auto' %}selected{% endif %}>Auto (Source-aware)</option>
<option value="copy" {% if audio_codec == 'copy' %}selected{% endif %}>Copy (Original)</option>
<option value="aac" {% if audio_codec == 'aac' %}selected{% endif %}>AAC (Transcode)</option>
<option value="ac3" {% if audio_codec == 'ac3' %}selected{% endif %}>AC3 (Transcode)</option>
</select>
<div class="select-chevron absolute inset-y-0 right-3 flex items-center pointer-events-none text-zinc-500">
<svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7"></path></svg>
</div>
</div>
<p class="text-[10px] text-zinc-500">Auto keeps AAC as AAC, copies other audio, and uses no audio if the source has none.</p>
</div>
<div class="toggle-card flex items-center justify-between gap-3 rounded-2xl border border-zinc-850 bg-zinc-950/80 px-3.5 py-2">
<div class="toggle-copy flex flex-col gap-1">
<label for="rename-output" class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Rename Output Files</label>
<p class="text-[10px] text-zinc-500">Uses a Plex/Radarr/Sonarr-friendly name with title, year or episode, quality, and codec tags.</p>
</div>
<label class="inline-flex items-center cursor-pointer select-none">
<input type="checkbox" id="rename-output" {% if rename_output %}checked{% endif %} class="sr-only peer">
<span class="toggle-switch relative h-5 w-10 rounded-full bg-zinc-800 transition-colors peer-checked:bg-emerald-500 after:absolute after:left-0.5 after:top-0.5 after:h-4 after:w-4 after:rounded-full after:bg-white after:transition-transform peer-checked:after:translate-x-5"></span>
</label>
</div>
<div class="toggle-card flex items-center justify-between gap-3 rounded-2xl border border-zinc-850 bg-zinc-950/80 px-3.5 py-2">
<div class="toggle-copy flex flex-col gap-1">
<label for="force-reencode" class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Force Re-encode</label>
<p class="text-[10px] text-zinc-500">Always re-encode to HEVC and chosen audio, even if already HEVC/AAC.</p>
</div>
<label class="inline-flex items-center cursor-pointer select-none">
<input type="checkbox" id="force-reencode" {% if force_reencode %}checked{% endif %} class="sr-only peer">
<span class="toggle-switch relative h-5 w-10 rounded-full bg-zinc-800 transition-colors peer-checked:bg-emerald-500 after:absolute after:left-0.5 after:top-0.5 after:h-4 after:w-4 after:rounded-full after:bg-white after:transition-transform peer-checked:after:translate-x-5"></span>
</label>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">CRF Value (Quality)</label>
<input type="number" id="crf" value="22" min="18" max="28">
</div>
<div class="flex flex-col gap-1 sm:col-span-2">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Discord Webhook URL (Optional)</label>
<div class="flex gap-2">
<input type="text" id="discord-webhook" value="{{ discord_webhook }}" placeholder="https://discord.com/api/webhooks/...">
<button class="px-3 bg-zinc-900 border border-zinc-800 hover:bg-zinc-800 text-zinc-300 rounded-2xl text-xs font-semibold transition-colors flex items-center justify-center whitespace-nowrap" id="btn-test-webhook">
Test
</button>
</div>
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Custom FFmpeg Path</label>
<input type="text" id="custom-ffmpeg" value="{{ custom_ffmpeg }}" placeholder="Auto-detect if empty">
</div>
<div class="flex flex-col gap-1">
<label class="text-[9px] uppercase font-bold tracking-[0.24em] text-zinc-500">Custom FFprobe Path</label>
<input type="text" id="custom-ffprobe" value="{{ custom_ffprobe }}" placeholder="Auto-detect if empty">
</div>
</div>
<!-- Actions Panel -->
<div class="actions-panel grid grid-cols-1 gap-1 pt-1 border-t border-zinc-800 mt-1 sm:col-span-2">
<button class="panel-button panel-button-primary" id="btn-scan">
<svg class="w-4 h-4 text-black" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
Scan Media Library
</button>
<div class="grid grid-cols-3 gap-2">
<button class="panel-button panel-button-success" id="btn-process">
<svg class="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
Run
</button>
<button class="panel-button panel-button-neutral" id="btn-pause" disabled>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>
Pause
</button>
<button class="panel-button panel-button-neutral is-hidden" id="btn-resume" disabled>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
Resume
</button>
<button class="panel-button panel-button-danger" id="btn-stop" disabled>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>
Stop
</button>
</div>
<button class="panel-button panel-button-muted" id="btn-clear-db">
Clear SQLite Cache
</button>
</div>
</div>
</div>
</div>
<!-- Directory Browser Modal -->
<div class="modal-backdrop fixed inset-0 bg-black/85 backdrop-blur-sm z-50 flex items-center justify-center p-4 is-hidden" id="modal-backdrop">
<div class="bg-zinc-950 border border-zinc-800 w-full max-w-xl rounded-2xl overflow-hidden shadow-2xl flex flex-col max-h-[85vh]" id="modal-content">
<div class="px-6 py-4 border-b border-zinc-900 text-xs font-bold text-zinc-300 uppercase tracking-widest" id="modal-title">Browse Directory</div>
<div id="modal-drive-switcher" class="px-6 py-2.5 border-b border-zinc-900 bg-zinc-900/20 flex items-center gap-2 flex-wrap text-xs">
<span class="font-bold text-[10px] uppercase text-zinc-500 tracking-wider mr-2">Drives:</span>
<!-- Drive buttons will be injected here dynamically -->
</div>
<div id="modal-breadcrumb" class="flex flex-col"></div>
<div class="p-2 overflow-y-auto flex-1 max-h-[450px]" id="modal-body"></div>
<div class="px-6 py-4 border-t border-zinc-900 bg-zinc-950/50 flex justify-end gap-3">
<input type="hidden" id="modal-current-path">
<button class="px-4 py-2 border border-zinc-800 hover:bg-zinc-900 text-zinc-400 hover:text-white rounded-xl text-xs font-semibold transition-colors" id="btn-modal-cancel">Cancel</button>
<button class="px-5 py-2 bg-white hover:bg-zinc-200 text-black rounded-xl text-xs font-semibold transition-colors" id="btn-modal-select">Select Folder</button>
</div>
</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>