Move 47 JS files from static/{admin,vendor,shared}/js/ to their
respective module directories app/modules/*/static/*/js/:
- Orders: orders.js, order-detail.js
- Catalog: products.js (renamed from vendor-products.js), product-*.js
- Inventory: inventory.js (admin & vendor)
- Customers: customers.js, users.js, user-*.js
- Billing: billing-history.js, subscriptions.js, subscription-tiers.js,
billing.js, invoices.js, feature-store.js, upgrade-prompts.js
- Messaging: messages.js, notifications.js, email-templates.js
- Marketplace: marketplace*.js, letzshop*.js, onboarding.js
- Monitoring: monitoring.js, background-tasks.js, imports.js, logs.js
- Dev Tools: testing-*.js, code-quality-*.js
Update 39 templates to reference new module static paths using
url_for('{module}_static', path='...') pattern.
Files staying in static/ (platform core):
- admin: dashboard, login, platforms, vendors, companies, admin-users,
settings, components, init-alpine, module-config
- vendor: dashboard, login, profile, settings, team, media, init-alpine
- shared: api-client, utils, money, icons, log-config, vendor-selector,
media-picker
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
293 lines
10 KiB
JavaScript
293 lines
10 KiB
JavaScript
// noqa: js-006 - async init pattern is safe, loadData has try/catch
|
|
/**
|
|
* 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,
|
|
scanDropdownOpen: 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() {
|
|
// Guard against multiple initialization
|
|
if (window._adminCodeQualityDashboardInitialized) return;
|
|
window._adminCodeQualityDashboardInitialized = true;
|
|
|
|
try {
|
|
// 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();
|
|
} catch (error) {
|
|
codeQualityLog.error('Failed to initialize code quality dashboard:', error);
|
|
}
|
|
},
|
|
|
|
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);
|
|
}
|
|
};
|
|
}
|