async function triggerLibraryScan(): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/libraries/scan", { method: "POST", headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { if ((window as any).showToast?.success) { (window as any).showToast.success("Library scan started"); } } else { const error = await response.json(); if ((window as any).showToast?.error) { (window as any).showToast.error(error.error || "Failed to start scan"); } } } catch (error) { console.error("Scan error:", error); if ((window as any).showToast?.error) { (window as any).showToast.error("Failed to start library scan"); } } } async function triggerQuickScan(): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/libraries/quick-scan", { method: "POST", headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { if ((window as any).showToast?.success) { (window as any).showToast.success("Quick scan started"); } } else { const error = await response.json(); if ((window as any).showToast?.error) { (window as any).showToast.error( error.error || "Failed to start quick scan", ); } } } catch (error) { console.error("Quick scan error:", error); if ((window as any).showToast?.error) { (window as any).showToast.error("Failed to start quick scan"); } } } async function loadSystemStats(): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/admin/stats", { headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { const stats = await response.json(); renderSystemStats(stats); } } catch (error) { console.error("Failed to load stats:", error); } } function renderSystemStats(stats: Record): void { const container = document.getElementById("system-stats"); if (!container) return; container.innerHTML = `

${stats.total_books || 0}

Total Books

${stats.total_users || 0}

Users

${stats.total_devices || 0}

Devices

${stats.total_libraries || 0}

Libraries

`; } async function scanAllLibraries(): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const libsResp = await fetch("/api/libraries", { headers: { Authorization: `Bearer ${token}` }, }); if (!libsResp.ok) { throw new Error("Failed to get libraries"); } const libsData = await libsResp.json(); if (!libsData.data || libsData.data.length === 0) { if ((window as any).showToast?.error) { (window as any).showToast.error( "No libraries found. Please create a library first.", ); } return; } const libraries = libsData.data; const jobs: string[] = []; const libraryNames: Record = {}; for (const lib of libraries) { const scanResp = await fetch(`/api/libraries/${lib.id}/scan`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ force: true }), }); if (scanResp.ok) { const result = await scanResp.json(); jobs.push(result.job_id); libraryNames[result.job_id] = lib.name; } else { console.error(`Failed to scan library: ${lib.name}`); } } if (jobs.length === 0) { if ((window as any).showToast?.error) { (window as any).showToast.error("Failed to start scan for any library"); } return; } showScanProgress(jobs, libraryNames); } catch (error) { console.error("Scan error:", error); if ((window as any).showToast?.error) { (window as any).showToast.error( "Failed to start scan: " + (error as Error).message, ); } } } function showScanProgress( jobIds: string[], libraryNames: Record, ): void { const container = document.getElementById( "scan-progress-container", ) as HTMLElement; const list = document.getElementById("library-progress-list") as HTMLElement; if (!container || !list) return; container.classList.remove("hidden"); container.classList.remove("opacity-0", "-translate-y-2.5"); list.innerHTML = jobIds .map( (jobId) => `
${libraryNames[jobId]} Pending...
`, ) .join(""); pollScanProgress(jobIds, libraryNames); } function pollScanProgress( jobIds: string[], _libraryNames: Record, ): void { const token = localStorage.getItem("token"); const startTime = Date.now(); scanPollInterval = setInterval(async () => { let allComplete = true; let totalProgress = 0; let totalFiles = 0; let totalNewItems = 0; let totalErrors = 0; for (const jobId of jobIds) { try { const resp = await fetch(`/api/scanner/status/${jobId}`, { headers: { Authorization: `Bearer ${token}` }, }); if (resp.ok) { const status = await resp.json(); updateLibraryProgress(jobId, status); totalProgress += status.progress || 0; totalFiles += status.files_scanned || 0; totalNewItems += status.new_items || 0; totalErrors += status.errors || 0; if (status.status !== "completed" && status.status !== "failed") { allComplete = false; } } } catch (error) { console.error(`Failed to poll job ${jobId}:`, error); } } const overallProgress = Math.round(totalProgress / jobIds.length); const progressBar = document.getElementById( "scan-progress-bar", ) as HTMLElement; const progressText = document.getElementById( "scan-progress-text", ) as HTMLElement; const statusText = document.getElementById("scan-status") as HTMLElement; if (progressBar) progressBar.style.width = overallProgress + "%"; if (progressText) progressText.textContent = overallProgress + "%"; const elapsed = Math.round((Date.now() - startTime) / 1000); if (!allComplete && statusText) { statusText.textContent = `Scanning... ${elapsed}s elapsed • ${totalFiles} files processed`; } if (allComplete) { clearInterval(scanPollInterval); showScanResults( jobIds.length, totalFiles, totalNewItems, totalErrors, elapsed, ); } }, 2000); } function updateLibraryProgress(jobId: string, status: any): void { const bar = document.getElementById(`bar-${jobId}`) as HTMLElement; const statusText = document.getElementById(`status-${jobId}`) as HTMLElement; if (bar) { bar.style.width = (status.progress || 0) + "%"; } if (statusText) { const statusMessages: Record = { pending: "Pending...", running: `Scanning... ${status.progress || 0}%`, completed: `✓ Complete (${status.new_items || 0} items)`, failed: `✗ Failed`, }; statusText.textContent = statusMessages[status.status] || status.status; } } function showScanResults( libCount: number, files: number, items: number, errors: number, elapsed: number, ): void { const resultsDiv = document.getElementById("scan-results") as HTMLElement; const contentDiv = document.getElementById( "scan-results-content", ) as HTMLElement; if (!resultsDiv || !contentDiv) return; contentDiv.innerHTML = `

