(#1687 part 4, reported by @IndividualGhost1905)
Reporter clarified after part 1 shipped that point 2 wasn't about
archive `tags` (which describe the model — home decor, toys), but
about failure-cause classification on the *log* row itself:
spaghetti, jam, bed-adhesion, etc. Different surface, different
lifetime.
The data field he wanted already existed. PrintLogEntry.failure_reason
is a String(100); the Failure Analysis widget already groups by it;
the Archive Edit modal already mirrors archive.failure_reason into
the most recent log entry (archives.py:1421, shipped with #1444).
The only gaps were:
1. The GET endpoint silently dropped failure_reason (and archive_id
and created_by_id) from PrintLogEntrySchema construction even
when set in the DB — so the Print Log table couldn't render what
the Failure Analysis widget grouped by. Fixed independently of
the editor; regression test added.
2. Orphan log entries (no archive — dispatch errors, aborts before
archive creation, manual entries) had no edit path at all because
the Archive Edit modal cannot reach them. The new endpoint is
the only way to classify those rows.
Changes:
- Backend: new PATCH /print-log/{entry_id} taking
{failure_reason, status}, gated on require_ownership_permission(
ARCHIVES_UPDATE_ALL, ARCHIVES_UPDATE_OWN) — same ownership shape
as the per-row DELETE. Validates against the same 11-key failure
vocabulary and 5-key status set the Archive Edit modal uses;
unknown values return 400 rather than getting stored as raw text
(the i18n layer maps the value back through the vocabulary,
unrecognised values would render as literal strings).
Empty-string failure_reason stores back as NULL so the column's
nullable=True intent is preserved end-to-end. GET endpoint now
surfaces failure_reason, archive_id, created_by_id.
- Frontend: FAILURE_REASON_KEYS moved to an export from
EditArchiveModal.tsx so the new editor reuses the exact same
vocabulary — backend and frontend stay in lockstep. Pencil icon
beside the existing trash icon on every Print Log row, opens a
compact two-field modal (status + failure reason). Save
invalidates print-log and archives-stats query keys so the
Failure Analysis widget reflects the re-classification on the
same response cycle. Failure reason rendered as a sub-label under
the status badge, matching PrintLogTable.tsx's convention.
- i18n: 10 new keys (editEntryTitle, editEntryDescription,
entryUpdated, entryUpdateFailed, archives.permission.noEdit, plus
a 5-key statuses block) translated across all 11 locales. No
English fallbacks.
- Wiki: features/print-log.md gains per-row actions section,
updated permissions table, PATCH/single-DELETE endpoint docs.
Statistics now aggregate over PrintLogEntry (one row per print event,
the same table backing the global Print Log) rather than PrintArchive
(one row per file). A reprint creates a new PrintLogEntry instead of
overwriting the source archive's runtime fields, so:
- a 100 g successful print + a 10 g failed reprint correctly sums to
110 g / 2 prints / 1 successful / 1 failed in Quick Stats and the
Prometheus /metrics endpoint (previously the failed reprint silently
replaced the source archive's data; totals dropped from 100 g to 10 g)
- the archive's card cost/energy_kwh are preserved on reprints (only
the first run writes them); per-run actuals live on PrintLogEntry
- failed/cancelled/stopped reprints record partial-aware filament: sum
of tracked spool deltas when inventory is set up, else estimate
scaled to progress%, else None — prevents the full slicer estimate
from inflating totals on a print that stopped at 10 % progress
PrintLogEntry gains six columns: archive_id (nullable FK, ON DELETE
SET NULL so log entries survive archive deletion preserving #1343
soft-delete-vs-stats decoupling), cost, energy_kwh, energy_cost,
failure_reason, created_by_id. Idempotent SQLite + Postgres migrations.
New per-archive surface:
- archive list response carries run_count / last_run_at /
total_filament_actual_grams / successful_run_count / failed_run_count
via a single batch JOIN, no N+1
- new GET /archives/{id}/runs endpoint returns every PrintLogEntry for
the archive (ARCHIVES_READ permission, newest-first ordering)
- archive cards render an orange "N prints" badge for archives with
more than one run; clicking the badge opens a dedicated PrintLogModal
with date/status/duration/filament/cost columns plus failure_reason
under failed runs. Also reachable via the context menu's new "Print
Log" entry (works for single-run archives too), and embedded at the
top of the Edit Archive modal for context.
The purge_stats=true delete path now hard-deletes linked PrintLogEntry
rows up front so the archive's contribution truly leaves the totals;
without it, ON DELETE SET NULL would orphan the runs and leave them
counting toward stats.
Add a chronological, table-based print log as a 4th view mode in the
Archives page. Log entries are stored in a separate print_log_entries
table — clearing the log never touches archives or queue items.
Backend:
- New PrintLogEntry model with independent table
- GET /print-log/ endpoint with search, printer, user, status, date filters
- DELETE /print-log/ clears only log entries
- Thumbnail serving endpoint for log entries
- write_log_entry() service called on print completion
- Auth: ARCHIVES_READ for viewing, ARCHIVES_DELETE_ALL for clearing
Frontend:
- New 'log' ViewMode with ClipboardList icon toggle
- Filterable/searchable table with pagination (10/25/50/100 rows)
- Colored status badges, duration formatting, filament color swatches
- Clear button with confirmation modal
- All filter state persisted to localStorage
- i18n: EN, DE, JA, FR, IT translations