fix: add missing code quality violation detail template
Problem:
- Accessing /admin/code-quality/violations/{id} returned 500 error
- TemplateNotFound: 'admin/code-quality-violation-detail.html'
- Route existed but template file was missing
Solution:
Created complete violation detail template with:
Features:
- Displays violation details (severity, status, rule, file, line)
- Shows code context and suggestions
- Status management (open, assigned, resolved, ignored)
- Assignment to users
- Comment system for collaboration
- Alpine.js component for API integration
- Loading and error states
- Proper dark mode support
Template Structure:
- Extends admin/base.html
- Uses codeQualityViolationDetail(violationId) Alpine component
- Loads violation data via API on init
- Interactive status updates and comments
- Breadcrumb navigation back to violations list
API Endpoints Used:
- GET /api/v1/admin/code-quality/violations/{id}
- PATCH /api/v1/admin/code-quality/violations/{id}/status
- PATCH /api/v1/admin/code-quality/violations/{id}/assign
- POST /api/v1/admin/code-quality/violations/{id}/comments
Now violation detail page loads successfully.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
306
app/templates/admin/code-quality-violation-detail.html
Normal file
306
app/templates/admin/code-quality-violation-detail.html
Normal file
@@ -0,0 +1,306 @@
|
||||
{# app/templates/admin/code-quality-violation-detail.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Violation Detail{% endblock %}
|
||||
|
||||
{% block alpine_data %}codeQualityViolationDetail({{ violation_id }}){% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
function codeQualityViolationDetail(violationId) {
|
||||
return {
|
||||
...data(),
|
||||
currentPage: 'code-quality',
|
||||
violationId: violationId,
|
||||
violation: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
updating: false,
|
||||
commenting: false,
|
||||
newComment: '',
|
||||
newStatus: '',
|
||||
assignedTo: '',
|
||||
|
||||
async init() {
|
||||
await this.loadViolation();
|
||||
},
|
||||
|
||||
async loadViolation() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(`/api/v1/admin/code-quality/violations/${this.violationId}`);
|
||||
this.violation = response.data;
|
||||
this.newStatus = this.violation.status;
|
||||
this.assignedTo = this.violation.assigned_to || '';
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Load Violation');
|
||||
this.error = error.response?.data?.message || 'Failed to load violation details';
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async updateStatus() {
|
||||
if (!this.newStatus) return;
|
||||
|
||||
this.updating = true;
|
||||
|
||||
try {
|
||||
await apiClient.patch(`/api/v1/admin/code-quality/violations/${this.violationId}/status`, {
|
||||
status: this.newStatus
|
||||
});
|
||||
|
||||
Utils.showToast('Status updated successfully', 'success');
|
||||
await this.loadViolation();
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Update Status');
|
||||
Utils.showToast(error.response?.data?.message || 'Failed to update status', 'error');
|
||||
} finally {
|
||||
this.updating = false;
|
||||
}
|
||||
},
|
||||
|
||||
async assignViolation() {
|
||||
if (!this.assignedTo) return;
|
||||
|
||||
this.updating = true;
|
||||
|
||||
try {
|
||||
await apiClient.patch(`/api/v1/admin/code-quality/violations/${this.violationId}/assign`, {
|
||||
assigned_to: this.assignedTo
|
||||
});
|
||||
|
||||
Utils.showToast('Violation assigned successfully', 'success');
|
||||
await this.loadViolation();
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Assign Violation');
|
||||
Utils.showToast(error.response?.data?.message || 'Failed to assign violation', 'error');
|
||||
} finally {
|
||||
this.updating = false;
|
||||
}
|
||||
},
|
||||
|
||||
async addComment() {
|
||||
if (!this.newComment.trim()) return;
|
||||
|
||||
this.commenting = true;
|
||||
|
||||
try {
|
||||
await apiClient.post(`/api/v1/admin/code-quality/violations/${this.violationId}/comments`, {
|
||||
comment: this.newComment
|
||||
});
|
||||
|
||||
this.newComment = '';
|
||||
Utils.showToast('Comment added successfully', 'success');
|
||||
await this.loadViolation();
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Add Comment');
|
||||
Utils.showToast(error.response?.data?.message || 'Failed to add comment', 'error');
|
||||
} finally {
|
||||
this.commenting = false;
|
||||
}
|
||||
},
|
||||
|
||||
getSeverityColor(severity) {
|
||||
return severity === 'error' ? 'red' : 'yellow';
|
||||
},
|
||||
|
||||
getStatusColor(status) {
|
||||
const colors = {
|
||||
'open': 'blue',
|
||||
'assigned': 'purple',
|
||||
'resolved': 'green',
|
||||
'ignored': 'gray'
|
||||
};
|
||||
return colors[status] || 'gray';
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
return Utils.formatDate(dateString);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||
Violation Details
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
Review and manage architecture violation
|
||||
</p>
|
||||
</div>
|
||||
<a href="/admin/code-quality/violations"
|
||||
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-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:shadow-outline-gray">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Violations
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div x-show="loading" class="text-center py-12">
|
||||
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading violation details...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="font-semibold">Error loading violation</p>
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div x-show="!loading && violation" class="grid gap-6 mb-8">
|
||||
<!-- Violation Info Card -->
|
||||
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
||||
:class="`bg-${getSeverityColor(violation.severity)}-100 text-${getSeverityColor(violation.severity)}-800 dark:bg-${getSeverityColor(violation.severity)}-800 dark:text-${getSeverityColor(violation.severity)}-100`"
|
||||
x-text="violation.severity.toUpperCase()"></span>
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
||||
:class="`bg-${getStatusColor(violation.status)}-100 text-${getStatusColor(violation.status)}-800 dark:bg-${getStatusColor(violation.status)}-800 dark:text-${getStatusColor(violation.status)}-100`"
|
||||
x-text="violation.status.toUpperCase()"></span>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-200" x-text="violation.rule_id"></h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1" x-text="violation.rule_name"></p>
|
||||
</div>
|
||||
<div class="text-right text-sm text-gray-500 dark:text-gray-400">
|
||||
<p>ID: <span x-text="violation.id"></span></p>
|
||||
<p>Scan: <span x-text="violation.scan_id"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Violation Details -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">File Path</label>
|
||||
<code class="block px-3 py-2 bg-gray-100 dark:bg-gray-900 text-sm rounded" x-text="violation.file_path"></code>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Line Number</label>
|
||||
<p class="text-gray-900 dark:text-white" x-text="violation.line_number"></p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">First Detected</label>
|
||||
<p class="text-gray-900 dark:text-white" x-text="formatDate(violation.first_detected)"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Message</label>
|
||||
<p class="text-gray-900 dark:text-white" x-text="violation.message"></p>
|
||||
</div>
|
||||
|
||||
<div x-show="violation.context">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Code Context</label>
|
||||
<pre class="p-3 bg-gray-100 dark:bg-gray-900 text-sm rounded overflow-x-auto"><code x-text="violation.context"></code></pre>
|
||||
</div>
|
||||
|
||||
<div x-show="violation.suggestion">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Suggestion</label>
|
||||
<div class="p-3 bg-blue-50 dark:bg-blue-900 text-sm rounded">
|
||||
<p class="text-blue-900 dark:text-blue-100" x-text="violation.suggestion"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Management Card -->
|
||||
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<h4 class="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-4">Manage Violation</h4>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2 mb-6">
|
||||
<!-- Update Status -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Status</label>
|
||||
<div class="flex gap-2">
|
||||
<select x-model="newStatus"
|
||||
class="flex-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-select rounded-md">
|
||||
<option value="open">Open</option>
|
||||
<option value="assigned">Assigned</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
<option value="ignored">Ignored</option>
|
||||
</select>
|
||||
<button @click="updateStatus()"
|
||||
:disabled="updating || newStatus === violation.status"
|
||||
class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!updating">Update</span>
|
||||
<span x-show="updating">Updating...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assign -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Assign To</label>
|
||||
<div class="flex gap-2">
|
||||
<input x-model="assignedTo"
|
||||
type="text"
|
||||
placeholder="Username or email"
|
||||
class="flex-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input rounded-md">
|
||||
<button @click="assignViolation()"
|
||||
:disabled="updating || !assignedTo"
|
||||
class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!updating">Assign</span>
|
||||
<span x-show="updating">Assigning...</span>
|
||||
</button>
|
||||
</div>
|
||||
<p x-show="violation.assigned_to" class="mt-1 text-xs text-gray-500">
|
||||
Currently assigned to: <span x-text="violation.assigned_to"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div>
|
||||
<h5 class="text-md font-semibold text-gray-700 dark:text-gray-200 mb-3">Comments</h5>
|
||||
|
||||
<!-- Add Comment -->
|
||||
<div class="mb-4">
|
||||
<textarea x-model="newComment"
|
||||
rows="3"
|
||||
placeholder="Add a comment..."
|
||||
class="block w-full mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-textarea rounded-md"></textarea>
|
||||
<div class="mt-2 flex justify-end">
|
||||
<button @click="addComment()"
|
||||
:disabled="commenting || !newComment.trim()"
|
||||
class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!commenting">Add Comment</span>
|
||||
<span x-show="commenting">Adding...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comment List -->
|
||||
<div class="space-y-3">
|
||||
<template x-if="!violation.comments || violation.comments.length === 0">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 italic">No comments yet</p>
|
||||
</template>
|
||||
|
||||
<template x-for="comment in (violation.comments || [])" :key="comment.id">
|
||||
<div class="p-3 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="comment.user"></p>
|
||||
<p class="text-xs text-gray-500" x-text="formatDate(comment.created_at)"></p>
|
||||
</div>
|
||||
<p class="text-sm text-gray-900 dark:text-white" x-text="comment.comment"></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user