Skip to main content

max / makenotwork

6.6 KB · 148 lines History Blame Raw
1 {% extends "base.html" %}
2
3 {% block title %}Admin: Scan Pipeline - Makenot.work{% endblock %}
4 {% block body_attrs %} class="padded-page admin-page"{% endblock %}
5
6 {% block content %}
7 {% include "partials/site_header.html" %}
8
9 <div class="container">
10 {% include "partials/admin_nav.html" %}
11
12 <h1 class="page-title">Scan Pipeline</h1>
13
14 <section class="admin-scan-section">
15 <div class="admin-section-header">
16 <h2>Pipeline Health</h2>
17 <span class="dimmed text-sm">last 24h</span>
18 </div>
19 <div class="layer-health-grid">
20 {% for card in layer_health %}
21 <div class="layer-health-card layer-{{ card.status_badge }}">
22 <div class="layer-name">{{ card.layer }}</div>
23 <div class="layer-stat">
24 <span class="dimmed">success</span> {{ card.success_rate_pct }}%
25 </div>
26 <div class="layer-stat">
27 <span class="dimmed">error</span> {{ card.error_rate_pct }}%
28 </div>
29 <div class="layer-stat">
30 <span class="dimmed">fails</span> {{ card.fail_count }}
31 </div>
32 <div class="layer-stat text-xs">
33 last clean: {{ card.last_seen }}
34 </div>
35 </div>
36 {% endfor %}
37 </div>
38 </section>
39
40 <section class="admin-scan-section">
41 <div class="admin-section-header">
42 <h2>Active Queue</h2>
43 <span class="dimmed text-sm">auto-refresh every 10s</span>
44 </div>
45 <div class="stats-row"
46 hx-get="/admin/uploads/queue-summary"
47 hx-trigger="every 10s"
48 hx-swap="outerHTML">
49 <div class="stat-box">
50 <div class="number">{{ queue_pending }}</div>
51 <div class="label">Pending</div>
52 </div>
53 <div class="stat-box">
54 <div class="number">{{ queue_running }}</div>
55 <div class="label">Scanning</div>
56 </div>
57 </div>
58 </section>
59
60 <section class="admin-scan-section">
61 <div class="admin-section-header">
62 <h2>Audit Log</h2>
63 <a href="/admin/uploads/audit" class="text-sm">View full log</a>
64 </div>
65 <p class="dimmed text-sm">Every promote, quarantine, and re-scan is recorded.</p>
66 </section>
67
68 <section class="admin-scan-section">
69 <div class="admin-section-header">
70 <h2>Held for Review</h2>
71 <div>
72 <span class="dimmed text-sm">{{ total_held }} held</span>
73 {% if total_held > 0 %}
74 <button class="btn-secondary small"
75 hx-post="/api/admin/uploads/bulk/rescan"
76 hx-target="#uploads-table"
77 hx-swap="innerHTML"
78 hx-confirm="Re-scan every held file under the current pipeline? Workers will pick them up shortly.">
79 Re-scan all held
80 </button>
81 <button class="btn-secondary small"
82 hx-post="/api/admin/uploads/bulk/promote"
83 hx-target="#uploads-table"
84 hx-swap="innerHTML"
85 hx-prompt="Note explaining why every held file is safe to clear (required, audit-logged):"
86 hx-vals='js:{ note: event.detail.prompt }'>
87 Promote all held
88 </button>
89 {% endif %}
90 </div>
91 </div>
92 <div id="uploads-table">
93 {% include "partials/admin_upload_entries.html" %}
94 </div>
95 </section>
96
97 <section class="admin-scan-section">
98 <details class="scan-history-grid">
99 <summary>
100 Recent History
101 <span class="dimmed text-sm">— last 7 days, {{ history_total }} scan{% if history_total != 1 %}s{% endif %}</span>
102 </summary>
103 {% if recent_history.is_empty() %}
104 <div class="dimmed text-sm" style="padding: var(--space-3) 0;">No scans recorded in the last 7 days.</div>
105 {% else %}
106 <div class="scroll-x" style="margin-top: var(--space-3);">
107 <table class="compact-table" aria-label="Recent scan results">
108 <thead>
109 <tr>
110 <th>Scanned</th>
111 <th>File</th>
112 <th>Status</th>
113 <th>Layers</th>
114 <th class="text-right">Size</th>
115 <th>SHA-256</th>
116 </tr>
117 </thead>
118 <tbody>
119 {% for row in recent_history %}
120 <tr>
121 <td class="text-sm nowrap">{{ row.scanned_at }}</td>
122 <td class="text-sm" title="{{ row.s3_key_short }}"><code>{{ row.s3_key_short }}</code></td>
123 <td class="text-sm">
124 <span class="layer-chip layer-chip-{% if row.scan_status == "clean" %}pass{% else if row.scan_status == "quarantined" %}fail{% else if row.scan_status == "held_for_review" %}error{% else %}skip{% endif %}">
125 {{ row.scan_status }}
126 </span>
127 </td>
128 <td class="layer-chips">
129 {% for chip in row.layers %}
130 <span class="layer-chip layer-chip-{{ chip.verdict }}"
131 title="{{ chip.layer }}: {{ chip.verdict }}{% if let Some(d) = chip.detail %} — {{ d }}{% endif %}">
132 {{ chip.layer }}
133 </span>
134 {% endfor %}
135 </td>
136 <td class="text-sm text-right nowrap">{{ row.size_bytes }}</td>
137 <td class="text-xs nowrap"><code>{{ row.sha256_short }}</code></td>
138 </tr>
139 {% endfor %}
140 </tbody>
141 </table>
142 </div>
143 {% endif %}
144 </details>
145 </section>
146 </div>
147 {% endblock %}
148