fix(audit-g5d): surface ErrorType taxonomy on FailingSourcesCard
Alembic 0032 adds Source.error_type (varchar(32), indexed).
_update_source_health stamps it alongside last_error on status='error'
and clears it on 'ok'. SourceRecord/to_dict exposes it.
FailingSourcesCard renders a colored chip next to the consecutive-
failures count, with a tooltip explaining the suggested operator
action. Color reflects intent:
- warning (yellow) — operator action needed (auth_error)
- info (blue) — backend-paced (rate_limited / timeout /
network_error / partial / tier_limited)
- error (red) — likely terminal without intervention
(not_found / access_denied / validation_failed /
unsupported_url / http_error / unknown_error)
Audit 2026-06-02: the backend computed 13 ErrorType categories but
only the free-text last_error reached the operator. Bulk-triage by
class ("all auth_error → rotate cookies", "12 rate_limited → just
wait") required opening Logs per row.
This commit is contained in:
@@ -25,6 +25,15 @@
|
||||
<v-chip size="x-small" color="error" variant="flat" label class="fc-fail__count">
|
||||
{{ s.consecutive_failures }}× failed
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="s.error_type"
|
||||
size="x-small" variant="outlined" label
|
||||
:color="errorTypeColor(s.error_type)"
|
||||
class="fc-fail__class"
|
||||
:title="errorTypeHint(s.error_type)"
|
||||
>
|
||||
{{ s.error_type }}
|
||||
</v-chip>
|
||||
<span class="fc-fail__err" :title="s.last_error || ''">
|
||||
{{ s.last_error || 'no error message recorded' }}
|
||||
</span>
|
||||
@@ -66,6 +75,42 @@ const open = ref(true)
|
||||
// Per-row loading flag so the spinner lives on the row whose Logs
|
||||
// button was clicked, not on every row.
|
||||
const logLoadingIds = ref(new Set())
|
||||
|
||||
// Audit 2026-06-02: surface the ErrorType taxonomy as a colored chip
|
||||
// next to the consecutive-failures count so operators can bulk-triage
|
||||
// by error class. Color reflects "what to do next":
|
||||
// warning (yellow) — auth/cookie issue: operator should rotate
|
||||
// info (blue) — backend-paced (cooldown / rate limit / timeout)
|
||||
// error (red) — likely terminal without operator intervention
|
||||
const ERROR_TYPE_COLOR = {
|
||||
auth_error: 'warning',
|
||||
rate_limited: 'info',
|
||||
timeout: 'info',
|
||||
network_error: 'info',
|
||||
not_found: 'error',
|
||||
access_denied: 'error',
|
||||
validation_failed: 'error',
|
||||
unsupported_url: 'error',
|
||||
http_error: 'error',
|
||||
unknown_error: 'error',
|
||||
partial: 'info',
|
||||
tier_limited: 'info',
|
||||
no_new_content: 'info',
|
||||
}
|
||||
const ERROR_TYPE_HINT = {
|
||||
auth_error: 'Cookies likely expired — re-upload in Credentials.',
|
||||
rate_limited: 'Platform-wide cooldown active. Will retry after it expires.',
|
||||
timeout: 'Subprocess exceeded its time budget. Often retries cleanly.',
|
||||
network_error: 'Transient network issue. Will retry on next tick.',
|
||||
not_found: 'URL 404 — creator may have renamed or deleted.',
|
||||
access_denied: 'Subscription tier may not grant this content.',
|
||||
validation_failed: 'Downloaded files were quarantined by the validator.',
|
||||
http_error: 'Generic HTTP error — see Logs.',
|
||||
unsupported_url: 'gallery-dl does not support this URL pattern.',
|
||||
unknown_error: 'Could not classify — see Logs.',
|
||||
}
|
||||
function errorTypeColor(t) { return ERROR_TYPE_COLOR[t] || 'error' }
|
||||
function errorTypeHint(t) { return ERROR_TYPE_HINT[t] || '' }
|
||||
async function onViewLogs(s) {
|
||||
if (logLoadingIds.value.has(s.id)) return
|
||||
logLoadingIds.value = new Set(logLoadingIds.value).add(s.id)
|
||||
@@ -115,6 +160,7 @@ async function onViewLogs(s) {
|
||||
}
|
||||
.fc-fail__artist { font-weight: 600; white-space: nowrap; }
|
||||
.fc-fail__count { flex: 0 0 auto; }
|
||||
.fc-fail__class { flex: 0 0 auto; }
|
||||
.fc-fail__err {
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
font-size: 0.8rem;
|
||||
|
||||
Reference in New Issue
Block a user