feat(ui): classify publisher errors with actionable hints

This commit is contained in:
Oliver 2026-02-21 13:11:43 +01:00
parent 24d8e5ad0f
commit 8d7375c99f
3 changed files with 65 additions and 2 deletions

View file

@ -169,6 +169,23 @@ def _publish_readiness(article: dict, meta: dict) -> tuple[bool, list[str]]:
return len(reasons) == 0, reasons
def _classify_publish_error(error_message: str | None) -> tuple[str, str]:
text = (error_message or "").lower()
if not text.strip():
return "ok", "-"
if "rechtsfreigabe fehlt" in text or "hauptbild nicht gesetzt" in text or "status ist nicht" in text:
return "policy", "Artikelvoraussetzungen im UI prüfen (Status/Rechtsfreigabe/Hauptbild)."
if "401" in text or "403" in text or "authorization" in text or "forbidden" in text or "unauthorized" in text:
return "auth", "WordPress Nutzer/App-Passwort prüfen."
if "404" in text and ("media" in text or "posts" in text or "wp-json" in text):
return "api", "WordPress REST-Endpunkt prüfen (`/wp-json/wp/v2`)."
if "timed out" in text or "timeout" in text or "nodename nor servname provided" in text or "name or service not known" in text:
return "dns", "DNS/Netzwerk zur WordPress-Domain prüfen."
if "media-upload fehlgeschlagen" in text or "liefert kein bild" in text or "featured_media" in text:
return "media", "Bild-URL/Format prüfen oder anderes Hauptbild auswählen."
return "unknown", "Fehlerdetails prüfen und bei Bedarf Job erneut starten."
def _legal_checklist(article: dict, feed: dict | None) -> list[dict[str, str]]:
meta = article.get("meta", {})
extraction = meta.get("extraction") if isinstance(meta.get("extraction"), dict) else {}
@ -289,6 +306,10 @@ def admin_dashboard(request: Request):
feeds = list_feeds()
runs = list_runs(limit=30)
publish_jobs = list_publish_jobs(limit=30)
for job in publish_jobs:
category, hint = _classify_publish_error(job.get("error_message"))
job["error_category"] = category
job["error_hint"] = hint
status_filter = request.query_params.get("status_filter")
if status_filter in {"new", "rewrite", "review", "approved", "published", "error"}:
articles = list_articles(limit=100, status_filter=status_filter)

View file

@ -131,6 +131,40 @@ button.secondary {
color: #991b1b;
}
.badge.errcat {
margin-bottom: 4px;
}
.badge.errcat-policy {
background: #fee2e2;
color: #991b1b;
}
.badge.errcat-auth {
background: #ffedd5;
color: #9a3412;
}
.badge.errcat-dns {
background: #dbeafe;
color: #1e40af;
}
.badge.errcat-media {
background: #fef9c3;
color: #854d0e;
}
.badge.errcat-api {
background: #ede9fe;
color: #5b21b6;
}
.badge.errcat-unknown {
background: #e2e8f0;
color: #334155;
}
.alert {
margin-bottom: 12px;
padding: 10px;

View file

@ -256,7 +256,7 @@
<h2>Publish Jobs</h2>
<table>
<thead>
<tr><th>ID</th><th>Artikel</th><th>Status</th><th>Attempts</th><th>WP Post</th><th>Fehler</th></tr>
<tr><th>ID</th><th>Artikel</th><th>Status</th><th>Attempts</th><th>WP Post</th><th>Fehler</th><th>Hinweis</th></tr>
</thead>
<tbody>
{% for j in publish_jobs %}
@ -274,7 +274,15 @@
-
{% endif %}
</td>
<td>{{ j.error_message or "-" }}</td>
<td>
{% if j.error_message %}
<span class="badge errcat errcat-{{ j.error_category }}">{{ j.error_category }}</span>
<div>{{ j.error_message }}</div>
{% else %}
-
{% endif %}
</td>
<td>{{ j.error_hint or "-" }}</td>
</tr>
{% endfor %}
</tbody>