diff --git a/TASKS-scanning-progress.md b/TASKS-scanning-progress.md index 82df616..663f9ba 100644 --- a/TASKS-scanning-progress.md +++ b/TASKS-scanning-progress.md @@ -109,49 +109,65 @@ function quickScan() { ### Part 1: Fix Scan Library Button +**NOTE - Major Changes to Original Plan:** +- **Switched from inline JavaScript to TypeScript** (follows PROJECT_GUIDELINES.md: "convert all JavaScript to TypeScript") +- **Uses existing `web/src/admin.ts` infrastructure** instead of adding new inline code +- **No custom CSS** - uses TailwindCSS transition classes for animation (follows "TailwindCSS classes only" rule) +- **Inline CSS with variables retained** - follows existing pattern in admin.templ for theme support + +**Rationale:** +- Project already has `web/src/admin.ts` with TypeScript scanning functions +- Inline JavaScript in templates makes code harder to maintain +- TypeScript provides better type safety and code organization +- TailwindCSS transitions are sufficient for UI animation + **Files to Modify:** -- `templates/admin.templ` +- `web/src/admin.ts` (extend TypeScript scanning functions) +- `templates/admin.templ` (add progress UI, include admin.js) **Implementation Steps:** -#### Step 1: Replace quickScan() Function +#### Step 1: Extend TypeScript in web/src/admin.ts -**Location:** `templates/admin.templ` lines 69-85 +**Location:** Add new functions after `loadSystemStats()` (around line 103) -**New Implementation:** -```javascript -async function quickScan() { +**Add these functions:** +```typescript +async function scanAllLibraries(): Promise { const token = localStorage.getItem('token'); - + if (!token) return; + try { // Step 1: Get all libraries 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) { - alert('No libraries found. Please create a library first.'); + if ((window as any).showToast?.error) { + (window as any).showToast.error('No libraries found. Please create a library first.'); + } return; } - + const libraries = libsData.data; - + // Step 2: Scan each library - const jobs = []; - const libraryNames = {}; - + 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}` } }); - + if (scanResp.ok) { const result = await scanResp.json(); jobs.push(result.job_id); @@ -160,30 +176,259 @@ async function quickScan() { console.error(`Failed to scan library: ${lib.name}`); } } - + if (jobs.length === 0) { - alert('Failed to start scan for any library'); + if ((window as any).showToast?.error) { + (window as any).showToast.error('Failed to start scan for any library'); + } return; } - + // Step 3: Show progress UI showScanProgress(jobs, libraryNames); - + } catch (error) { console.error('Scan error:', error); - alert('Failed to start scan: ' + error.message); + 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'); + // Trigger slide-in animation by removing opacity and transform classes + container.classList.remove('opacity-0', '-translate-y-2.5'); + + // Create progress items for each library + list.innerHTML = jobIds.map(jobId => ` +
+
+ + ${libraryNames[jobId]} + + + Pending... + +
+
+
+
+
+
+ `).join(''); + + // Start polling + pollScanProgress(jobIds, libraryNames); +} + +function pollScanProgress(jobIds: string[], libraryNames: Record): void { + const token = localStorage.getItem('token'); + const startTime = Date.now(); + + const interval = 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(); + + // Update individual library progress + 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); + } + } + + // Update overall progress + 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 + '%'; + + // Update status text + const elapsed = Math.round((Date.now() - startTime) / 1000); + if (!allComplete && statusText) { + statusText.textContent = `Scanning... ${elapsed}s elapsed • ${totalFiles} files processed`; + } + + // Check if all complete + if (allComplete) { + clearInterval(interval); + 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'); +} + +// Export to window +(window as any).scanAllLibraries = scanAllLibraries; +(window as any).hideScanProgress = hideScanProgress; ``` -#### Step 2: Add Progress UI to admin.templ +#### Step 2: Update admin.templ to Include admin.js -**Location:** After the Quick Actions grid (around line 64) +**Location:** `templates/admin.templ` lines 7-12 (head section) -**Add this HTML:** +**Add admin.js script tag:** +```html + + + + +``` + +#### Step 3: Update Scan Button onClick Handler + +**Location:** `templates/admin.templ` line 54 + +**Change from:** +```html + - +
@@ -201,7 +446,7 @@ async function quickScan() { 0%
-
@@ -237,161 +482,33 @@ async function quickScan() {
``` -#### Step 3: Add Progress Polling Logic +#### Step 5: Build TypeScript to JavaScript -**Location:** In the `