@ -239,6 +239,10 @@
< span class = "nav-icon" > < svg class = "nav-svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" > < path d = "M4 19.5A2.5 2.5 0 0 1 6.5 17H20" / > < path d = "M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" / > < line x1 = "9" y1 = "7" x2 = "16" y2 = "7" / > < line x1 = "9" y1 = "11" x2 = "14" y2 = "11" / > < / svg > < / span >
< span class = "nav-text" > Library< / span >
< / button >
< button class = "nav-button" data-page = "tools" >
< span class = "nav-icon" > < svg class = "nav-svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" > < path d = "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" / > < / svg > < / span >
< span class = "nav-text" > Tools< / span >
< / button >
< button class = "nav-button" data-page = "stats" >
< span class = "nav-icon" > < svg class = "nav-svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "1.8" stroke-linecap = "round" stroke-linejoin = "round" > < path d = "M18 20V10" / > < path d = "M12 20V4" / > < path d = "M6 20v-6" / > < / svg > < / span >
< span class = "nav-text" > Stats< / span >
@ -727,335 +731,18 @@
< div class = "dashboard-section" >
< h3 class = "section-title" > Tools & Operations< / h3 >
< div class = "tools-grid" >
< div class = "tool-card" id = "db-updater-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Database Updater< / h4 >
< button class = "tool-help-button" data-tool = "db-updater"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Last Full Refresh: < span id = "db-last-refresh" > Never< / span > < / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Artists:< / span >
< span class = "stat-item-value" id = "db-stat-artists" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Albums:< / span >
< span class = "stat-item-value" id = "db-stat-albums" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Tracks:< / span >
< span class = "stat-item-value" id = "db-stat-tracks" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Size:< / span >
< span class = "stat-item-value" id = "db-stat-size" > 0.0 MB< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< select id = "db-refresh-type" >
< option value = "incremental" > Incremental Update< / option >
< option value = "full" > Full Refresh< / option >
< / select >
< button id = "db-update-button" > Update Database< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "db-phase-label" > Idle< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "db-progress-bar" style = "width: 0%;" > < / div >
< / div >
< p class = "progress-details-label" id = "db-progress-label" > 0 / 0 artists (0.0%)< / p >
< / div >
< / div >
< div class = "tool-card" id = "metadata-updater-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Metadata Updater< / h4 >
< button class = "tool-help-button" data-tool = "metadata-updater"
title="Learn more about this tool">?< / button >
< / div >
< p class = "metadata-updater-description tool-card-info" > Updates artist photos, genres,
and album art from Spotify.< / p >
< div class = "tool-card-controls" >
< select id = "metadata-refresh-interval" >
< option value = "180" > 6 months< / option >
< option value = "90" > 3 months< / option >
< option value = "30" selected > 1 month< / option >
< option value = "14" > 2 weeks< / option >
< option value = "7" > 1 week< / option >
< option value = "0" > Full refresh< / option >
< / select >
< button id = "metadata-update-button" > Begin Update< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "metadata-phase-label" > Current Artist: Not
running< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "metadata-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "metadata-progress-label" > 0 / 0 artists (0.0%)
< / p >
< / div >
< / div >
< div class = "tool-card" id = "quality-scanner-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Quality Scanner< / h4 >
< button class = "tool-help-button" data-tool = "quality-scanner"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Scan library for tracks below quality preferences< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Processed:< / span >
< span class = "stat-item-value" id = "quality-stat-processed" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Quality Met:< / span >
< span class = "stat-item-value" id = "quality-stat-met" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Low Quality:< / span >
< span class = "stat-item-value" id = "quality-stat-low" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Matched:< / span >
< span class = "stat-item-value" id = "quality-stat-matched" > 0< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< select id = "quality-scan-scope" >
< option value = "watchlist" > Watchlist Artists Only< / option >
< option value = "all" > All Library Tracks< / option >
< / select >
< button id = "quality-scan-button" > Scan Library< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "quality-phase-label" > Ready to scan< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "quality-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "quality-progress-label" > 0 / 0 tracks scanned
(0.0%)< / p >
< / div >
< / div >
< div class = "tool-card" id = "duplicate-cleaner-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Duplicate Cleaner< / h4 >
< button class = "tool-help-button" data-tool = "duplicate-cleaner"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Detect and remove duplicate tracks in Transfer folder< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Files Scanned:< / span >
< span class = "stat-item-value" id = "duplicate-stat-scanned" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Duplicates Found:< / span >
< span class = "stat-item-value" id = "duplicate-stat-found" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Deleted:< / span >
< span class = "stat-item-value" id = "duplicate-stat-deleted" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Space Freed:< / span >
< span class = "stat-item-value" id = "duplicate-stat-space" > 0 MB< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "duplicate-clean-button" > Clean Duplicates< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "duplicate-phase-label" > Ready to scan< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "duplicate-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "duplicate-progress-label" > 0 files scanned
(0.0%)< / p >
< / div >
< / div >
< div class = "tool-card" id = "discovery-pool-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Discovery Pool< / h4 >
< / div >
< p class = "tool-card-info" > View and fix matched/failed discovery results across all mirrored playlists< / p >
< div class = "tool-card-stats" id = "discovery-pool-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Matched:< / span >
< span class = "stat-item-value" id = "discovery-pool-matched-count" > — < / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Failed:< / span >
< span class = "stat-item-value" id = "discovery-pool-failed-count" style = "background-color: rgba(239, 68, 68, 0.15); color: #ef4444;" > — < / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button onclick = "openDiscoveryPoolModal()" > Open Discovery Pool< / button >
< / div >
< / div >
< div class = "tool-card" id = "retag-tool-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Retag Tool< / h4 >
< button class = "tool-help-button" data-tool = "retag-tool"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Fix metadata on previously downloaded albums & singles< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Groups:< / span >
< span class = "stat-item-value" id = "retag-stat-groups" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Tracks:< / span >
< span class = "stat-item-value" id = "retag-stat-tracks" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Artists:< / span >
< span class = "stat-item-value" id = "retag-stat-artists" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Status:< / span >
< span class = "stat-item-value" id = "retag-stat-status" > Idle< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "retag-open-button" > Open Retag Tool< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "retag-phase-label" > Ready< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "retag-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "retag-progress-label" > 0 / 0 tracks (0.0%)< / p >
< / div >
< / div >
< div class = "tool-card" id = "media-scan-card" style = "display: none;" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Media Server Scan< / h4 >
< button class = "tool-help-button" data-tool = "media-scan"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Manually trigger Plex media library scan for music< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Last Scan:< / span >
< span class = "stat-item-value" id = "media-scan-last-time" > Never< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Status:< / span >
< span class = "stat-item-value" id = "media-scan-status" > Idle< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "media-scan-button" class = "media-scan-btn" >
< span class = "scan-icon" > 📡< / span >
< span class = "scan-text" > Scan Library< / span >
< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "media-scan-phase-label" > Ready to scan< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "media-scan-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "media-scan-progress-label" > Waiting for scan
request< / p >
< / div >
< / div >
< div class = "tool-card" id = "backup-manager-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Backup Manager< / h4 >
< button class = "tool-help-button" data-tool = "backup-manager"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Create, download, restore and manage database backups< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Last Backup:< / span >
< span class = "stat-item-value" id = "backup-stat-last" > Never< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Backups:< / span >
< span class = "stat-item-value" id = "backup-stat-count" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Latest Size:< / span >
< span class = "stat-item-value" id = "backup-stat-latest-size" > —< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > DB Size:< / span >
< span class = "stat-item-value" id = "backup-stat-db-size" > —< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "backup-now-button" > Backup Now< / button >
< / div >
< div id = "backup-list-container" class = "backup-list-container" > < / div >
< / div >
<!-- Metadata Cache Tool Card -->
< div class = "tool-card" id = "metadata-cache-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Metadata Cache< / h4 >
< button class = "tool-help-button" data-tool = "metadata-cache"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Cached API responses from Spotify & iTunes< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Artists:< / span >
< span class = "stat-item-value" id = "mcache-stat-artists" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Albums:< / span >
< span class = "stat-item-value" id = "mcache-stat-albums" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Tracks:< / span >
< span class = "stat-item-value" id = "mcache-stat-tracks" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Hits:< / span >
< span class = "stat-item-value" id = "mcache-stat-hits" > 0< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "mcache-browse-button" onclick = "openMetadataCacheModal()" > Browse Cache< / button >
< button onclick = "openCacheHealthModal()" class = "tool-card-btn-secondary" > Cache Health< / button >
< / div >
< / div >
<!-- Download Blacklist Card -->
< div class = "tool-card" id = "blacklist-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Download Blacklist< / h4 >
< / div >
< p class = "tool-card-info" > Blocked sources that won't be used for future downloads< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Blocked:< / span >
< span class = "stat-item-value" id = "blacklist-count" > 0< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button onclick = "openBlacklistModal()" > View Blacklist< / button >
< div class = "dashboard-tools-link" onclick = "navigateToPage('tools')" >
< div class = "dashboard-tools-link-content" >
< svg width = "20" height = "20" viewBox = "0 0 24 24" fill = "none" stroke = "rgba(255,255,255,0.5)" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" > < path d = "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" / > < / svg >
< div >
< span class = "dashboard-tools-link-title" > Database, scanning, backups, cache, maintenance & more< / span >
< / div >
< / div >
< svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "rgba(255,255,255,0.3)" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" > < polyline points = "9 18 15 12 9 6" / > < / svg >
< / div >
< / div >
< div class = "dashboard-section" >
< div class = "section-title-row" >
< h3 class = "section-title" > Recent Activity< / h3 >
@ -6411,6 +6098,443 @@
< / div >
< / div >
<!-- ═══════════════════════════════════════════════════════════════════
TOOLS PAGE
═══════════════════════════════════════════════════════════════════ -->
< div class = "page" id = "tools-page" >
< div class = "tools-page-container" >
< div class = "tools-page-header" >
< div class = "tools-page-header-left" >
< h2 class = "tools-page-title" >
< svg width = "22" height = "22" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" / >
< / svg >
Tools & Operations
< / h2 >
< p class = "tools-page-subtitle" > Database management, library scanning, metadata, backups< / p >
< / div >
< / div >
<!-- ── Library Maintenance (hero section) ── -->
< div class = "tools-maintenance-hero" >
< div class = "tools-maintenance-header" >
< div class = "tools-maintenance-header-left" >
< img src = "/static/whisoul.png" alt = "" class = "tools-maintenance-logo" / >
< div >
< h3 class = "tools-maintenance-title" > Library Maintenance< / h3 >
< p class = "tools-maintenance-subtitle" > Automated scanning, detection, and repair of library issues< / p >
< / div >
< / div >
< label class = "repair-master-toggle" >
< input type = "checkbox" id = "repair-master-toggle" onchange = "toggleRepairMaster()" >
< span class = "repair-toggle-slider" > < / span >
< span class = "repair-toggle-label" id = "repair-master-label" > Enabled< / span >
< / label >
< / div >
< div class = "repair-tabs" >
< button class = "repair-tab active" data-tab = "jobs" onclick = "switchRepairTab('jobs')" > Jobs< / button >
< button class = "repair-tab" data-tab = "findings" onclick = "switchRepairTab('findings')" >
Findings < span class = "repair-tab-badge" id = "repair-findings-tab-badge" style = "display:none" > 0< / span >
< / button >
< button class = "repair-tab" data-tab = "history" onclick = "switchRepairTab('history')" > History< / button >
< / div >
< div class = "repair-tab-content" id = "repair-tab-jobs" >
< div class = "repair-jobs-list" id = "repair-jobs-list" >
< div class = "repair-loading" > Loading jobs...< / div >
< / div >
< / div >
< div class = "repair-tab-content" id = "repair-tab-findings" style = "display:none;" >
< div class = "repair-findings-dashboard" id = "repair-findings-dashboard" > < / div >
< div class = "repair-findings-toolbar" >
< div class = "repair-findings-filters" >
< select id = "repair-findings-job-filter" onchange = "_repairFindingsPage=0;loadRepairFindings()" >
< option value = "" > All Jobs< / option >
< / select >
< select id = "repair-findings-severity-filter" onchange = "_repairFindingsPage=0;loadRepairFindings()" >
< option value = "" > All Severity< / option >
< option value = "info" > Info< / option >
< option value = "warning" > Warning< / option >
< / select >
< select id = "repair-findings-status-filter" onchange = "_repairFindingsPage=0;loadRepairFindings()" >
< option value = "pending" > Pending< / option >
< option value = "" > All Status< / option >
< option value = "resolved" > Resolved< / option >
< option value = "dismissed" > Dismissed< / option >
< / select >
< / div >
< label class = "repair-select-all" title = "Select all on this page" >
< input type = "checkbox" id = "repair-select-all-cb" onchange = "toggleSelectAllFindings(this.checked)" >
< span > Select All< / span >
< / label >
< div class = "repair-findings-bulk" id = "repair-findings-bulk" style = "display:none;" >
< span class = "repair-bulk-count" id = "repair-bulk-count" > < / span >
< button class = "repair-bulk-btn fix" onclick = "bulkFixFindings()" > Fix Selected< / button >
< button class = "repair-bulk-btn" onclick = "bulkRepairAction('dismiss')" > Dismiss Selected< / button >
< button class = "repair-bulk-btn fix-all" id = "repair-fix-all-btn" style = "display:none;" onclick = "fixAllMatchingFindings()" > Fix All< / button >
< / div >
< button class = "repair-clear-btn" onclick = "clearRepairFindings()" title = "Clear findings matching current filters" > Clear Findings< / button >
< / div >
< div class = "repair-findings-list" id = "repair-findings-list" >
< div class = "repair-loading" > Loading findings...< / div >
< / div >
< div class = "repair-findings-pagination" id = "repair-findings-pagination" > < / div >
< / div >
< div class = "repair-tab-content" id = "repair-tab-history" style = "display:none;" >
< div class = "repair-history-list" id = "repair-history-list" >
< div class = "repair-loading" > Loading history...< / div >
< / div >
< / div >
< / div >
<!-- ── Database & Scanning ── -->
< div class = "tools-section" >
< h3 class = "tools-section-title" > Database & Scanning< / h3 >
< div class = "tools-grid" >
< div class = "tool-card" id = "db-updater-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Database Updater< / h4 >
< button class = "tool-help-button" data-tool = "db-updater"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Last Full Refresh: < span id = "db-last-refresh" > Never< / span > < / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Artists:< / span >
< span class = "stat-item-value" id = "db-stat-artists" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Albums:< / span >
< span class = "stat-item-value" id = "db-stat-albums" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Tracks:< / span >
< span class = "stat-item-value" id = "db-stat-tracks" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Size:< / span >
< span class = "stat-item-value" id = "db-stat-size" > 0.0 MB< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< select id = "db-refresh-type" >
< option value = "incremental" > Incremental Update< / option >
< option value = "full" > Full Refresh< / option >
< / select >
< button id = "db-update-button" > Update Database< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "db-phase-label" > Idle< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "db-progress-bar" style = "width: 0%;" > < / div >
< / div >
< p class = "progress-details-label" id = "db-progress-label" > 0 / 0 artists (0.0%)< / p >
< / div >
< / div >
< div class = "tool-card" id = "metadata-updater-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Metadata Updater< / h4 >
< button class = "tool-help-button" data-tool = "metadata-updater"
title="Learn more about this tool">?< / button >
< / div >
< p class = "metadata-updater-description tool-card-info" > Updates artist photos, genres,
and album art from Spotify.< / p >
< div class = "tool-card-controls" >
< select id = "metadata-refresh-interval" >
< option value = "180" > 6 months< / option >
< option value = "90" > 3 months< / option >
< option value = "30" selected > 1 month< / option >
< option value = "14" > 2 weeks< / option >
< option value = "7" > 1 week< / option >
< option value = "0" > Full refresh< / option >
< / select >
< button id = "metadata-update-button" > Begin Update< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "metadata-phase-label" > Current Artist: Not
running< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "metadata-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "metadata-progress-label" > 0 / 0 artists (0.0%)
< / p >
< / div >
< / div >
< div class = "tool-card" id = "quality-scanner-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Quality Scanner< / h4 >
< button class = "tool-help-button" data-tool = "quality-scanner"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Scan library for tracks below quality preferences< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Processed:< / span >
< span class = "stat-item-value" id = "quality-stat-processed" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Quality Met:< / span >
< span class = "stat-item-value" id = "quality-stat-met" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Low Quality:< / span >
< span class = "stat-item-value" id = "quality-stat-low" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Matched:< / span >
< span class = "stat-item-value" id = "quality-stat-matched" > 0< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< select id = "quality-scan-scope" >
< option value = "watchlist" > Watchlist Artists Only< / option >
< option value = "all" > All Library Tracks< / option >
< / select >
< button id = "quality-scan-button" > Scan Library< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "quality-phase-label" > Ready to scan< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "quality-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "quality-progress-label" > 0 / 0 tracks scanned
(0.0%)< / p >
< / div >
< / div >
< div class = "tool-card" id = "duplicate-cleaner-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Duplicate Cleaner< / h4 >
< button class = "tool-help-button" data-tool = "duplicate-cleaner"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Detect and remove duplicate tracks in Transfer folder< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Files Scanned:< / span >
< span class = "stat-item-value" id = "duplicate-stat-scanned" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Duplicates Found:< / span >
< span class = "stat-item-value" id = "duplicate-stat-found" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Deleted:< / span >
< span class = "stat-item-value" id = "duplicate-stat-deleted" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Space Freed:< / span >
< span class = "stat-item-value" id = "duplicate-stat-space" > 0 MB< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "duplicate-clean-button" > Clean Duplicates< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "duplicate-phase-label" > Ready to scan< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "duplicate-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "duplicate-progress-label" > 0 files scanned
(0.0%)< / p >
< / div >
< / div >
<!-- media - scan moved here from below for grouping -->
< div class = "tool-card" id = "media-scan-card" style = "display: none;" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Media Server Scan< / h4 >
< button class = "tool-help-button" data-tool = "media-scan"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Manually trigger Plex media library scan for music< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Last Scan:< / span >
< span class = "stat-item-value" id = "media-scan-last-time" > Never< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Status:< / span >
< span class = "stat-item-value" id = "media-scan-status" > Idle< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "media-scan-button" class = "media-scan-btn" >
< span class = "scan-icon" > 📡 < / span >
< span class = "scan-text" > Scan Library< / span >
< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "media-scan-phase-label" > Ready to scan< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "media-scan-progress-bar" style = "width: 0%;" > < / div >
< / div >
< p class = "progress-details-label" id = "media-scan-progress-label" > Waiting for scan request< / p >
< / div >
< / div >
< / div > < / div >
<!-- ── Metadata & Cache ── -->
< div class = "tools-section" >
< h3 class = "tools-section-title" > Metadata & Cache< / h3 >
< div class = "tools-grid" >
< div class = "tool-card" id = "discovery-pool-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Discovery Pool< / h4 >
< / div >
< p class = "tool-card-info" > View and fix matched/failed discovery results across all mirrored playlists< / p >
< div class = "tool-card-stats" id = "discovery-pool-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Matched:< / span >
< span class = "stat-item-value" id = "discovery-pool-matched-count" > — < / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Failed:< / span >
< span class = "stat-item-value" id = "discovery-pool-failed-count" style = "background-color: rgba(239, 68, 68, 0.15); color: #ef4444;" > — < / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button onclick = "openDiscoveryPoolModal()" > Open Discovery Pool< / button >
< / div >
< / div >
< div class = "tool-card" id = "retag-tool-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Retag Tool< / h4 >
< button class = "tool-help-button" data-tool = "retag-tool"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Fix metadata on previously downloaded albums & singles< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Groups:< / span >
< span class = "stat-item-value" id = "retag-stat-groups" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Tracks:< / span >
< span class = "stat-item-value" id = "retag-stat-tracks" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Artists:< / span >
< span class = "stat-item-value" id = "retag-stat-artists" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Status:< / span >
< span class = "stat-item-value" id = "retag-stat-status" > Idle< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "retag-open-button" > Open Retag Tool< / button >
< / div >
< div class = "tool-card-progress-section" >
< p class = "progress-phase-label" id = "retag-phase-label" > Ready< / p >
< div class = "progress-bar-container" >
< div class = "progress-bar-fill" id = "retag-progress-bar" style = "width: 0%;" >
< / div >
< / div >
< p class = "progress-details-label" id = "retag-progress-label" > 0 / 0 tracks (0.0%)< / p >
< / div >
< / div >
< / div > < / div >
<!-- ── Management ── -->
< div class = "tools-section" >
< h3 class = "tools-section-title" > Management< / h3 >
< div class = "tools-grid" >
< div class = "tool-card" id = "backup-manager-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Backup Manager< / h4 >
< button class = "tool-help-button" data-tool = "backup-manager"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Create, download, restore and manage database backups< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Last Backup:< / span >
< span class = "stat-item-value" id = "backup-stat-last" > Never< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Backups:< / span >
< span class = "stat-item-value" id = "backup-stat-count" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Latest Size:< / span >
< span class = "stat-item-value" id = "backup-stat-latest-size" > —< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > DB Size:< / span >
< span class = "stat-item-value" id = "backup-stat-db-size" > —< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "backup-now-button" > Backup Now< / button >
< / div >
< div id = "backup-list-container" class = "backup-list-container" > < / div >
< / div >
<!-- Metadata Cache Tool Card -->
< div class = "tool-card" id = "metadata-cache-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Metadata Cache< / h4 >
< button class = "tool-help-button" data-tool = "metadata-cache"
title="Learn more about this tool">?< / button >
< / div >
< p class = "tool-card-info" > Cached API responses from Spotify & iTunes< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Artists:< / span >
< span class = "stat-item-value" id = "mcache-stat-artists" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Albums:< / span >
< span class = "stat-item-value" id = "mcache-stat-albums" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Tracks:< / span >
< span class = "stat-item-value" id = "mcache-stat-tracks" > 0< / span >
< / div >
< div class = "stat-item" >
< span class = "stat-item-label" > Hits:< / span >
< span class = "stat-item-value" id = "mcache-stat-hits" > 0< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button id = "mcache-browse-button" onclick = "openMetadataCacheModal()" > Browse Cache< / button >
< button onclick = "openCacheHealthModal()" class = "tool-card-btn-secondary" > Cache Health< / button >
< / div >
< / div >
<!-- Download Blacklist Card -->
< div class = "tool-card" id = "blacklist-card" >
< div class = "tool-card-header" >
< h4 class = "tool-card-title" > Download Blacklist< / h4 >
< / div >
< p class = "tool-card-info" > Blocked sources that won't be used for future downloads< / p >
< div class = "tool-card-stats" >
< div class = "stat-item" >
< span class = "stat-item-label" > Blocked:< / span >
< span class = "stat-item-value" id = "blacklist-count" > 0< / span >
< / div >
< / div >
< div class = "tool-card-controls" >
< button onclick = "openBlacklistModal()" > View Blacklist< / button >
< / div >
< / div >
< / div > < / div >
< / div >
< / div >
<!-- ═══════════════════════════════════════════════════════════════════
WATCHLIST PAGE
═══════════════════════════════════════════════════════════════════ -->
@ -7497,89 +7621,6 @@
< / div >
< / div >
<!-- Library Maintenance Modal -->
< div class = "repair-modal-overlay" id = "repair-modal" style = "display:none;" onclick = "if(event.target===this)closeRepairModal()" >
< div class = "repair-modal" >
< div class = "repair-modal-header" >
< div class = "repair-modal-header-left" >
< img src = "/static/whisoul.png" alt = "" class = "repair-modal-logo" / >
< div class = "repair-modal-header-text" >
< h2 class = "repair-modal-title" > Library Maintenance< / h2 >
< p class = "repair-modal-subtitle" > Scan, detect, and fix issues in your music library< / p >
< / div >
< / div >
< div class = "repair-modal-header-actions" >
< label class = "repair-master-toggle" >
< input type = "checkbox" id = "repair-master-toggle" onchange = "toggleRepairMaster()" >
< span class = "repair-toggle-slider" > < / span >
< span class = "repair-toggle-label" id = "repair-master-label" > Enabled< / span >
< / label >
< button class = "repair-modal-close" onclick = "closeRepairModal()" > × < / button >
< / div >
< / div >
< div class = "repair-tabs" >
< button class = "repair-tab active" data-tab = "jobs" onclick = "switchRepairTab('jobs')" > Jobs< / button >
< button class = "repair-tab" data-tab = "findings" onclick = "switchRepairTab('findings')" >
Findings < span class = "repair-tab-badge" id = "repair-findings-tab-badge" style = "display:none" > 0< / span >
< / button >
< button class = "repair-tab" data-tab = "history" onclick = "switchRepairTab('history')" > History< / button >
< / div >
< div class = "repair-tab-content" id = "repair-tab-jobs" >
< div class = "repair-jobs-list" id = "repair-jobs-list" >
< div class = "repair-loading" > Loading jobs...< / div >
< / div >
< / div >
< div class = "repair-tab-content" id = "repair-tab-findings" style = "display:none;" >
<!-- Summary dashboard -->
< div class = "repair-findings-dashboard" id = "repair-findings-dashboard" > < / div >
<!-- Toolbar: filters + bulk actions -->
< div class = "repair-findings-toolbar" >
< div class = "repair-findings-filters" >
< select id = "repair-findings-job-filter" onchange = "_repairFindingsPage=0;loadRepairFindings()" >
< option value = "" > All Jobs< / option >
< / select >
< select id = "repair-findings-severity-filter" onchange = "_repairFindingsPage=0;loadRepairFindings()" >
< option value = "" > All Severity< / option >
< option value = "info" > Info< / option >
< option value = "warning" > Warning< / option >
< / select >
< select id = "repair-findings-status-filter" onchange = "_repairFindingsPage=0;loadRepairFindings()" >
< option value = "pending" > Pending< / option >
< option value = "" > All Status< / option >
< option value = "resolved" > Resolved< / option >
< option value = "dismissed" > Dismissed< / option >
< / select >
< / div >
< label class = "repair-select-all" title = "Select all on this page" >
< input type = "checkbox" id = "repair-select-all-cb" onchange = "toggleSelectAllFindings(this.checked)" >
< span > Select All< / span >
< / label >
< div class = "repair-findings-bulk" id = "repair-findings-bulk" style = "display:none;" >
< span class = "repair-bulk-count" id = "repair-bulk-count" > < / span >
< button class = "repair-bulk-btn fix" onclick = "bulkFixFindings()" > Fix Selected< / button >
< button class = "repair-bulk-btn" onclick = "bulkRepairAction('dismiss')" > Dismiss Selected< / button >
< button class = "repair-bulk-btn fix-all" id = "repair-fix-all-btn" style = "display:none;" onclick = "fixAllMatchingFindings()" > Fix All< / button >
< / div >
< button class = "repair-clear-btn" onclick = "clearRepairFindings()" title = "Clear findings matching current filters" > Clear Findings< / button >
< / div >
< div class = "repair-findings-list" id = "repair-findings-list" >
< div class = "repair-loading" > Loading findings...< / div >
< / div >
< div class = "repair-findings-pagination" id = "repair-findings-pagination" > < / div >
< / div >
< div class = "repair-tab-content" id = "repair-tab-history" style = "display:none;" >
< div class = "repair-history-list" id = "repair-history-list" >
< div class = "repair-loading" > Loading history...< / div >
< / div >
< / div >
< / div >
< / div >
<!-- Right Sidebar Download Indicator (Global - outside all containers) -->
< div class = "discover-download-sidebar" id = "discover-download-sidebar" >
< div class = "discover-download-sidebar-header" >