Add Tools sidebar page with grouped layout and Library Maintenance hero
Dashboard Tools & Operations section replaced with a compact link card. All 10 tool cards moved to a dedicated Tools page in the sidebar, grouped into three sections: Database & Scanning, Metadata & Cache, Management. Library Maintenance promoted to hero position at the top of the page with accent top bar, logo, enable toggle, and tabbed content (Jobs, Findings, History) — no longer buried in a modal. openRepairModal() now navigates to the Tools page. Repair modal HTML removed. Tool initialization extracted from loadDashboardData() into a dedicated initializeToolsPage() with idempotent event listener wiring. Container sizing updated to use margin: 20px (matching Dashboard/Stats) instead of max-width: 1400px for consistent full-width appearance across all pages.
This commit is contained in:
parent
cf18590794
commit
60d737f7ab
4 changed files with 721 additions and 467 deletions
855
webui/index.html
855
webui/index.html
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -3602,6 +3602,7 @@ const WHATS_NEW = {
|
|||
'2.2': [
|
||||
// --- April 15, 2026 ---
|
||||
{ date: 'April 15, 2026' },
|
||||
{ title: 'Tools Page', desc: 'All tool cards (Database Updater, Quality Scanner, Duplicate Cleaner, Retag, Backups, Cache, etc.) and Library Maintenance moved from the Dashboard to a dedicated Tools page in the sidebar. Dashboard shows a quick-link card', page: 'tools' },
|
||||
{ title: 'Watchlist & Wishlist Sidebar Pages', desc: 'Watchlist and Wishlist promoted from modals to full sidebar pages. All features preserved — artist grid, scan controls, batch operations, live activity, countdown timers, category cards with mosaic backgrounds. Header buttons now navigate to the pages', page: 'watchlist' },
|
||||
{ title: 'Picard-Style MusicBrainz Album Consistency', desc: 'Recording MBIDs now pulled from the matched release tracklist instead of independent searches. Batch-level artist name used for stable cache keys. Post-batch consistency pass rewrites album-level tags on all files to guarantee identical MusicBrainz IDs — prevents Navidrome album splits' },
|
||||
{ title: 'Fix Spotify API Leaking When Deezer/iTunes is Primary', desc: 'Spotify was being called for watchlist album scanning, similar artist discovery, repair jobs, and the Artists page search even when another source was set as primary. All data-fetching now respects the configured primary source. Spotify playlist sync is unaffected' },
|
||||
|
|
|
|||
|
|
@ -3048,6 +3048,9 @@ async function loadPageData(pageId) {
|
|||
// Load comparisons
|
||||
loadHydrabaseComparisons();
|
||||
break;
|
||||
case 'tools':
|
||||
await initializeToolsPage();
|
||||
break;
|
||||
case 'watchlist':
|
||||
await initializeWatchlistPage();
|
||||
break;
|
||||
|
|
@ -25088,80 +25091,81 @@ function resetWishlistModalToIdleState() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadDashboardData() {
|
||||
// Attach event listeners for the DB updater tool
|
||||
let toolsPageState = { isInitialized: false };
|
||||
|
||||
async function initializeToolsPage() {
|
||||
// Attach event listeners for tool buttons (idempotent — getElementById returns null if already wired)
|
||||
const updateButton = document.getElementById('db-update-button');
|
||||
if (updateButton) {
|
||||
if (updateButton && !updateButton._toolsWired) {
|
||||
updateButton.addEventListener('click', handleDbUpdateButtonClick);
|
||||
updateButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Attach event listeners for the metadata updater tool
|
||||
const metadataButton = document.getElementById('metadata-update-button');
|
||||
if (metadataButton) {
|
||||
if (metadataButton && !metadataButton._toolsWired) {
|
||||
metadataButton.addEventListener('click', handleMetadataUpdateButtonClick);
|
||||
metadataButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Check active media server and hide metadata updater if not Plex
|
||||
await checkAndHideMetadataUpdaterForNonPlex();
|
||||
|
||||
// Check for ongoing metadata update and restore state
|
||||
await checkAndRestoreMetadataUpdateState();
|
||||
|
||||
// Attach event listener for the quality scanner tool
|
||||
const qualityScanButton = document.getElementById('quality-scan-button');
|
||||
if (qualityScanButton) {
|
||||
if (qualityScanButton && !qualityScanButton._toolsWired) {
|
||||
qualityScanButton.addEventListener('click', handleQualityScanButtonClick);
|
||||
qualityScanButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Attach event listener for the duplicate cleaner tool
|
||||
const duplicateCleanButton = document.getElementById('duplicate-clean-button');
|
||||
if (duplicateCleanButton) {
|
||||
if (duplicateCleanButton && !duplicateCleanButton._toolsWired) {
|
||||
duplicateCleanButton.addEventListener('click', handleDuplicateCleanButtonClick);
|
||||
duplicateCleanButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Attach event listener for the retag tool
|
||||
const retagOpenButton = document.getElementById('retag-open-button');
|
||||
if (retagOpenButton) {
|
||||
if (retagOpenButton && !retagOpenButton._toolsWired) {
|
||||
retagOpenButton.addEventListener('click', openRetagModal);
|
||||
retagOpenButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Attach event listener for the media scan tool
|
||||
const mediaScanButton = document.getElementById('media-scan-button');
|
||||
if (mediaScanButton) {
|
||||
if (mediaScanButton && !mediaScanButton._toolsWired) {
|
||||
mediaScanButton.addEventListener('click', handleMediaScanButtonClick);
|
||||
mediaScanButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Check active media server and show media scan tool only for Plex
|
||||
await checkAndShowMediaScanForPlex();
|
||||
|
||||
// Attach event listener for the backup manager
|
||||
const backupNowButton = document.getElementById('backup-now-button');
|
||||
if (backupNowButton) backupNowButton.addEventListener('click', handleBackupNowClick);
|
||||
if (backupNowButton && !backupNowButton._toolsWired) {
|
||||
backupNowButton.addEventListener('click', handleBackupNowClick);
|
||||
backupNowButton._toolsWired = true;
|
||||
}
|
||||
|
||||
// Tool-specific init
|
||||
await checkAndHideMetadataUpdaterForNonPlex();
|
||||
await checkAndRestoreMetadataUpdateState();
|
||||
await checkAndShowMediaScanForPlex();
|
||||
loadBackupList();
|
||||
|
||||
// Attach event listeners for tool help buttons
|
||||
initializeToolHelpButtons();
|
||||
|
||||
// Initial load of retag stats
|
||||
loadRetagStats();
|
||||
|
||||
// Check for ongoing retag operation
|
||||
checkRetagStatus();
|
||||
|
||||
// Initial load of stats
|
||||
await fetchAndUpdateDbStats();
|
||||
loadDiscoveryPoolStats();
|
||||
loadMetadataCacheStats();
|
||||
|
||||
// Start periodic refresh of stats (every 10 seconds)
|
||||
stopDbStatsPolling(); // Ensure no duplicates
|
||||
// Start polling (cleared when navigating away via loadPageData preamble)
|
||||
stopDbStatsPolling();
|
||||
dbStatsInterval = setInterval(fetchAndUpdateDbStats, 10000);
|
||||
|
||||
// Initial load of discovery pool stats for the tool card
|
||||
loadDiscoveryPoolStats();
|
||||
// Check for ongoing operations
|
||||
await checkAndUpdateDbProgress();
|
||||
await checkAndUpdateQualityScanProgress();
|
||||
await checkAndUpdateDuplicateCleanProgress();
|
||||
|
||||
// Initial load of metadata cache stats + periodic refresh
|
||||
loadMetadataCacheStats();
|
||||
setInterval(loadMetadataCacheStats, 15000);
|
||||
// Initialize library maintenance section
|
||||
updateRepairStatus();
|
||||
switchRepairTab('jobs');
|
||||
|
||||
toolsPageState.isInitialized = true;
|
||||
}
|
||||
|
||||
async function loadDashboardData() {
|
||||
// Initial load of wishlist count
|
||||
await updateWishlistCount();
|
||||
|
||||
|
|
@ -25186,15 +25190,6 @@ async function loadDashboardData() {
|
|||
// Start periodic toast checking (every 3 seconds)
|
||||
setInterval(checkForActivityToasts, 3000);
|
||||
|
||||
// Also check the status of any ongoing update when the page loads
|
||||
await checkAndUpdateDbProgress();
|
||||
|
||||
// Check for any ongoing quality scanner when the page loads
|
||||
await checkAndUpdateQualityScanProgress();
|
||||
|
||||
// Check for any ongoing duplicate cleaner when the page loads
|
||||
await checkAndUpdateDuplicateCleanProgress();
|
||||
|
||||
// Check for any active download processes that need rehydration
|
||||
await checkForActiveProcesses();
|
||||
|
||||
|
|
@ -63191,10 +63186,12 @@ let _repairJobsCache = {}; // Cache job data for help modal
|
|||
* Open the Library Maintenance modal
|
||||
*/
|
||||
async function openRepairModal() {
|
||||
const modal = document.getElementById('repair-modal');
|
||||
if (!modal) return;
|
||||
modal.style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
navigateToPage('tools');
|
||||
// Scroll to maintenance section
|
||||
setTimeout(() => {
|
||||
const section = document.querySelector('.tools-maintenance-section');
|
||||
if (section) section.scrollIntoView({ behavior: 'smooth' });
|
||||
}, 100);
|
||||
_repairCurrentTab = 'jobs';
|
||||
switchRepairTab('jobs');
|
||||
// Load master toggle state
|
||||
|
|
@ -63213,10 +63210,7 @@ async function openRepairModal() {
|
|||
}
|
||||
|
||||
function closeRepairModal() {
|
||||
const modal = document.getElementById('repair-modal');
|
||||
if (!modal) return;
|
||||
modal.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
// No-op — repair content now lives on the tools page, no modal to close
|
||||
}
|
||||
|
||||
async function toggleRepairMaster() {
|
||||
|
|
|
|||
|
|
@ -55797,14 +55797,232 @@ body.reduce-effects *::after {
|
|||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
DASHBOARD TOOLS LINK CARD
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.dashboard-tools-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.dashboard-tools-link:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: rgba(var(--accent-rgb), 0.15);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.dashboard-tools-link-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboard-tools-link-title {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
TOOLS PAGE
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.tools-page-container {
|
||||
padding: 28px 24px 30px;
|
||||
margin: 20px;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(20, 20, 20, 0.55) 0%,
|
||||
rgba(12, 12, 12, 0.62) 100%);
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||
0 4px 16px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.tools-page-header {
|
||||
padding: 20px 24px 18px;
|
||||
margin: -28px -24px 24px -24px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(var(--accent-rgb), 0.08) 0%,
|
||||
rgba(var(--accent-rgb), 0.03) 40%,
|
||||
transparent 100%);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-top-left-radius: 24px;
|
||||
border-top-right-radius: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tools-page-header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(var(--accent-rgb), 0.2) 20%,
|
||||
rgba(var(--accent-rgb), 0.35) 50%,
|
||||
rgba(var(--accent-rgb), 0.2) 80%,
|
||||
transparent 100%);
|
||||
}
|
||||
|
||||
.tools-page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
letter-spacing: -0.5px;
|
||||
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.tools-page-subtitle {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin: 6px 0 0 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── Library Maintenance hero section ── */
|
||||
.tools-maintenance-hero {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(20, 20, 20, 0.95) 0%,
|
||||
rgba(12, 12, 12, 0.98) 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 16px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 28px;
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tools-maintenance-hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(var(--accent-rgb), 0.5),
|
||||
rgba(var(--accent-rgb), 0.8),
|
||||
rgba(var(--accent-rgb), 0.5),
|
||||
transparent);
|
||||
box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.3);
|
||||
}
|
||||
|
||||
.tools-maintenance-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tools-maintenance-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.tools-maintenance-logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.tools-maintenance-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin: 0 0 2px 0;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.tools-maintenance-subtitle {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Repair tabs on the tools page */
|
||||
.tools-maintenance-hero .repair-tabs {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tools-maintenance-hero .repair-tab-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* ── Tool card sections ── */
|
||||
.tools-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tools-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.2px;
|
||||
margin: 0 0 12px 4px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tools-page-container {
|
||||
padding: 16px;
|
||||
margin: 10px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.tools-page-header {
|
||||
padding: 16px;
|
||||
margin: -16px -16px 16px -16px;
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
}
|
||||
|
||||
.tools-page-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.tools-maintenance-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
WATCHLIST PAGE
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.watchlist-page-container {
|
||||
padding: 28px 24px 30px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
margin: 20px;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(20, 20, 20, 0.55) 0%,
|
||||
rgba(12, 12, 12, 0.62) 100%);
|
||||
|
|
@ -56216,8 +56434,7 @@ body.reduce-effects *::after {
|
|||
|
||||
.wishlist-page-container {
|
||||
padding: 28px 24px 30px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
margin: 20px;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(20, 20, 20, 0.55) 0%,
|
||||
rgba(12, 12, 12, 0.62) 100%);
|
||||
|
|
@ -56534,6 +56751,7 @@ body.reduce-effects *::after {
|
|||
.watchlist-page-container,
|
||||
.wishlist-page-container {
|
||||
padding: 16px;
|
||||
margin: 10px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue