scan tool
This commit is contained in:
parent
97d3af5ec6
commit
abbb93f053
3 changed files with 269 additions and 0 deletions
|
|
@ -384,6 +384,37 @@
|
|||
<p class="progress-details-label" id="duplicate-progress-label">0 files scanned (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>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -9467,6 +9467,44 @@ const TOOL_HELP_CONTENT = {
|
|||
<li><strong>Space Freed:</strong> Total disk space reclaimed</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
'media-scan': {
|
||||
title: 'Media Server Scan',
|
||||
content: `
|
||||
<h4>What does this tool do?</h4>
|
||||
<p>The Media Server Scan tool manually triggers a Plex media library scan to detect newly downloaded music files.</p>
|
||||
|
||||
<h4>When to use it?</h4>
|
||||
<ul>
|
||||
<li>After downloading new tracks to refresh your Plex library</li>
|
||||
<li>When new music isn't showing up in Plex</li>
|
||||
<li>To force an immediate library update instead of waiting for auto-scan</li>
|
||||
</ul>
|
||||
|
||||
<h4>What happens when you scan?</h4>
|
||||
<ol>
|
||||
<li><strong>Plex library scan:</strong> Plex scans your music folder for new/changed files</li>
|
||||
<li><strong>Automatic database update:</strong> After the scan completes, SoulSync automatically updates its internal database with new tracks</li>
|
||||
<li><strong>Library refreshed:</strong> New music appears in Plex and SoulSync within moments</li>
|
||||
</ol>
|
||||
|
||||
<h4>Plex only?</h4>
|
||||
<p>Yes! This tool only appears when Plex is your active media server because:</p>
|
||||
<ul>
|
||||
<li><strong>Jellyfin</strong> automatically detects new files instantly (real-time monitoring)</li>
|
||||
<li><strong>Navidrome</strong> automatically detects new files instantly (real-time monitoring)</li>
|
||||
<li><strong>Plex</strong> requires manual scans or has delayed auto-scanning</li>
|
||||
</ul>
|
||||
|
||||
<h4>Stats Explained</h4>
|
||||
<ul>
|
||||
<li><strong>Last Scan:</strong> Time of the most recent scan request</li>
|
||||
<li><strong>Status:</strong> Current scan state (Idle, Scanning, Error)</li>
|
||||
</ul>
|
||||
|
||||
<h4>Scan workflow</h4>
|
||||
<p>This tool replicates the same scan process that runs automatically after completing a download modal - ensuring your new tracks are immediately available in your library!</p>
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -9637,6 +9675,15 @@ async function loadDashboardData() {
|
|||
duplicateCleanButton.addEventListener('click', handleDuplicateCleanButtonClick);
|
||||
}
|
||||
|
||||
// Attach event listener for the media scan tool
|
||||
const mediaScanButton = document.getElementById('media-scan-button');
|
||||
if (mediaScanButton) {
|
||||
mediaScanButton.addEventListener('click', handleMediaScanButtonClick);
|
||||
}
|
||||
|
||||
// Check active media server and show media scan tool only for Plex
|
||||
await checkAndShowMediaScanForPlex();
|
||||
|
||||
// Attach event listeners for tool help buttons
|
||||
initializeToolHelpButtons();
|
||||
|
||||
|
|
@ -19456,6 +19503,159 @@ async function checkAndHideMetadataUpdaterForNonPlex() {
|
|||
}
|
||||
}
|
||||
|
||||
async function checkAndShowMediaScanForPlex() {
|
||||
/**
|
||||
* Show media scan tool only for Plex (Jellyfin/Navidrome auto-scan)
|
||||
*/
|
||||
try {
|
||||
const response = await fetch('/api/active-media-server');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const mediaScanCard = document.getElementById('media-scan-card');
|
||||
if (mediaScanCard) {
|
||||
// Show media scan tool only for Plex
|
||||
if (data.active_server === 'plex') {
|
||||
mediaScanCard.style.display = 'flex';
|
||||
console.log('Media scan tool shown: Plex is active server');
|
||||
} else {
|
||||
// Hide for Jellyfin/Navidrome (they auto-scan)
|
||||
mediaScanCard.style.display = 'none';
|
||||
console.log(`Media scan tool hidden: ${data.active_server} auto-scans`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not check active media server for media scan visibility:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMediaScanButtonClick() {
|
||||
/**
|
||||
* Trigger a manual Plex media library scan
|
||||
*/
|
||||
const button = document.getElementById('media-scan-button');
|
||||
const phaseLabel = document.getElementById('media-scan-phase-label');
|
||||
const progressBar = document.getElementById('media-scan-progress-bar');
|
||||
const progressLabel = document.getElementById('media-scan-progress-label');
|
||||
const statusValue = document.getElementById('media-scan-status');
|
||||
|
||||
if (!button) return;
|
||||
|
||||
try {
|
||||
// Disable button and update UI
|
||||
button.disabled = true;
|
||||
phaseLabel.textContent = 'Requesting scan...';
|
||||
progressBar.style.width = '30%';
|
||||
progressLabel.textContent = 'Sending scan request to Plex';
|
||||
statusValue.textContent = 'Scanning...';
|
||||
statusValue.style.color = '#1db954';
|
||||
|
||||
// Request scan
|
||||
const response = await fetch('/api/scan/request', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
reason: 'Manual scan triggered from dashboard',
|
||||
auto_database_update: true
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Update UI to show scan in progress
|
||||
phaseLabel.textContent = 'Scan in progress...';
|
||||
progressBar.style.width = '100%';
|
||||
|
||||
if (result.scan_info && result.scan_info.delay_seconds) {
|
||||
progressLabel.textContent = `Scan scheduled (${result.scan_info.delay_seconds}s delay)`;
|
||||
showToast(`📡 Media scan scheduled (${result.scan_info.delay_seconds}s delay)`, 'success', 5000);
|
||||
} else {
|
||||
progressLabel.textContent = 'Scan initiated successfully';
|
||||
showToast('📡 Media scan initiated successfully', 'success', 3000);
|
||||
}
|
||||
|
||||
// Show auto database update message
|
||||
if (result.auto_database_update) {
|
||||
showToast('🔄 Database will update automatically after scan', 'info', 3000);
|
||||
}
|
||||
|
||||
// Update last scan time
|
||||
const lastTimeEl = document.getElementById('media-scan-last-time');
|
||||
if (lastTimeEl) {
|
||||
const now = new Date();
|
||||
lastTimeEl.textContent = now.toLocaleTimeString();
|
||||
}
|
||||
|
||||
// Poll scan status for ~30 seconds
|
||||
let pollCount = 0;
|
||||
const pollInterval = setInterval(async () => {
|
||||
pollCount++;
|
||||
|
||||
if (pollCount > 15) { // Stop after 30 seconds (15 * 2s)
|
||||
clearInterval(pollInterval);
|
||||
// Reset UI
|
||||
button.disabled = false;
|
||||
phaseLabel.textContent = 'Scan completed';
|
||||
progressBar.style.width = '0%';
|
||||
progressLabel.textContent = 'Ready for next scan';
|
||||
statusValue.textContent = 'Idle';
|
||||
statusValue.style.color = '#b3b3b3';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statusResponse = await fetch('/api/scan/status');
|
||||
const statusData = await statusResponse.json();
|
||||
|
||||
if (statusData.success && statusData.status) {
|
||||
const status = statusData.status;
|
||||
|
||||
// Update status display
|
||||
if (status.is_scanning) {
|
||||
phaseLabel.textContent = 'Plex is scanning library...';
|
||||
progressLabel.textContent = status.progress_message || 'Scan in progress';
|
||||
} else {
|
||||
// Scan complete
|
||||
clearInterval(pollInterval);
|
||||
button.disabled = false;
|
||||
phaseLabel.textContent = 'Scan completed successfully';
|
||||
progressBar.style.width = '0%';
|
||||
progressLabel.textContent = 'Ready for next scan';
|
||||
statusValue.textContent = 'Idle';
|
||||
statusValue.style.color = '#b3b3b3';
|
||||
showToast('✅ Media scan completed', 'success', 3000);
|
||||
}
|
||||
}
|
||||
} catch (pollError) {
|
||||
console.debug('Scan status poll error:', pollError);
|
||||
}
|
||||
}, 2000); // Poll every 2 seconds
|
||||
|
||||
} else {
|
||||
// Error occurred
|
||||
showToast(`❌ Scan request failed: ${result.error}`, 'error', 5000);
|
||||
button.disabled = false;
|
||||
phaseLabel.textContent = 'Scan failed';
|
||||
progressBar.style.width = '0%';
|
||||
progressLabel.textContent = result.error || 'Unknown error';
|
||||
statusValue.textContent = 'Error';
|
||||
statusValue.style.color = '#f44336';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error requesting media scan:', error);
|
||||
showToast('❌ Failed to request media scan', 'error', 3000);
|
||||
button.disabled = false;
|
||||
phaseLabel.textContent = 'Error';
|
||||
progressBar.style.width = '0%';
|
||||
progressLabel.textContent = error.message;
|
||||
statusValue.textContent = 'Error';
|
||||
statusValue.style.color = '#f44336';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for ongoing metadata update and restore state on page load
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4136,6 +4136,44 @@ body {
|
|||
transform: none;
|
||||
}
|
||||
|
||||
/* Media Scan Button Styling */
|
||||
.media-scan-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2) !important;
|
||||
padding: 14px 24px !important;
|
||||
font-size: 15px !important;
|
||||
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.media-scan-btn:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #7c8ff0, #8a5ab8) !important;
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.media-scan-btn .scan-icon {
|
||||
font-size: 18px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.media-scan-btn:disabled .scan-icon {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.7; transform: scale(1.1); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.tool-card-progress-section {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #404040;
|
||||
|
|
|
|||
Loading…
Reference in a new issue