/** * Code Quality Dashboard Component * Manages the unified code quality dashboard page * Supports multiple validator types: architecture, security, performance */ // Use centralized logger const codeQualityLog = window.LogConfig.createLogger('CODE-QUALITY'); function codeQualityDashboard() { return { // Extend base data ...data(), // Set current page for navigation currentPage: 'code-quality', // Validator type selection selectedValidator: 'all', // 'all', 'architecture', 'security', 'performance' validatorTypes: ['architecture', 'security', 'performance'], // Dashboard-specific data loading: false, scanning: false, error: null, successMessage: null, scanProgress: null, // Progress message during scan runningScans: [], // Track running scan IDs pollInterval: null, // Polling interval ID stats: { total_violations: 0, errors: 0, warnings: 0, info: 0, open: 0, assigned: 0, resolved: 0, ignored: 0, technical_debt_score: 100, trend: [], by_severity: {}, by_rule: {}, by_module: {}, top_files: [], last_scan: null, validator_type: null, by_validator: {} }, async init() { // Check URL for validator_type parameter const urlParams = new URLSearchParams(window.location.search); const urlValidator = urlParams.get('validator_type'); if (urlValidator && this.validatorTypes.includes(urlValidator)) { this.selectedValidator = urlValidator; } else { // Ensure 'all' is explicitly set as default this.selectedValidator = 'all'; } await this.loadStats(); // Check for any running scans on page load await this.checkRunningScans(); }, async checkRunningScans() { try { const runningScans = await apiClient.get('/admin/code-quality/scans/running'); if (runningScans && runningScans.length > 0) { this.scanning = true; this.runningScans = runningScans.map(s => s.id); this.updateProgressMessage(runningScans); this.startPolling(); } } catch (err) { codeQualityLog.error('Failed to check running scans:', err); } }, updateProgressMessage(scans) { const runningScans = scans.filter(s => s.status === 'running' || s.status === 'pending'); if (runningScans.length === 0) { this.scanProgress = null; return; } // Show progress from the first running scan const firstRunning = runningScans.find(s => s.status === 'running'); if (firstRunning && firstRunning.progress_message) { this.scanProgress = firstRunning.progress_message; } else { const validatorNames = runningScans.map(s => this.capitalizeFirst(s.validator_type)); this.scanProgress = `Running ${validatorNames.join(', ')} scan${runningScans.length > 1 ? 's' : ''}...`; } }, async loadStats() { this.loading = true; this.error = null; try { // Build URL with validator_type filter if not 'all' let url = '/admin/code-quality/stats'; if (this.selectedValidator !== 'all') { url += `?validator_type=${this.selectedValidator}`; } const stats = await apiClient.get(url); this.stats = stats; } catch (err) { codeQualityLog.error('Failed to load stats:', err); this.error = err.message; // Redirect to login if unauthorized if (err.message.includes('Unauthorized')) { window.location.href = '/admin/login'; } } finally { this.loading = false; } }, async selectValidator(validatorType) { if (this.selectedValidator !== validatorType) { this.selectedValidator = validatorType; await this.loadStats(); // Update URL without reload const url = new URL(window.location); if (validatorType === 'all') { url.searchParams.delete('validator_type'); } else { url.searchParams.set('validator_type', validatorType); } window.history.pushState({}, '', url); } }, async runScan(validatorType = 'all') { this.scanning = true; this.error = null; this.successMessage = null; this.scanProgress = 'Queuing scan...'; try { // Determine which validators to run const validatorTypesToRun = validatorType === 'all' ? this.validatorTypes : [validatorType]; const result = await apiClient.post('/admin/code-quality/scan', { validator_types: validatorTypesToRun }); // Store running scan IDs for polling this.runningScans = result.scans.map(s => s.id); // Show initial status message const validatorNames = validatorTypesToRun.map(v => this.capitalizeFirst(v)); this.scanProgress = `Running ${validatorNames.join(', ')} scan${validatorNames.length > 1 ? 's' : ''}...`; // Start polling for completion this.startPolling(); } catch (err) { codeQualityLog.error('Failed to run scan:', err); this.error = err.message; this.scanning = false; this.scanProgress = null; // Redirect to login if unauthorized if (err.message.includes('Unauthorized')) { window.location.href = '/admin/login'; } } }, startPolling() { // Clear any existing polling if (this.pollInterval) { clearInterval(this.pollInterval); } // Poll every 3 seconds this.pollInterval = setInterval(async () => { await this.pollScanStatus(); }, 3000); }, stopPolling() { if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; } }, async pollScanStatus() { if (this.runningScans.length === 0) { this.stopPolling(); this.scanning = false; this.scanProgress = null; return; } try { const runningScans = await apiClient.get('/admin/code-quality/scans/running'); // Update progress message from running scans this.updateProgressMessage(runningScans); // Check if our scans have completed const stillRunning = this.runningScans.filter(id => runningScans.some(s => s.id === id) ); if (stillRunning.length === 0) { // All scans completed - get final results await this.handleScanCompletion(); } else { // Update running scans list this.runningScans = stillRunning; } } catch (err) { codeQualityLog.error('Failed to poll scan status:', err); } }, async handleScanCompletion() { this.stopPolling(); // Get results for all completed scans let totalViolations = 0; let totalErrors = 0; let totalWarnings = 0; const completedScans = []; for (const scanId of this.runningScans) { try { const scan = await apiClient.get(`/admin/code-quality/scans/${scanId}/status`); completedScans.push(scan); totalViolations += scan.total_violations || 0; totalErrors += scan.errors || 0; totalWarnings += scan.warnings || 0; } catch (err) { codeQualityLog.error(`Failed to get scan ${scanId} results:`, err); } } // Format success message based on number of validators run if (completedScans.length > 1) { this.successMessage = `Scan completed: ${totalViolations} total violations found (${totalErrors} errors, ${totalWarnings} warnings) across ${completedScans.length} validators`; } else if (completedScans.length === 1) { const scan = completedScans[0]; this.successMessage = `${this.capitalizeFirst(scan.validator_type)} scan completed: ${scan.total_violations} violations found (${scan.errors} errors, ${scan.warnings} warnings)`; } else { this.successMessage = 'Scan completed'; } // Reload stats after scan await this.loadStats(); // Reset scanning state this.scanning = false; this.scanProgress = null; this.runningScans = []; // Clear success message after 5 seconds setTimeout(() => { this.successMessage = null; }, 5000); }, async refresh() { await this.loadStats(); }, capitalizeFirst(str) { return str.charAt(0).toUpperCase() + str.slice(1); } }; }