• ${libCount} librar${libCount === 1 ? "y" : "ies"} scanned

• ${files} files processed

• ${items} new items added

${errors > 0 ? `

• ${errors} errors

` : ""}

Completed in ${elapsed} seconds

`; resultsDiv.classList.remove("hidden"); const statusText = document.getElementById("scan-status") as HTMLElement; if (statusText) statusText.textContent = "Scan complete!"; } function hideScanProgress(): void { const container = document.getElementById( "scan-progress-container", ) as HTMLElement; if (container) container.classList.add("hidden"); } async function loadWatchStatus(): Promise { const token = localStorage.getItem("token"); if (!token) return; try { const response = await fetch("/api/scanner/watch/status", { headers: { Authorization: `Bearer ${token}` }, }); if (response.ok) { const data = await response.json(); const countEl = document.getElementById("watch-count"); if (countEl) { countEl.textContent = data.total_watching?.toString() || "0"; } } } catch (error) { console.error("Failed to load watch status:", error); } } document.addEventListener("DOMContentLoaded", function () { loadWatchStatus(); connectScanWebSocket(); }); let scanPollInterval: ReturnType | undefined = undefined; let scanWs: WebSocket | null = null; function connectScanWebSocket(): void { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${protocol}//${window.location.host}/api/ws`; scanWs = new WebSocket(wsUrl); scanWs.onmessage = (event) => { const message = JSON.parse(event.data); switch (message.type) { case "scan_progress": updateScanProgress(message.data); break; case "scan_complete": showScanComplete(message.data); stopScanStatusPolling(); // Optional: stop HTTP polling break; case "scan_error": showScanError(message.data); break; } }; } function updateScanProgress(data: any): void { // Update progress bar const progressBar = document.getElementById( "scan-progress-bar", ) as HTMLElement; if (progressBar) { progressBar.style.width = `${data.progress * 100}%`; } // Update text const progressText = document.getElementById("scan-progress-text"); if (progressText) { progressText.textContent = `${data.files_scanned} files scanned (${data.new_items} new)`; } } function showScanComplete(data: any): void { console.log("Scan complete:", data); } function showScanError(data: any): void { console.error("Scan error:", data); } function stopScanStatusPolling(): void { if (scanPollInterval !== undefined) { clearInterval(scanPollInterval); scanPollInterval = undefined; } } (window as any).triggerLibraryScan = triggerLibraryScan; (window as any).triggerQuickScan = triggerQuickScan; (window as any).loadSystemStats = loadSystemStats; (window as any).scanAllLibraries = scanAllLibraries; (window as any).loadWatchStatus = loadWatchStatus; (window as any).hideScanProgress = hideScanProgress; (window as any).stopScanStatusPolling = stopScanStatusPolling;