diff --git a/tests/test_script_split_integrity.py b/tests/test_script_split_integrity.py
index ba08b4df..e9c86e98 100644
--- a/tests/test_script_split_integrity.py
+++ b/tests/test_script_split_integrity.py
@@ -53,7 +53,7 @@ SPLIT_MODULES = [
# Other JS files that exist in static/ but are NOT part of the split
NON_SPLIT_JS = {"setup-wizard.js", "docs.js", "helper.js", "particles.js", "worker-orbs.js",
"enrichment-manager.js", "origin-history.js", "blocklist.js",
- "watchlist-history.js", "credential-sets.js", "service-switch.js"}
+ "watchlist-history.js", "service-switch.js"}
# Pre-existing duplicate helper functions that lived in the original monolith.
# In a plain
-
diff --git a/webui/static/credential-sets.js b/webui/static/credential-sets.js
deleted file mode 100644
index 2745df7d..00000000
--- a/webui/static/credential-sets.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Service credential sets — admin manager (Settings) + per-profile quick-switch
- * modal (sidebar Service Status).
- *
- * Admin creates named credential "pills" per auth service; every profile picks
- * which one is active for it. Secrets are entered here but NEVER read back —
- * the API only ever returns id/label, so this UI shows names, never values.
- *
- * Backend: /api/credentials (admin CRUD) and /api/profiles/me/services[/select]
- * (per-profile selection, any profile).
- */
-
-// Display order + labels for the supported services (mirrors the backend
-// SERVICE_CREDENTIAL_SCHEMA). Each lists the fields the admin enters; `req`
-// marks required (matches server validation), `pw` renders as a password input.
-const CRED_SERVICES = [
- { id: 'spotify', name: 'Spotify', fields: [
- { key: 'client_id', label: 'Client ID', req: true },
- { key: 'client_secret', label: 'Client Secret', req: true, pw: true },
- { key: 'redirect_uri', label: 'Redirect URI (optional)' },
- ]},
- { id: 'tidal', name: 'Tidal', fields: [
- { key: 'access_token', label: 'Access Token', req: true, pw: true },
- { key: 'refresh_token', label: 'Refresh Token', req: true, pw: true },
- ]},
- { id: 'deezer', name: 'Deezer', fields: [
- { key: 'arl', label: 'ARL', req: true, pw: true },
- ]},
- { id: 'qobuz', name: 'Qobuz', fields: [
- { key: 'user_auth_token', label: 'User Auth Token', req: true, pw: true },
- ]},
- { id: 'plex', name: 'Plex', fields: [
- { key: 'base_url', label: 'Server URL', req: true },
- { key: 'token', label: 'Token', req: true, pw: true },
- ]},
- { id: 'jellyfin', name: 'Jellyfin', fields: [
- { key: 'base_url', label: 'Server URL', req: true },
- { key: 'api_key', label: 'API Key', req: true, pw: true },
- ]},
- { id: 'navidrome', name: 'Navidrome', fields: [
- { key: 'base_url', label: 'Server URL', req: true },
- { key: 'username', label: 'Username', req: true },
- { key: 'password', label: 'Password', req: true, pw: true },
- ]},
-];
-
-const _credServiceById = Object.fromEntries(CRED_SERVICES.map(s => [s.id, s]));
-
-function _credEsc(s) {
- return String(s == null ? '' : s).replace(/[&<>"']/g, c =>
- ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
-}
-
-// ── Admin manager (rendered inside Settings) ─────────────────────────────────
-
-async function loadCredentialSets() {
- const host = document.getElementById('credential-sets-container');
- if (!host) return;
- try {
- const res = await fetch('/api/credentials');
- if (res.status === 403) { host.innerHTML = ''; return; } // not admin
- const data = await res.json();
- _renderCredentialSets(host, (data && data.services) || {});
- } catch (e) {
- host.innerHTML = '
Could not load credential sets.
';
- }
-}
-
-function _renderCredentialSets(host, services) {
- host.innerHTML = CRED_SERVICES.map(svc => {
- const sets = services[svc.id] || [];
- const pills = sets.map(s => `
-
- ${_credEsc(s.label)}
-
- `).join('');
- return `
-
-
- ${svc.name}
-
-
-
${pills || 'No saved accounts'}
-
-
`;
- }).join('');
-}
-
-function toggleAddCredentialForm(serviceId) {
- const form = document.getElementById(`cred-add-form-${serviceId}`);
- if (!form) return;
- if (!form.hidden) { form.hidden = true; form.innerHTML = ''; return; }
- const svc = _credServiceById[serviceId];
- form.innerHTML = `
-
- ${svc.fields.map(f => `
- `).join('')}
-
-
-
-
- `;
- form.hidden = false;
- const first = document.getElementById(`cred-new-label-${serviceId}`);
- if (first) first.focus();
-}
-
-async function saveNewCredential(serviceId) {
- const svc = _credServiceById[serviceId];
- const errEl = document.getElementById(`cred-form-error-${serviceId}`);
- const label = (document.getElementById(`cred-new-label-${serviceId}`).value || '').trim();
- if (!label) { if (errEl) errEl.textContent = 'Give this account a name.'; return; }
- const payload = {};
- for (const f of svc.fields) {
- const v = (document.getElementById(`cred-new-${serviceId}-${f.key}`).value || '').trim();
- if (v) payload[f.key] = v;
- }
- const missing = svc.fields.filter(f => f.req && !payload[f.key]).map(f => f.label);
- if (missing.length) { if (errEl) errEl.textContent = `Required: ${missing.join(', ')}`; return; }
-
- try {
- const res = await fetch('/api/credentials', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ service: serviceId, label, payload }),
- });
- const data = await res.json();
- if (!data.success) { if (errEl) errEl.textContent = data.error || 'Save failed.'; return; }
- if (typeof showToast === 'function') showToast(`Saved "${label}" for ${svc.name}`, 'success');
- loadCredentialSets();
- } catch (e) {
- if (errEl) errEl.textContent = 'Save failed.';
- }
-}
-
-async function deleteCredentialSet(id, serviceName) {
- if (!confirm(`Delete this ${serviceName} account? Profiles using it will fall back to the default.`)) return;
- try {
- const res = await fetch(`/api/credentials/${id}`, { method: 'DELETE' });
- const data = await res.json();
- if (data.success) {
- if (typeof showToast === 'function') showToast('Account deleted', 'success');
- loadCredentialSets();
- } else if (typeof showToast === 'function') {
- showToast(data.error || 'Delete failed', 'error');
- }
- } catch (e) { /* no-op */ }
-}
diff --git a/webui/static/settings.js b/webui/static/settings.js
index 99140053..30288c68 100644
--- a/webui/static/settings.js
+++ b/webui/static/settings.js
@@ -1437,9 +1437,6 @@ async function loadSettingsData() {
// sentinel, which the server treats as "keep existing").
_wireRedactedSecrets();
- // Admin "Connected Accounts" manager (no-op / empty for non-admins).
- if (typeof loadCredentialSets === 'function') loadCredentialSets();
-
} catch (error) {
console.error('Error loading settings:', error);
showToast('Failed to load settings', 'error');
diff --git a/webui/static/style.css b/webui/static/style.css
index 760e886a..26f6e2e3 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -67522,97 +67522,6 @@ body.em-scroll-lock { overflow: hidden; }
.blocklist-search-results::-webkit-scrollbar, .blocklist-current::-webkit-scrollbar { width: 8px; }
.blocklist-search-results::-webkit-scrollbar-thumb, .blocklist-current::-webkit-scrollbar-thumb { background: rgba(var(--accent-rgb),0.28); border-radius: 999px; }
-/* ── Connected Accounts (admin credential-set manager) + quick-switch modal ── */
-.credential-sets-container { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
-.cred-svc-row {
- background: rgba(255, 255, 255, 0.03);
- border: 1px solid rgba(255, 255, 255, 0.06);
- border-radius: 12px;
- padding: 12px 14px;
-}
-.cred-svc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
-.cred-svc-name { font-weight: 600; font-size: 0.95rem; }
-.cred-add-btn {
- background: rgba(var(--accent-light-rgb), 0.12);
- color: rgb(var(--accent-light-rgb));
- border: 1px solid rgba(var(--accent-light-rgb), 0.35);
- border-radius: 8px; padding: 5px 12px; cursor: pointer; font-size: 0.82rem;
- transition: all 0.15s;
-}
-.cred-add-btn:hover { background: rgba(var(--accent-light-rgb), 0.22); }
-.cred-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
-.cred-pill {
- display: inline-flex; align-items: center; gap: 8px;
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 999px; padding: 5px 6px 5px 12px; font-size: 0.85rem;
-}
-.cred-pill-del {
- background: rgba(255, 80, 80, 0.12); color: #ff8a8a;
- border: none; border-radius: 999px; width: 20px; height: 20px;
- cursor: pointer; line-height: 1; font-size: 0.7rem;
-}
-.cred-pill-del:hover { background: rgba(255, 80, 80, 0.28); }
-.cred-empty { color: rgba(255, 255, 255, 0.4); font-size: 0.85rem; font-style: italic; }
-.cred-add-form { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
-.cred-input {
- background: rgba(0, 0, 0, 0.25);
- border: 1px solid rgba(255, 255, 255, 0.12);
- border-radius: 8px; padding: 9px 12px; color: #fff; font-size: 0.85rem;
-}
-.cred-input:focus { outline: none; border-color: rgb(var(--accent-light-rgb)); }
-.cred-form-actions { display: flex; gap: 8px; }
-.cred-save-btn {
- background: rgb(var(--accent-light-rgb)); color: #0a0a0a; font-weight: 600;
- border: none; border-radius: 8px; padding: 7px 18px; cursor: pointer;
-}
-.cred-cancel-btn {
- background: transparent; color: rgba(255, 255, 255, 0.6);
- border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 8px;
- padding: 7px 14px; cursor: pointer;
-}
-.cred-form-error { color: #ff8a8a; font-size: 0.8rem; min-height: 1em; }
-
-/* sidebar Service Status → clickable quick-switch trigger */
-.status-section--clickable { cursor: pointer; border-radius: 10px; transition: background 0.15s; }
-.status-section--clickable:hover { background: rgba(255, 255, 255, 0.04); }
-.status-switch-hint {
- float: right; font-size: 0.7rem; font-weight: 500;
- color: rgb(var(--accent-light-rgb)); opacity: 0; transition: opacity 0.15s;
-}
-.status-section--clickable:hover .status-switch-hint { opacity: 0.9; }
-
-/* quick-switch modal */
-.cred-switch-modal {
- background: #14151a; border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 16px; width: min(560px, 92vw); max-height: 82vh;
- overflow-y: auto; padding: 22px 24px;
- box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
-}
-.cred-switch-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 18px; }
-.cred-switch-title { margin: 0; font-size: 1.3rem; }
-.cred-switch-sub { margin: 4px 0 0; color: rgba(255, 255, 255, 0.5); font-size: 0.85rem; }
-.cred-switch-close {
- background: rgba(255, 255, 255, 0.06); border: none; color: #fff;
- border-radius: 8px; width: 32px; height: 32px; cursor: pointer; font-size: 0.9rem;
-}
-.cred-switch-close:hover { background: rgba(255, 255, 255, 0.14); }
-.cred-switch-row { padding: 12px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.05); }
-.cred-switch-row:last-child { border-bottom: none; }
-.cred-switch-svc { font-weight: 600; margin-bottom: 8px; font-size: 0.92rem; }
-.cred-switch-pills { display: flex; flex-wrap: wrap; gap: 8px; }
-.cred-switch-pill {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.12);
- border-radius: 999px; padding: 6px 16px; cursor: pointer;
- color: rgba(255, 255, 255, 0.8); font-size: 0.85rem; transition: all 0.15s;
-}
-.cred-switch-pill:hover { border-color: rgba(var(--accent-light-rgb), 0.5); }
-.cred-switch-pill.active {
- background: rgb(var(--accent-light-rgb)); color: #0a0a0a;
- border-color: rgb(var(--accent-light-rgb)); font-weight: 600;
-}
-
/* ── Quick-switch modal (active Metadata / Server / Download) — Manage-Workers style ── */
.ss-overlay { display: flex; align-items: center; justify-content: center; }
.ss-modal {