feat: implement background task architecture for code quality scans
- Add status fields to ArchitectureScan model (status, started_at, completed_at, error_message, progress_message) - Create database migration for new status fields - Create background task function execute_code_quality_scan() - Update API to return 202 with job IDs and support polling - Add code quality scans to unified BackgroundTasksService - Integrate scans into background tasks API and page - Implement frontend polling with 3-second interval - Add progress banner showing scan status - Users can navigate away while scans run in background - Document the implementation in architecture docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{# app/templates/admin/code-quality-dashboard.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state, alert_dynamic %}
|
||||
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button, action_button %}
|
||||
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
|
||||
|
||||
{% block title %}Code Quality Dashboard{% endblock %}
|
||||
|
||||
@@ -12,9 +12,46 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call page_header_flex(title='Code Quality Dashboard', subtitle='Architecture validation and technical debt tracking') %}
|
||||
{% call page_header_flex(title='Code Quality Dashboard', subtitle='Unified code quality tracking: architecture, security, and performance') %}
|
||||
{{ refresh_button(variant='secondary') }}
|
||||
{{ action_button('Run Scan', 'Scanning...', 'scanning', 'runScan()', icon='search') }}
|
||||
<!-- Scan Dropdown -->
|
||||
<div x-data="{ scanDropdownOpen: false }" class="relative">
|
||||
<button @click="scanDropdownOpen = !scanDropdownOpen"
|
||||
:disabled="scanning"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50">
|
||||
<template x-if="!scanning">
|
||||
<span class="flex items-center">
|
||||
<span x-html="$icon('search', 'w-4 h-4 mr-2')"></span>
|
||||
Run Scan
|
||||
<span x-html="$icon('chevron-down', 'w-4 h-4 ml-1')"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="scanning">
|
||||
<span>Scanning...</span>
|
||||
</template>
|
||||
</button>
|
||||
<div x-show="scanDropdownOpen"
|
||||
@click.away="scanDropdownOpen = false"
|
||||
x-transition
|
||||
class="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg z-10 border border-gray-200 dark:border-gray-700">
|
||||
<button @click="runScan('all'); scanDropdownOpen = false"
|
||||
class="block w-full px-4 py-2 text-sm text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-t-lg">
|
||||
Run All Validators
|
||||
</button>
|
||||
<button @click="runScan('architecture'); scanDropdownOpen = false"
|
||||
class="block w-full px-4 py-2 text-sm text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
Architecture Only
|
||||
</button>
|
||||
<button @click="runScan('security'); scanDropdownOpen = false"
|
||||
class="block w-full px-4 py-2 text-sm text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
Security Only
|
||||
</button>
|
||||
<button @click="runScan('performance'); scanDropdownOpen = false"
|
||||
class="block w-full px-4 py-2 text-sm text-left text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-b-lg">
|
||||
Performance Only
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading dashboard...') }}
|
||||
@@ -23,8 +60,79 @@
|
||||
|
||||
{{ alert_dynamic(type='success', message_var='successMessage', show_condition='successMessage') }}
|
||||
|
||||
<!-- Scan Progress Alert -->
|
||||
<div x-show="scanning && scanProgress"
|
||||
x-transition
|
||||
class="flex items-center p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400"
|
||||
role="alert">
|
||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span x-text="scanProgress">Running scan...</span>
|
||||
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">(You can navigate away - scan runs in background)</span>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard Content -->
|
||||
<div x-show="!loading && !error">
|
||||
<!-- Validator Type Tabs -->
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-wrap space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1 inline-flex">
|
||||
<button @click="selectValidator('all')"
|
||||
:class="selectedValidator === 'all' ? 'bg-white dark:bg-gray-800 text-purple-600 dark:text-purple-400 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-colors duration-150">
|
||||
All
|
||||
</button>
|
||||
<button @click="selectValidator('architecture')"
|
||||
:class="selectedValidator === 'architecture' ? 'bg-white dark:bg-gray-800 text-purple-600 dark:text-purple-400 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-colors duration-150">
|
||||
Architecture
|
||||
</button>
|
||||
<button @click="selectValidator('security')"
|
||||
:class="selectedValidator === 'security' ? 'bg-white dark:bg-gray-800 text-red-600 dark:text-red-400 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-colors duration-150">
|
||||
Security
|
||||
</button>
|
||||
<button @click="selectValidator('performance')"
|
||||
:class="selectedValidator === 'performance' ? 'bg-white dark:bg-gray-800 text-yellow-600 dark:text-yellow-400 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-colors duration-150">
|
||||
Performance
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Per-Validator Summary (shown when "All" is selected) -->
|
||||
<div x-show="selectedValidator === 'all' && stats.by_validator && Object.keys(stats.by_validator).length > 0" class="grid gap-4 mb-6 md:grid-cols-3">
|
||||
<template x-for="vtype in ['architecture', 'security', 'performance']" :key="vtype">
|
||||
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800 cursor-pointer hover:ring-2 hover:ring-purple-500"
|
||||
@click="selectValidator(vtype)">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400 capitalize" x-text="vtype"></p>
|
||||
<p class="text-xl font-semibold text-gray-700 dark:text-gray-200"
|
||||
x-text="stats.by_validator[vtype]?.total_violations || 0"></p>
|
||||
</div>
|
||||
<div class="p-2 rounded-full"
|
||||
:class="{
|
||||
'bg-purple-100 text-purple-600 dark:bg-purple-900 dark:text-purple-400': vtype === 'architecture',
|
||||
'bg-red-100 text-red-600 dark:bg-red-900 dark:text-red-400': vtype === 'security',
|
||||
'bg-yellow-100 text-yellow-600 dark:bg-yellow-900 dark:text-yellow-400': vtype === 'performance'
|
||||
}">
|
||||
<span x-html="vtype === 'architecture' ? $icon('cube', 'w-5 h-5') : (vtype === 'security' ? $icon('shield-check', 'w-5 h-5') : $icon('lightning-bolt', 'w-5 h-5'))"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex space-x-3 text-xs">
|
||||
<span class="text-red-600 dark:text-red-400">
|
||||
<span x-text="stats.by_validator[vtype]?.errors || 0"></span> errors
|
||||
</span>
|
||||
<span class="text-yellow-600 dark:text-yellow-400">
|
||||
<span x-text="stats.by_validator[vtype]?.warnings || 0"></span> warnings
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
<!-- Card: Total Violations -->
|
||||
@@ -192,7 +300,15 @@
|
||||
<template x-if="stats.by_rule && Object.keys(stats.by_rule).length > 0">
|
||||
<template x-for="[rule_id, count] in Object.entries(stats.by_rule)" :key="rule_id">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-700 dark:text-gray-300" x-text="rule_id"></span>
|
||||
<span class="text-gray-700 dark:text-gray-300 flex items-center">
|
||||
<span class="inline-block w-2 h-2 rounded-full mr-2"
|
||||
:class="{
|
||||
'bg-purple-500': rule_id.startsWith('API') || rule_id.startsWith('SVC') || rule_id.startsWith('FE'),
|
||||
'bg-red-500': rule_id.startsWith('SEC'),
|
||||
'bg-yellow-500': rule_id.startsWith('PERF')
|
||||
}"></span>
|
||||
<span x-text="rule_id"></span>
|
||||
</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-gray-100" x-text="count"></span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -231,17 +347,17 @@
|
||||
Quick Actions
|
||||
</h4>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/admin/code-quality/violations"
|
||||
<a :href="'/admin/code-quality/violations' + (selectedValidator !== 'all' ? '?validator_type=' + selectedValidator : '')"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
|
||||
<span x-html="$icon('clipboard-list', 'w-4 h-4 mr-2')"></span>
|
||||
View All Violations
|
||||
</a>
|
||||
<a href="/admin/code-quality/violations?status=open"
|
||||
<a :href="'/admin/code-quality/violations?status=open' + (selectedValidator !== 'all' ? '&validator_type=' + selectedValidator : '')"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-300 transition-colors duration-150 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:shadow-outline-gray">
|
||||
<span x-html="$icon('folder-open', 'w-4 h-4 mr-2')"></span>
|
||||
Open Violations
|
||||
</a>
|
||||
<a href="/admin/code-quality/violations?severity=error"
|
||||
<a :href="'/admin/code-quality/violations?severity=error' + (selectedValidator !== 'all' ? '&validator_type=' + selectedValidator : '')"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-300 transition-colors duration-150 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:shadow-outline-gray">
|
||||
<span x-html="$icon('exclamation', 'w-4 h-4 mr-2')"></span>
|
||||
Errors Only
|
||||
@@ -253,6 +369,9 @@
|
||||
<!-- Last Scan Info -->
|
||||
<div x-show="stats.last_scan" class="text-sm text-gray-600 dark:text-gray-400 text-center">
|
||||
Last scan: <span x-text="stats.last_scan ? new Date(stats.last_scan).toLocaleString() : 'Never'"></span>
|
||||
<template x-if="selectedValidator !== 'all'">
|
||||
<span class="ml-2">(<span class="capitalize" x-text="selectedValidator"></span> validator)</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user