Initial commit
This commit is contained in:
4
dist/sortarr/web/Dockerfile
vendored
Normal file
4
dist/sortarr/web/Dockerfile
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM nginx:1.27-alpine
|
||||
COPY src /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
20
dist/sortarr/web/nginx.conf
vendored
Normal file
20
dist/sortarr/web/nginx.conf
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8099/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
1006
dist/sortarr/web/src/app.js
vendored
Normal file
1006
dist/sortarr/web/src/app.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
158
dist/sortarr/web/src/index.html
vendored
Normal file
158
dist/sortarr/web/src/index.html
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sortarr</title>
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
<link rel="stylesheet" href="/themes.css">
|
||||
<link rel="stylesheet" href="/api/theme/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<span class="brand-mark">S</span>
|
||||
<div>
|
||||
<strong>Sortarr</strong>
|
||||
</div>
|
||||
</div>
|
||||
<nav>
|
||||
<a href="#/overview" data-route="overview" class="active">Overview</a>
|
||||
<a href="#/library" data-route="library">Library</a>
|
||||
<a href="#/downloads" data-route="downloads">Downloads</a>
|
||||
<a href="#/releases" data-route="releases">Releases</a>
|
||||
<a href="#/tools" data-route="tools">Tools</a>
|
||||
<a href="#/settings" data-route="settings">Settings</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<h1>Media Dashboard</h1>
|
||||
<p id="statusLine">Connecting to backend...</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="scanButton">Run scan</button>
|
||||
<button id="refreshButton">Refresh</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="page-overview" class="page active">
|
||||
<div class="grid overview-grid">
|
||||
<article class="panel">
|
||||
<h2>Storage</h2>
|
||||
<div id="storageCards" class="storage-list"></div>
|
||||
</article>
|
||||
<article class="panel">
|
||||
<h2>File Types</h2>
|
||||
<div id="extensionBreakdown" class="bars"></div>
|
||||
</article>
|
||||
<article class="panel">
|
||||
<h2>Activity</h2>
|
||||
<div id="events" class="event-list"></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="page-library" class="page panel">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>Library Contents</h2>
|
||||
<p id="libraryStatus" class="muted"></p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<input id="libraryFilter" placeholder="Filter library">
|
||||
<button id="libraryScanButton">Scan library</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="libraryTabs" class="segmented"></div>
|
||||
<div id="libraryGrid" class="poster-grid"></div>
|
||||
<div id="libraryPager" class="pager"></div>
|
||||
</section>
|
||||
|
||||
<section id="page-downloads" class="page panel">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>Downloads</h2>
|
||||
<p id="downloadsStatus" class="muted"></p>
|
||||
</div>
|
||||
<button id="downloadsRefresh">Refresh downloads</button>
|
||||
</div>
|
||||
<div class="downloads-layout">
|
||||
<article>
|
||||
<h3>Organizer Queue</h3>
|
||||
<div id="organizerSummary" class="queue-summary"></div>
|
||||
<div id="organizerRows" class="download-list"></div>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Current /downloads Files</h3>
|
||||
<div id="downloadRows" class="download-list"></div>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Recently Planned or Moved</h3>
|
||||
<div id="recentDownloadRows" class="download-list"></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="page-releases" class="page panel">
|
||||
<div class="section-head">
|
||||
<h2>Missing & Upcoming</h2>
|
||||
<button id="releaseRefresh">Refresh releases</button>
|
||||
</div>
|
||||
<div id="releaseRows" class="release-grid"></div>
|
||||
</section>
|
||||
|
||||
<section id="page-tools" class="page panel">
|
||||
<div class="section-head">
|
||||
<h2>Library Tools</h2>
|
||||
<span class="muted">Uses the cached library index. Run a library scan first if results look stale.</span>
|
||||
</div>
|
||||
<div class="tool-grid">
|
||||
<button id="transcoderPlanButton">Build transcode queue</button>
|
||||
<button id="transcoderRunButton">Run next transcode</button>
|
||||
<button id="subtitleAuditButton">Run subtitle audit</button>
|
||||
<button id="duplicateButton">Duplicate finder</button>
|
||||
</div>
|
||||
<div id="toolOutput" class="tool-output"></div>
|
||||
</section>
|
||||
|
||||
<section id="page-settings" class="page panel">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>Settings</h2>
|
||||
<p class="muted">Runtime settings are saved in /data/state.json and override TOML/env values for this backend process.</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="tmdbTestButton" type="button">TMDb API Test</button>
|
||||
<button id="settingsSaveButton" type="button">Save settings</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settingsNotice" class="settings-notice" role="status" aria-live="polite"></div>
|
||||
<section class="settings-hero">
|
||||
<div>
|
||||
<h3>Dashboard Theme</h3>
|
||||
<p class="muted">Choose the local dashboard theme here. The default theme below is also configurable and saved on the server.</p>
|
||||
</div>
|
||||
<div id="themeOptions" class="theme-options"></div>
|
||||
</section>
|
||||
<div id="settingsForm" class="settings-stack"></div>
|
||||
<details open>
|
||||
<summary>Raw config</summary>
|
||||
<pre id="configView"></pre>
|
||||
</details>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<div id="mediaModal" class="modal">
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-shell">
|
||||
<button id="closeModal" class="modal-close">×</button>
|
||||
<div id="modalBody" class="modal-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="toastHost" class="toast-host" aria-live="polite"></div>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
822
dist/sortarr/web/src/styles.css
vendored
Normal file
822
dist/sortarr/web/src/styles.css
vendored
Normal file
@@ -0,0 +1,822 @@
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--font);
|
||||
font-size: calc(15px - (var(--compact, 0) * 1px));
|
||||
}
|
||||
.app-shell { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; }
|
||||
.sidebar {
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
padding: calc(20px * var(--density));
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
.brand { display: flex; gap: 12px; align-items: center; margin-bottom: 28px; }
|
||||
.brand-mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
font-weight: 800;
|
||||
}
|
||||
.brand small, #statusLine, .muted { color: var(--muted); }
|
||||
nav { display: grid; gap: 6px; }
|
||||
nav a {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
nav a.active, nav a:hover { background: var(--surface-2); color: var(--text); }
|
||||
.page { display: none; }
|
||||
.page.active { display: block; }
|
||||
select, input, button {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface-2);
|
||||
color: var(--text);
|
||||
border-radius: var(--radius);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
button { cursor: pointer; }
|
||||
button:hover { border-color: var(--accent); }
|
||||
button:disabled { cursor: wait; opacity: .62; }
|
||||
main { padding: 24px; display: grid; gap: 24px; align-content: start; }
|
||||
.topbar, .section-head { display: flex; justify-content: space-between; gap: 16px; align-items: center; }
|
||||
h1, h2, h3, p { margin: 0; }
|
||||
h1 { font-size: 28px; }
|
||||
h2 { font-size: 17px; }
|
||||
h3 { font-size: 14px; color: var(--muted); font-weight: 700; }
|
||||
.actions { display: flex; gap: 10px; }
|
||||
.grid { display: grid; gap: 16px; }
|
||||
.overview-grid { grid-template-columns: 1.3fr 1fr 1fr; }
|
||||
.panel {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: calc(18px * var(--density));
|
||||
}
|
||||
.storage-list, .event-list, .download-list, .bars { display: grid; gap: 12px; margin-top: 16px; }
|
||||
.storage-card { display: grid; gap: 8px; }
|
||||
.meter { height: 10px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
|
||||
.meter span { display: block; height: 100%; background: var(--accent); }
|
||||
.kv { display: flex; justify-content: space-between; color: var(--muted); font-size: 13px; }
|
||||
.bar-row { display: grid; grid-template-columns: 72px 1fr 44px; gap: 10px; align-items: center; }
|
||||
.event { border-left: 3px solid var(--accent); padding-left: 10px; color: var(--muted); }
|
||||
.event.error { border-color: var(--bad); }
|
||||
.segmented {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.segmented button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.segmented button.active {
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 18%, var(--surface-2));
|
||||
}
|
||||
.segmented span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.table-wrap { overflow: auto; margin-top: 16px; max-height: 68vh; }
|
||||
table { width: 100%; border-collapse: collapse; min-width: 720px; }
|
||||
th, td { border-bottom: 1px solid var(--border); padding: 10px; text-align: left; }
|
||||
th { color: var(--muted); font-weight: 600; }
|
||||
td:first-child {
|
||||
max-width: 520px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.download, .release {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.download.warning { border-color: var(--warn); }
|
||||
.poster-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.poster-card {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
text-align: left;
|
||||
}
|
||||
.poster-card.active .poster,
|
||||
.poster-card:hover .poster {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.poster {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
aspect-ratio: 2 / 3;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.poster img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.poster-placeholder {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--surface-2), var(--surface));
|
||||
color: var(--accent);
|
||||
font-size: 42px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.poster-card strong,
|
||||
.poster-card small {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.poster-card small { color: var(--muted); }
|
||||
.media-detail {
|
||||
margin-top: 22px;
|
||||
}
|
||||
.detail-shell {
|
||||
display: grid;
|
||||
grid-template-columns: 190px minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 22px;
|
||||
}
|
||||
.detail-poster {
|
||||
align-self: start;
|
||||
}
|
||||
.detail-body {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
.detail-block,
|
||||
.season-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.season-list details {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.season-list summary {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
.episode-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.episode {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
padding: 10px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
border-left: 3px solid var(--good);
|
||||
}
|
||||
.episode.missing { border-left-color: var(--bad); }
|
||||
.episode.upcoming { border-left-color: var(--warn); }
|
||||
.episode p {
|
||||
margin-top: 5px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.episode-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.probe-output {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.stream-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.stream-grid section {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
padding: 12px;
|
||||
}
|
||||
.stream-row {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.track-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.track-actions button {
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.downloads-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 1fr) minmax(0, 1.2fr) minmax(320px, .8fr);
|
||||
gap: 18px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.downloads-layout article { min-width: 0; }
|
||||
.queue-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(78px, 1fr));
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.queue-summary span {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
padding: 9px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.queue-summary strong {
|
||||
color: var(--text);
|
||||
font-size: 18px;
|
||||
}
|
||||
.download small, .download span {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.download.bundle {
|
||||
background: var(--surface);
|
||||
}
|
||||
.bundle-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
.bundle-head div {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
.subtitle-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.subtitle-chips span {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--surface-2);
|
||||
color: var(--muted);
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.download.loose {
|
||||
opacity: .82;
|
||||
}
|
||||
.organizer-card {
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
.organizer-card.needs-review,
|
||||
.organizer-card.dry-run {
|
||||
border-left-color: var(--warn);
|
||||
}
|
||||
.organizer-card.low-confidence,
|
||||
.organizer-card.skipped {
|
||||
border-left-color: var(--bad);
|
||||
}
|
||||
.organizer-card.moved {
|
||||
border-left-color: var(--good);
|
||||
}
|
||||
.confidence {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.confidence.good { color: var(--good); }
|
||||
.confidence.warn { color: var(--warn); }
|
||||
.confidence.bad { color: var(--bad); }
|
||||
.plan-paths {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
.plan-paths small {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
.plan-paths b {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.subtitle-list {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.plan-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.plan-actions button {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
.release-grid, .tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-top: 16px; }
|
||||
.release img {
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 3;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.release.missing { border-color: var(--bad); }
|
||||
.release.upcoming { border-color: var(--warn); }
|
||||
.release a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.tool-output { margin-top: 18px; display: grid; gap: 12px; }
|
||||
.tool-output h3 { margin: 0; font-size: 15px; }
|
||||
code {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
color: var(--muted);
|
||||
}
|
||||
.settings-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, .45fr) minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
margin-top: 18px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
}
|
||||
.settings-hero h3 { margin: 0 0 6px; }
|
||||
.settings-notice {
|
||||
display: none;
|
||||
margin-top: 14px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-2);
|
||||
color: var(--muted);
|
||||
}
|
||||
.settings-notice:not(:empty) { display: block; }
|
||||
.settings-stack {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
margin-top: 18px;
|
||||
max-width: 1180px;
|
||||
}
|
||||
.settings-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
overflow: hidden;
|
||||
}
|
||||
.settings-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface-2);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
.settings-card-head::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
.settings-card-head h3 {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
.settings-card-head p {
|
||||
margin: 0;
|
||||
}
|
||||
.settings-card-head > span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
.setting-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 1fr) minmax(260px, 520px);
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-radius: 0;
|
||||
background: var(--surface);
|
||||
padding: 16px;
|
||||
}
|
||||
.setting-row:last-child { border-bottom: 0; }
|
||||
.setting-rich {
|
||||
align-items: start;
|
||||
min-height: 0;
|
||||
}
|
||||
.setting-copy {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
max-width: 620px;
|
||||
}
|
||||
.setting-copy > div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
.setting-copy small,
|
||||
.setting-rich small {
|
||||
color: var(--muted);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.setting-path {
|
||||
font-size: 12px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
.setting-control {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
.setting-control.wide {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
justify-content: stretch;
|
||||
}
|
||||
.setting-row input[type="number"], .setting-row select { width: 132px; }
|
||||
.setting-row input[type="text"],
|
||||
.setting-row input[type="password"],
|
||||
.setting-row textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
padding: 10px;
|
||||
}
|
||||
.setting-row textarea {
|
||||
resize: vertical;
|
||||
min-height: 70px;
|
||||
}
|
||||
.setting-row input[type="checkbox"] {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
align-self: center;
|
||||
}
|
||||
.switch {
|
||||
justify-self: end;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 48px;
|
||||
height: 28px;
|
||||
}
|
||||
.switch input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
.switch span {
|
||||
width: 100%;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
transition: background .15s ease, border-color .15s ease;
|
||||
}
|
||||
.switch span::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
transition: transform .15s ease, background .15s ease;
|
||||
}
|
||||
.switch input:checked + span {
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 20%, var(--surface-2));
|
||||
}
|
||||
.switch input:checked + span::after {
|
||||
transform: translateX(20px);
|
||||
background: var(--accent);
|
||||
}
|
||||
.range-control {
|
||||
display: grid;
|
||||
align-content: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.range-control input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.range-control span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
.compound-control {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(160px, 1fr));
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
.compound-control input,
|
||||
.compound-control select {
|
||||
min-width: 0;
|
||||
}
|
||||
.compound-control label {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
.compound-control .inline-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.compound-control .span-2 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.theme-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
||||
gap: 10px;
|
||||
max-width: 980px;
|
||||
}
|
||||
.theme-option {
|
||||
display: grid;
|
||||
grid-template-columns: 42px 1fr;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-align: left;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.theme-option.active {
|
||||
border-color: var(--accent);
|
||||
box-shadow: inset 0 0 0 1px var(--accent);
|
||||
}
|
||||
.theme-swatch {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
width: 42px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
}
|
||||
.theme-swatch i,
|
||||
.theme-swatch b,
|
||||
.theme-swatch em {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
.theme-swatch i { background: var(--surface); }
|
||||
.theme-swatch b { background: var(--surface-2); }
|
||||
.theme-swatch em {
|
||||
grid-column: 1 / -1;
|
||||
background: var(--accent);
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
overflow: auto;
|
||||
background: var(--surface-2);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.toast-host {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
width: min(420px, calc(100vw - 36px));
|
||||
}
|
||||
.toast {
|
||||
transform: translateY(8px);
|
||||
opacity: 0;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-left: 4px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
box-shadow: 0 14px 34px rgba(0, 0, 0, .22);
|
||||
transition: opacity .18s ease, transform .18s ease;
|
||||
}
|
||||
.toast.visible {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
.toast.success { border-left-color: var(--good); }
|
||||
.toast.error { border-left-color: var(--bad); }
|
||||
@media (max-width: 900px) {
|
||||
.app-shell { grid-template-columns: 1fr; }
|
||||
.sidebar { position: static; height: auto; }
|
||||
.overview-grid { grid-template-columns: 1fr; }
|
||||
.downloads-layout { grid-template-columns: 1fr; }
|
||||
.detail-shell { grid-template-columns: 1fr; }
|
||||
.detail-poster { max-width: 220px; }
|
||||
.episode { grid-template-columns: 1fr; }
|
||||
.topbar, .section-head { align-items: stretch; flex-direction: column; }
|
||||
.actions, .pager { flex-wrap: wrap; }
|
||||
.settings-hero { grid-template-columns: 1fr; }
|
||||
.settings-card-head { flex-direction: column; }
|
||||
.setting-row { grid-template-columns: 1fr; gap: 14px; }
|
||||
.range-control { min-width: 0; }
|
||||
.setting-row input[type="text"],
|
||||
.setting-row input[type="password"],
|
||||
.setting-row textarea,
|
||||
.compound-control {
|
||||
width: 100%;
|
||||
}
|
||||
.compound-control { grid-template-columns: 1fr; }
|
||||
.bundle-head { flex-direction: column; }
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
.modal.active { display: flex; align-items: center; justify-content: center; }
|
||||
.modal-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.62);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.modal-shell {
|
||||
position: relative;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.42);
|
||||
animation: modal-slide 0.24s cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
@keyframes modal-slide {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
background: var(--surface-2);
|
||||
border: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
font-size: 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.modal-content {
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
background: var(--surface-2);
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.badge.accent { background: var(--accent); color: var(--bg); border: none; }
|
||||
|
||||
/* Multi-version Card Indicator */
|
||||
.poster-card { position: relative; }
|
||||
.card-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 5;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* Track actions enhanced */
|
||||
.track-actions { display: flex; gap: 6px; margin-top: 4px; }
|
||||
.track-actions button {
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.track-actions button:hover { border-color: var(--accent); }
|
||||
|
||||
.track-actions button.danger:hover { border-color: var(--bad); color: var(--bad); }
|
||||
.stream-row { padding: 8px 0; border-bottom: 1px solid var(--border); }
|
||||
.stream-row:last-child { border-bottom: none; }
|
||||
.circle-badge {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
.identify-panel { margin: 16px 0; background: var(--surface-2); }
|
||||
.identify-results { display: grid; gap: 8px; margin-top: 16px; max-height: 300px; overflow-y: auto; }
|
||||
.identify-result { display: flex; gap: 12px; align-items: flex-start; }
|
||||
.mini-poster { width: 60px; border-radius: 4px; flex-shrink: 0; }
|
||||
.small { font-size: 12px; }
|
||||
134
dist/sortarr/web/src/themes.css
vendored
Normal file
134
dist/sortarr/web/src/themes.css
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
:root,
|
||||
[data-theme="slate"] {
|
||||
--bg: #111318;
|
||||
--surface: #191d24;
|
||||
--surface-2: #222833;
|
||||
--text: #eef2f7;
|
||||
--muted: #96a1af;
|
||||
--border: #303846;
|
||||
--accent: #60a5fa;
|
||||
--good: #34d399;
|
||||
--warn: #fbbf24;
|
||||
--bad: #f87171;
|
||||
--radius: 8px;
|
||||
--density: 1;
|
||||
--font: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
[data-theme="midnight"] {
|
||||
--bg: #080b12;
|
||||
--surface: #121826;
|
||||
--surface-2: #1b2740;
|
||||
--text: #f8fafc;
|
||||
--muted: #93a4bd;
|
||||
--border: #293550;
|
||||
--accent: #22d3ee;
|
||||
--good: #4ade80;
|
||||
--warn: #facc15;
|
||||
--bad: #fb7185;
|
||||
}
|
||||
|
||||
[data-theme="graphite"] {
|
||||
--bg: #151515;
|
||||
--surface: #202020;
|
||||
--surface-2: #2b2b2b;
|
||||
--text: #f5f5f5;
|
||||
--muted: #b2b2b2;
|
||||
--border: #3a3a3a;
|
||||
--accent: #a3e635;
|
||||
--good: #86efac;
|
||||
--warn: #fde047;
|
||||
--bad: #fca5a5;
|
||||
}
|
||||
|
||||
[data-theme="nord"] {
|
||||
--bg: #202632;
|
||||
--surface: #2c3444;
|
||||
--surface-2: #374155;
|
||||
--text: #eceff4;
|
||||
--muted: #c0c9d8;
|
||||
--border: #4c566a;
|
||||
--accent: #88c0d0;
|
||||
--good: #a3be8c;
|
||||
--warn: #ebcb8b;
|
||||
--bad: #bf616a;
|
||||
}
|
||||
|
||||
[data-theme="dracula"] {
|
||||
--bg: #1d1b26;
|
||||
--surface: #282a36;
|
||||
--surface-2: #343746;
|
||||
--text: #f8f8f2;
|
||||
--muted: #c7bfdc;
|
||||
--border: #44475a;
|
||||
--accent: #bd93f9;
|
||||
--good: #50fa7b;
|
||||
--warn: #f1fa8c;
|
||||
--bad: #ff5555;
|
||||
}
|
||||
|
||||
[data-theme="solar"] {
|
||||
--bg: #f4f0df;
|
||||
--surface: #fffaf0;
|
||||
--surface-2: #eee8d5;
|
||||
--text: #273238;
|
||||
--muted: #657b83;
|
||||
--border: #d5cdb6;
|
||||
--accent: #268bd2;
|
||||
--good: #2aa198;
|
||||
--warn: #b58900;
|
||||
--bad: #dc322f;
|
||||
}
|
||||
|
||||
[data-theme="forest"] {
|
||||
--bg: #101812;
|
||||
--surface: #18251b;
|
||||
--surface-2: #213326;
|
||||
--text: #eef7ed;
|
||||
--muted: #a7b9a6;
|
||||
--border: #314638;
|
||||
--accent: #7ddf64;
|
||||
--good: #22c55e;
|
||||
--warn: #eab308;
|
||||
--bad: #ef4444;
|
||||
}
|
||||
|
||||
[data-theme="marine"] {
|
||||
--bg: #081417;
|
||||
--surface: #102225;
|
||||
--surface-2: #183236;
|
||||
--text: #edfdfd;
|
||||
--muted: #9fc5c7;
|
||||
--border: #28494e;
|
||||
--accent: #2dd4bf;
|
||||
--good: #5eead4;
|
||||
--warn: #fcd34d;
|
||||
--bad: #f97373;
|
||||
}
|
||||
|
||||
[data-theme="ember"] {
|
||||
--bg: #171111;
|
||||
--surface: #241818;
|
||||
--surface-2: #362221;
|
||||
--text: #fff7ed;
|
||||
--muted: #d7b6a2;
|
||||
--border: #513530;
|
||||
--accent: #fb923c;
|
||||
--good: #84cc16;
|
||||
--warn: #facc15;
|
||||
--bad: #f43f5e;
|
||||
}
|
||||
|
||||
[data-theme="paper"] {
|
||||
--bg: #f7f8fa;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #eef1f5;
|
||||
--text: #151a22;
|
||||
--muted: #5f6b7a;
|
||||
--border: #d6dce5;
|
||||
--accent: #2563eb;
|
||||
--good: #16a34a;
|
||||
--warn: #ca8a04;
|
||||
--bad: #dc2626;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user