feat: integrate WebSocket for real-time scan progress in admin panel

- Add WebSocket connection for scan progress updates
- Display live progress bar and file count during scans
- Handle scan_complete and scan_error messages
- Store polling interval in module variable for cleanup
- Expose stopScanStatusPolling function for manual control

Replaces or supplements HTTP polling with push-based updates for
better UX and reduced server load.
This commit is contained in:
2026-03-05 17:13:23 -05:00
parent 40f303b004
commit 39a87ddabc
+56 -3
View File
@@ -213,8 +213,7 @@ function pollScanProgress(
): void {
const token = localStorage.getItem("token");
const startTime = Date.now();
const interval = setInterval(async () => {
scanPollInterval = setInterval(async () => {
let allComplete = true;
let totalProgress = 0;
let totalFiles = 0;
@@ -264,7 +263,7 @@ function pollScanProgress(
}
if (allComplete) {
clearInterval(interval);
clearInterval(scanPollInterval);
showScanResults(
jobIds.length,
totalFiles,
@@ -352,11 +351,65 @@ async function loadWatchStatus(): Promise<void> {
document.addEventListener("DOMContentLoaded", function () {
loadWatchStatus();
connectScanWebSocket();
});
let scanPollInterval: ReturnType<typeof setInterval> | 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;