ui(settings): collapsible sections + Lidarr-style polish for Indexers tab
Restructure the Indexers & Downloaders tab to mirror the Paths & Organization / Post-Processing / Library Preferences pattern on the Library page — each subsystem (Indexers / Torrent Client / Usenet Client) gets its own collapsible section header with a status dot, hint, and animated arrow. Visual cues borrowed from Lidarr but rendered in SoulSync's existing dark-glass theme: - Intro hero card at the top of the tab with a 1-2-3 flow: Indexers find releases → Downloader fetches → SoulSync imports. Accent-color stepper pills + sub-copy summarising what's optional vs required. - Status dot in each section header — grey 'unknown' before testing, green after Test Connection succeeds, red on failure. Driven by _setIndStatusDot() helper called from each test handler. Soft glow on the active states. - Per-service service-title color accents matching existing spotify-title / tidal-title pattern: prowlarr-title (orange, Prowlarr brand), torrent-title (sky blue, qBit family), usenet-title (violet). - Indexer list cards replace the inline-emoji list — proper protocol badges (Torrent vs Usenet pill), monospace id chip, privacy tag, dimmed appearance when the indexer is disabled in Prowlarr. - Indexers section starts open; Torrent + Usenet start collapsed since most users only configure one protocol. No behavior changes — same fields, same endpoints, same save flow. Pure visual restructure of the panels added in the previous three commits.
This commit is contained in:
parent
e4ca56b499
commit
9b36d421ee
4 changed files with 396 additions and 146 deletions
330
webui/index.html
330
webui/index.html
|
|
@ -4947,160 +4947,206 @@
|
|||
</div>
|
||||
|
||||
<!-- ═══ INDEXERS & DOWNLOADERS ═══ -->
|
||||
<div class="settings-group" data-stg="indexers">
|
||||
<h3>🔎 Prowlarr (Indexer Aggregator)</h3>
|
||||
<div class="setting-help-text" style="margin-bottom: 10px;">
|
||||
Prowlarr manages your Usenet and torrent indexers and exposes them through one API. SoulSync uses Prowlarr to search across every indexer at once for the Torrent and Usenet download sources.
|
||||
<br><br>
|
||||
Don't have it? Grab Prowlarr from <code>prowlarr.com</code> (or your *arr stack). You point Prowlarr at your indexers, then point SoulSync at Prowlarr.
|
||||
<!-- Intro hero — explains the search → fetch flow in one glance -->
|
||||
<div class="settings-group ind-hero" data-stg="indexers">
|
||||
<div class="ind-hero-title">Indexers & Downloaders</div>
|
||||
<div class="ind-hero-flow">
|
||||
<span class="ind-hero-step"><span class="ind-hero-step-num">1</span> Indexers find releases</span>
|
||||
<span class="ind-hero-arrow">→</span>
|
||||
<span class="ind-hero-step"><span class="ind-hero-step-num">2</span> Downloader fetches them</span>
|
||||
<span class="ind-hero-arrow">→</span>
|
||||
<span class="ind-hero-step"><span class="ind-hero-step-num">3</span> SoulSync imports to library</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Prowlarr URL:</label>
|
||||
<input type="text" id="prowlarr-url" placeholder="http://localhost:9696">
|
||||
<div class="setting-help-text">
|
||||
Full URL to your Prowlarr instance (e.g. <code>http://192.168.1.100:9696</code>).
|
||||
</div>
|
||||
<div class="ind-hero-sub">
|
||||
Configure your Prowlarr instance to feed search results, and pick a torrent client and/or usenet client to handle the actual downloads. You only need to set up the protocols you actually use.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="prowlarr-api-key" placeholder="Your Prowlarr API key">
|
||||
<div class="setting-help-text">
|
||||
Found in Prowlarr → Settings → General → Security → API Key.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Prowlarr Status:</label>
|
||||
<div class="form-actions" style="margin-top: 4px;">
|
||||
<button class="test-button" id="prowlarr-test-btn" onclick="testProwlarrConnection()">
|
||||
Test Connection
|
||||
</button>
|
||||
<button class="test-button" id="prowlarr-refresh-indexers-btn" onclick="loadProwlarrIndexers()" style="margin-left: 6px;">
|
||||
Refresh Indexer List
|
||||
</button>
|
||||
<span id="prowlarr-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Configured Indexers:</label>
|
||||
<div id="prowlarr-indexer-list" class="setting-help-text" style="margin-top: 4px;">
|
||||
Connect to Prowlarr and click <strong>Refresh Indexer List</strong> to see the indexers SoulSync can search.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Restrict to indexer IDs (optional):</label>
|
||||
<input type="text" id="prowlarr-indexer-ids" placeholder="e.g. 1,3,7 (blank = all enabled indexers)">
|
||||
<div class="setting-help-text">
|
||||
Comma-separated Prowlarr indexer IDs. Leave blank to search every enabled indexer. Restrict when you have a private tracker you want to prioritise or a noisy public one to exclude.
|
||||
</div>
|
||||
|
||||
<!-- ─── INDEXERS section ─── -->
|
||||
<div class="settings-section-header" data-stg="indexers" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
||||
<span class="settings-section-arrow">▼</span>
|
||||
<h3>🔎 Indexers</h3>
|
||||
<span class="settings-section-hint">Prowlarr — search aggregator</span>
|
||||
<span class="ind-status-dot ind-status-dot-unknown" id="prowlarr-status-dot" title="Not tested"></span>
|
||||
</div>
|
||||
<div class="settings-section-body" data-stg="indexers">
|
||||
<div class="settings-group" data-stg="indexers">
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title prowlarr-title">⬢ Prowlarr</h4>
|
||||
<div class="setting-help-text" style="margin-bottom: 14px;">
|
||||
Prowlarr manages your Usenet and torrent indexers and exposes them through one API. SoulSync uses Prowlarr to search across every indexer at once for the Torrent and Usenet download sources.
|
||||
<br><br>
|
||||
Don't have it? Grab Prowlarr from <code>prowlarr.com</code> (or your *arr stack). You point Prowlarr at your indexers, then point SoulSync at Prowlarr.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Prowlarr URL:</label>
|
||||
<input type="text" id="prowlarr-url" placeholder="http://localhost:9696">
|
||||
<div class="setting-help-text">
|
||||
Full URL to your Prowlarr instance (e.g. <code>http://192.168.1.100:9696</code>).
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="prowlarr-api-key" placeholder="Your Prowlarr API key">
|
||||
<div class="setting-help-text">
|
||||
Found in Prowlarr → Settings → General → Security → API Key.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Restrict to indexer IDs (optional):</label>
|
||||
<input type="text" id="prowlarr-indexer-ids" placeholder="e.g. 1,3,7 (blank = all enabled indexers)">
|
||||
<div class="setting-help-text">
|
||||
Comma-separated Prowlarr indexer IDs. Leave blank to search every enabled indexer. Restrict when you have a private tracker you want to prioritise or a noisy public one to exclude.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status:</label>
|
||||
<div class="form-actions" style="margin-top: 4px;">
|
||||
<button class="test-button" id="prowlarr-test-btn" onclick="testProwlarrConnection()">
|
||||
Test Connection
|
||||
</button>
|
||||
<button class="test-button" id="prowlarr-refresh-indexers-btn" onclick="loadProwlarrIndexers()" style="margin-left: 6px;">
|
||||
Refresh Indexer List
|
||||
</button>
|
||||
<span id="prowlarr-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Configured Indexers:</label>
|
||||
<div id="prowlarr-indexer-list" class="ind-indexer-list">
|
||||
Connect to Prowlarr and click <strong>Refresh Indexer List</strong> to see the indexers SoulSync can search.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ TORRENT CLIENT ═══ -->
|
||||
<div class="settings-group" data-stg="indexers">
|
||||
<!-- ─── TORRENT CLIENT section ─── -->
|
||||
<div class="settings-section-header collapsed" data-stg="indexers" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
||||
<span class="settings-section-arrow">▼</span>
|
||||
<h3>🧲 Torrent Client</h3>
|
||||
<div class="setting-help-text" style="margin-bottom: 10px;">
|
||||
Where SoulSync sends torrents once Prowlarr finds them. Pick the client you already use — only one can be active at a time. Add the URL of the client's WebUI, log in if it asks for credentials, and hit Test Connection.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Client Type:</label>
|
||||
<select id="torrent-client-type" class="form-select">
|
||||
<option value="qbittorrent">qBittorrent</option>
|
||||
<option value="transmission">Transmission</option>
|
||||
<option value="deluge">Deluge 2.x</option>
|
||||
</select>
|
||||
<div class="setting-help-text" id="torrent-client-help">
|
||||
qBittorrent: WebUI port (default 8080). Transmission: RPC port (default 9091). Deluge: WebUI port (default 8112).
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>WebUI URL:</label>
|
||||
<input type="text" id="torrent-client-url" placeholder="http://localhost:8080">
|
||||
</div>
|
||||
<div class="form-group" id="torrent-client-username-group">
|
||||
<label>Username:</label>
|
||||
<input type="text" id="torrent-client-username" placeholder="leave blank if WebUI doesn't require auth">
|
||||
<div class="setting-help-text">
|
||||
qBittorrent and Transmission use username + password. Deluge uses password only — paste it in the password field below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password:</label>
|
||||
<input type="password" id="torrent-client-password" placeholder="WebUI password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Category / Label:</label>
|
||||
<input type="text" id="torrent-client-category" placeholder="soulsync" value="soulsync">
|
||||
<div class="setting-help-text">
|
||||
SoulSync tags every torrent it adds with this label so it's easy to spot in your client. Deluge needs the Label plugin installed for this to stick.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Save Path (optional):</label>
|
||||
<input type="text" id="torrent-client-save-path" placeholder="leave blank to use the client's default">
|
||||
<div class="setting-help-text">
|
||||
Override where the torrent client writes downloads. This path is on the <strong>torrent client's</strong> machine, not SoulSync's.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Torrent Client Status:</label>
|
||||
<div class="form-actions" style="margin-top: 4px;">
|
||||
<button class="test-button" id="torrent-client-test-btn" onclick="testTorrentClientConnection()">
|
||||
Test Connection
|
||||
</button>
|
||||
<span id="torrent-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
<span class="settings-section-hint">qBittorrent · Transmission · Deluge</span>
|
||||
<span class="ind-status-dot ind-status-dot-unknown" id="torrent-client-status-dot" title="Not tested"></span>
|
||||
</div>
|
||||
<div class="settings-section-body collapsed" data-stg="indexers">
|
||||
<div class="settings-group" data-stg="indexers">
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title torrent-title">⬢ Torrent Downloader</h4>
|
||||
<div class="setting-help-text" style="margin-bottom: 14px;">
|
||||
Where SoulSync sends torrents once Prowlarr finds them. Pick the client you already use — only one can be active at a time. Add the URL of the client's WebUI, log in if it asks for credentials, and hit Test Connection.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Client Type:</label>
|
||||
<select id="torrent-client-type" class="form-select">
|
||||
<option value="qbittorrent">qBittorrent</option>
|
||||
<option value="transmission">Transmission</option>
|
||||
<option value="deluge">Deluge 2.x</option>
|
||||
</select>
|
||||
<div class="setting-help-text" id="torrent-client-help">
|
||||
qBittorrent: WebUI port (default 8080). Transmission: RPC port (default 9091). Deluge: WebUI port (default 8112).
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>WebUI URL:</label>
|
||||
<input type="text" id="torrent-client-url" placeholder="http://localhost:8080">
|
||||
</div>
|
||||
<div class="form-group" id="torrent-client-username-group">
|
||||
<label>Username:</label>
|
||||
<input type="text" id="torrent-client-username" placeholder="leave blank if WebUI doesn't require auth">
|
||||
<div class="setting-help-text">
|
||||
qBittorrent and Transmission use username + password. Deluge uses password only — paste it in the password field below.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password:</label>
|
||||
<input type="password" id="torrent-client-password" placeholder="WebUI password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Category / Label:</label>
|
||||
<input type="text" id="torrent-client-category" placeholder="soulsync" value="soulsync">
|
||||
<div class="setting-help-text">
|
||||
SoulSync tags every torrent it adds with this label so it's easy to spot in your client. Deluge needs the Label plugin installed for this to stick.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Save Path (optional):</label>
|
||||
<input type="text" id="torrent-client-save-path" placeholder="leave blank to use the client's default">
|
||||
<div class="setting-help-text">
|
||||
Override where the torrent client writes downloads. This path is on the <strong>torrent client's</strong> machine, not SoulSync's.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status:</label>
|
||||
<div class="form-actions" style="margin-top: 4px;">
|
||||
<button class="test-button" id="torrent-client-test-btn" onclick="testTorrentClientConnection()">
|
||||
Test Connection
|
||||
</button>
|
||||
<span id="torrent-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ USENET CLIENT ═══ -->
|
||||
<div class="settings-group" data-stg="indexers">
|
||||
<!-- ─── USENET CLIENT section ─── -->
|
||||
<div class="settings-section-header collapsed" data-stg="indexers" onclick="this.classList.toggle('collapsed'); const b=this.nextElementSibling; b.classList.toggle('collapsed'); b.style.display=b.classList.contains('collapsed')?'none':''">
|
||||
<span class="settings-section-arrow">▼</span>
|
||||
<h3>📰 Usenet Client</h3>
|
||||
<div class="setting-help-text" style="margin-bottom: 10px;">
|
||||
Where SoulSync sends NZBs once Prowlarr finds them. Pick one usenet downloader. SABnzbd uses an API key for auth; NZBGet uses a username + password.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Client Type:</label>
|
||||
<select id="usenet-client-type" class="form-select" onchange="updateUsenetClientUI()">
|
||||
<option value="sabnzbd">SABnzbd</option>
|
||||
<option value="nzbget">NZBGet</option>
|
||||
</select>
|
||||
<div class="setting-help-text">
|
||||
SABnzbd: default WebUI port 8080. NZBGet: default WebUI port 6789.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>WebUI URL:</label>
|
||||
<input type="text" id="usenet-client-url" placeholder="http://localhost:8080">
|
||||
</div>
|
||||
<div class="form-group" id="usenet-apikey-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="usenet-client-api-key" placeholder="SABnzbd API key">
|
||||
<div class="setting-help-text">
|
||||
SABnzbd → Config → General → API Key. Used by SABnzbd only.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="usenet-username-group" style="display: none;">
|
||||
<label>Username:</label>
|
||||
<input type="text" id="usenet-client-username" placeholder="NZBGet WebUI username">
|
||||
</div>
|
||||
<div class="form-group" id="usenet-password-group" style="display: none;">
|
||||
<label>Password:</label>
|
||||
<input type="password" id="usenet-client-password" placeholder="NZBGet WebUI password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Category / Label:</label>
|
||||
<input type="text" id="usenet-client-category" placeholder="soulsync" value="soulsync">
|
||||
<div class="setting-help-text">
|
||||
SoulSync tags every NZB with this category so it ends up in a predictable post-processing folder.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Usenet Client Status:</label>
|
||||
<div class="form-actions" style="margin-top: 4px;">
|
||||
<button class="test-button" id="usenet-client-test-btn" onclick="testUsenetClientConnection()">
|
||||
Test Connection
|
||||
</button>
|
||||
<span id="usenet-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
<span class="settings-section-hint">SABnzbd · NZBGet</span>
|
||||
<span class="ind-status-dot ind-status-dot-unknown" id="usenet-client-status-dot" title="Not tested"></span>
|
||||
</div>
|
||||
<div class="settings-section-body collapsed" data-stg="indexers">
|
||||
<div class="settings-group" data-stg="indexers">
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title usenet-title">⬢ Usenet Downloader</h4>
|
||||
<div class="setting-help-text" style="margin-bottom: 14px;">
|
||||
Where SoulSync sends NZBs once Prowlarr finds them. Pick one usenet downloader. SABnzbd uses an API key for auth; NZBGet uses a username + password.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Client Type:</label>
|
||||
<select id="usenet-client-type" class="form-select" onchange="updateUsenetClientUI()">
|
||||
<option value="sabnzbd">SABnzbd</option>
|
||||
<option value="nzbget">NZBGet</option>
|
||||
</select>
|
||||
<div class="setting-help-text">
|
||||
SABnzbd: default WebUI port 8080. NZBGet: default WebUI port 6789.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>WebUI URL:</label>
|
||||
<input type="text" id="usenet-client-url" placeholder="http://localhost:8080">
|
||||
</div>
|
||||
<div class="form-group" id="usenet-apikey-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="usenet-client-api-key" placeholder="SABnzbd API key">
|
||||
<div class="setting-help-text">
|
||||
SABnzbd → Config → General → API Key. Used by SABnzbd only.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" id="usenet-username-group" style="display: none;">
|
||||
<label>Username:</label>
|
||||
<input type="text" id="usenet-client-username" placeholder="NZBGet WebUI username">
|
||||
</div>
|
||||
<div class="form-group" id="usenet-password-group" style="display: none;">
|
||||
<label>Password:</label>
|
||||
<input type="password" id="usenet-client-password" placeholder="NZBGet WebUI password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Category / Label:</label>
|
||||
<input type="text" id="usenet-client-category" placeholder="soulsync" value="soulsync">
|
||||
<div class="setting-help-text">
|
||||
SoulSync tags every NZB with this category so it ends up in a predictable post-processing folder.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status:</label>
|
||||
<div class="form-actions" style="margin-top: 4px;">
|
||||
<button class="test-button" id="usenet-client-test-btn" onclick="testUsenetClientConnection()">
|
||||
Test Connection
|
||||
</button>
|
||||
<span id="usenet-client-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3415,6 +3415,7 @@ function closeHelperSearch() {
|
|||
const WHATS_NEW = {
|
||||
'2.6.0': [
|
||||
{ unreleased: true },
|
||||
{ title: 'Indexers & Downloaders tab restyled with collapsible sections', desc: 'tab now opens with an intro hero card explaining the flow (indexers find releases → downloader fetches them → SoulSync imports) and folds the rest into three collapsible sections: Indexers, Torrent Client, Usenet Client. each section gets a per-service color accent (Prowlarr orange, torrent sky-blue, usenet violet), a status dot in the header (green when Test Connection succeeds, red on failure, grey before testing), and Lidarr-style indexer cards with protocol badges instead of the inline emoji list. Indexers section is open by default; the downloader sections start collapsed since not everyone uses both protocols.' },
|
||||
{ title: 'Regression tests for the new indexer + downloader plumbing', desc: 'mocked unit tests for Prowlarr + all five downloader adapters (qBittorrent, Transmission, Deluge, SABnzbd, NZBGet). 54 tests pin the state-mapping tables, the parse logic, and the protocol quirks each client needs handled (qBit Referer header, Transmission session-id renegotiation, Deluge magnet-vs-URL method split, SAB queue+history merge, NZBGet 64-bit size fields). next time anyone touches one of these adapters, CI catches breakage before it hits a real downloader.' },
|
||||
{ title: 'Usenet client adapters (SABnzbd, NZBGet)', desc: 'third commit in the torrent + usenet rollout. SoulSync now talks to SABnzbd and NZBGet through a sibling adapter contract that mirrors the torrent adapter set — pick one downloader in Settings → Indexers & Downloaders, fill in its API key (SABnzbd) or username + password (NZBGet), and Test Connection confirms the link. all three layers are now stood up: Prowlarr finds releases, the torrent adapter and the usenet adapter each know how to ship work to the underlying client. next commit wires Prowlarr search → adapter dispatch → archive extraction so the new sources actually download. job state mapping covers SABnzbd queue + history and NZBGet groups + history, including the verify/repair/unpack phases that are unique to usenet.' },
|
||||
{ title: 'Torrent client adapters (qBittorrent, Transmission, Deluge)', desc: 'second commit in the torrent + usenet rollout. SoulSync can now talk to any of the three big torrent clients through a single adapter contract — pick which one you use in Settings → Indexers & Downloaders, paste your WebUI URL and credentials, and Test Connection confirms the link. each adapter handles its own auth quirk (qBit cookies, Transmission session-id, Deluge JSON-RPC) and maps native state strings onto a uniform set so the rest of the app stays client-agnostic. no download wiring yet — that gets layered on once the usenet client adapters land in the next commit.' },
|
||||
|
|
|
|||
|
|
@ -3582,6 +3582,22 @@ async function testLidarrConnection() {
|
|||
}
|
||||
}
|
||||
|
||||
function _setIndStatusDot(dotId, state) {
|
||||
const dot = document.getElementById(dotId);
|
||||
if (!dot) return;
|
||||
dot.classList.remove('ind-status-dot-unknown', 'ind-status-dot-connected', 'ind-status-dot-error');
|
||||
if (state === 'connected') {
|
||||
dot.classList.add('ind-status-dot-connected');
|
||||
dot.title = 'Connected';
|
||||
} else if (state === 'error') {
|
||||
dot.classList.add('ind-status-dot-error');
|
||||
dot.title = 'Connection failed';
|
||||
} else {
|
||||
dot.classList.add('ind-status-dot-unknown');
|
||||
dot.title = 'Not tested';
|
||||
}
|
||||
}
|
||||
|
||||
async function testProwlarrConnection() {
|
||||
const statusEl = document.getElementById('prowlarr-connection-status');
|
||||
if (!statusEl) return;
|
||||
|
|
@ -3598,14 +3614,17 @@ async function testProwlarrConnection() {
|
|||
if (data.success) {
|
||||
statusEl.textContent = data.message || 'Connected';
|
||||
statusEl.style.color = '#4caf50';
|
||||
_setIndStatusDot('prowlarr-status-dot', 'connected');
|
||||
loadProwlarrIndexers();
|
||||
} else {
|
||||
statusEl.textContent = data.error || 'Connection failed';
|
||||
statusEl.style.color = '#f44336';
|
||||
_setIndStatusDot('prowlarr-status-dot', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Connection error';
|
||||
statusEl.style.color = '#f44336';
|
||||
_setIndStatusDot('prowlarr-status-dot', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3641,13 +3660,16 @@ async function testUsenetClientConnection() {
|
|||
if (data.success) {
|
||||
statusEl.textContent = data.message || 'Connected';
|
||||
statusEl.style.color = '#4caf50';
|
||||
_setIndStatusDot('usenet-client-status-dot', 'connected');
|
||||
} else {
|
||||
statusEl.textContent = data.error || 'Connection failed';
|
||||
statusEl.style.color = '#f44336';
|
||||
_setIndStatusDot('usenet-client-status-dot', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Connection error';
|
||||
statusEl.style.color = '#f44336';
|
||||
_setIndStatusDot('usenet-client-status-dot', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3667,13 +3689,16 @@ async function testTorrentClientConnection() {
|
|||
if (data.success) {
|
||||
statusEl.textContent = data.message || 'Connected';
|
||||
statusEl.style.color = '#4caf50';
|
||||
_setIndStatusDot('torrent-client-status-dot', 'connected');
|
||||
} else {
|
||||
statusEl.textContent = data.error || 'Connection failed';
|
||||
statusEl.style.color = '#f44336';
|
||||
_setIndStatusDot('torrent-client-status-dot', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Connection error';
|
||||
statusEl.style.color = '#f44336';
|
||||
_setIndStatusDot('torrent-client-status-dot', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3694,10 +3719,16 @@ async function loadProwlarrIndexers() {
|
|||
}
|
||||
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
const rows = data.indexers.map(idx => {
|
||||
const proto = idx.protocol === 'usenet' ? '📰 Usenet' : '🧲 Torrent';
|
||||
const enabled = idx.enable ? '✅' : '⛔';
|
||||
const privacy = idx.privacy ? `<span style="opacity:0.6;">(${esc(idx.privacy)})</span>` : '';
|
||||
return `<div style="padding:3px 0;">${enabled} <strong>#${esc(idx.id)}</strong> ${esc(idx.name)} — ${proto} ${privacy}</div>`;
|
||||
const protoClass = idx.protocol === 'usenet' ? 'ind-indexer-card-proto-usenet' : 'ind-indexer-card-proto-torrent';
|
||||
const protoLabel = idx.protocol === 'usenet' ? 'Usenet' : 'Torrent';
|
||||
const privacyHTML = idx.privacy ? `<span class="ind-indexer-card-privacy">${esc(idx.privacy)}</span>` : '';
|
||||
const disabledClass = idx.enable ? '' : ' ind-indexer-card-disabled';
|
||||
return `<div class="ind-indexer-card${disabledClass}">
|
||||
<span class="ind-indexer-card-id">#${esc(idx.id)}</span>
|
||||
<span class="ind-indexer-card-name">${esc(idx.name)}</span>
|
||||
${privacyHTML}
|
||||
<span class="ind-indexer-card-proto ${protoClass}">${protoLabel}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
listEl.innerHTML = rows;
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -3243,6 +3243,178 @@ body.helper-mode-active #dashboard-activity-feed:hover {
|
|||
color: #eb743b;
|
||||
}
|
||||
|
||||
/* Indexers & Downloaders tab — service title colors */
|
||||
.prowlarr-title {
|
||||
color: #e07712; /* Prowlarr brand orange */
|
||||
}
|
||||
|
||||
.torrent-title {
|
||||
color: #5dade2; /* Torrent — sky blue (qBit family) */
|
||||
}
|
||||
|
||||
.usenet-title {
|
||||
color: #a78bfa; /* Usenet — violet */
|
||||
}
|
||||
|
||||
/* Lidarr-style intro hero card on Indexers & Downloaders tab */
|
||||
.ind-hero {
|
||||
padding: 18px 22px;
|
||||
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.08), rgba(35, 35, 35, 0.6));
|
||||
border: 1px solid rgba(var(--accent-rgb), 0.15);
|
||||
}
|
||||
|
||||
.ind-hero-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
color: rgb(var(--accent-rgb));
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ind-hero-flow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.ind-hero-step {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.ind-hero-step-num {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: rgba(var(--accent-rgb), 0.2);
|
||||
color: rgb(var(--accent-rgb));
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ind-hero-arrow {
|
||||
color: rgba(var(--accent-rgb), 0.55);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ind-hero-sub {
|
||||
font-size: 12.5px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
/* Section-header status dots — Lidarr-style green/red/grey indicator */
|
||||
.ind-status-dot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
margin-left: 8px;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.ind-status-dot-unknown {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.ind-status-dot-connected {
|
||||
background: #2ecc71;
|
||||
box-shadow: 0 0 8px rgba(46, 204, 113, 0.6), 0 0 0 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.ind-status-dot-error {
|
||||
background: #e74c3c;
|
||||
box-shadow: 0 0 8px rgba(231, 76, 60, 0.5), 0 0 0 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Indexer list — card-per-indexer, Lidarr-style */
|
||||
.ind-indexer-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.ind-indexer-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 8px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.ind-indexer-card:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.ind-indexer-card-id {
|
||||
font-family: 'SF Mono', Monaco, monospace;
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ind-indexer-card-name {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.ind-indexer-card-proto {
|
||||
font-size: 11px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ind-indexer-card-proto-torrent {
|
||||
background: rgba(93, 173, 226, 0.15);
|
||||
color: #5dade2;
|
||||
border: 1px solid rgba(93, 173, 226, 0.3);
|
||||
}
|
||||
|
||||
.ind-indexer-card-proto-usenet {
|
||||
background: rgba(167, 139, 250, 0.15);
|
||||
color: #a78bfa;
|
||||
border: 1px solid rgba(167, 139, 250, 0.3);
|
||||
}
|
||||
|
||||
.ind-indexer-card-privacy {
|
||||
font-size: 10.5px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ind-indexer-card-disabled {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.acoustid-title {
|
||||
color: #ba55d3;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue