pull/64/head
Broque Thomas 6 months ago
parent 97d3af5ec6
commit abbb93f053

@ -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…
Cancel
Save