Files
doctate/server/templates/case_detail.html
T
Brummel b2b1368902 Add admin-only case reset functionality
Introduce a new "reset" action for bulk operations and a dedicated
endpoint for individual case resets. These features allow administrators
to revert a case to its raw audio state by deleting derived artifacts
like transcripts, analysis input, and documents. The functionality also
includes renaming `.m4a.failed` files back to `.m4a` to re-trigger
transcription.

This commit also:
- Adds a `reset_case_artefacts` helper function to `case_actions.rs`.
- Implements the `handle_reset_case` endpoint.
- Adds the "Reset" button to the UI for administrators.
- Includes unit and integration tests to verify the new functionality
  and access control.
2026-04-16 11:27:55 +02:00

90 lines
3.8 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Doctate — Fall {{ case_id_short }}</title>
<style>
body { font-family: sans-serif; max-width: 900px; margin: 2em auto; padding: 0 1em; }
header { display: flex; justify-content: space-between; align-items: center; }
header form { margin: 0; }
.back { color: #4a90e2; text-decoration: none; }
.oneliner { font-size: 1.1em; color: #222; margin: 0.6em 0 1em; }
.meta { color: #666; font-family: monospace; font-size: 0.9em; }
.recording { margin: 1em 0; padding: 0.7em; border: 1px solid #ddd; border-radius: 4px; }
.recording.failed-row { background: #fff7f7; border-left: 3px solid #e24a4a; }
.recording-head { display: flex; align-items: center; gap: 1em; flex-wrap: wrap; }
.recording-head span { font-family: monospace; font-size: 0.9em; min-width: 20em; }
.transcript { margin: 0.6em 0 0; padding: 0.6em; background: #f6f6f6; border-radius: 4px; white-space: pre-wrap; font-family: serif; }
.pending { color: #888; font-style: italic; margin-top: 0.5em; }
.failed { color: #b00; font-weight: bold; margin-top: 0.5em; }
.status-badge { display: inline-block; padding: 0.1em 0.6em; border-radius: 999px; font-size: 0.8em; font-weight: bold; color: white; }
.status-badge.open { background: #4a90e2; }
.status-badge.done { background: #7ed321; }
.actions { margin: 1em 0 1.5em; }
.actions form { margin: 0; display: inline; }
.actions button { font-size: 1em; padding: 0.5em 1em; }
.actions a.doc-link { display: inline-block; padding: 0.5em 1em; background: #7ed321; color: white; text-decoration: none; border-radius: 4px; font-weight: bold; }
.actions .analyzing { color: #888; font-style: italic; }
.actions .llm-missing { color: #a66; font-style: italic; }
</style>
</head>
<body>
<header>
<div><a class="back" href="/web/cases">&larr; Zurück</a></div>
<form method="post" action="/web/logout"><button type="submit">Logout</button></form>
</header>
<h1>Fall {{ case_id_short }} {% if has_document %}<span class="status-badge done">ausgewertet</span>{% else %}<span class="status-badge open">offen</span>{% endif %}</h1>
{% match oneliner %}
{% when Some with (t) %}
<div class="oneliner">{{ t }}</div>
{% when None %}
<div class="oneliner" style="color:#888;font-style:italic">Noch kein Oneliner.</div>
{% endmatch %}
<div class="meta">{{ case_id }}</div>
<div class="actions">
{% if analyzing %}
<span class="analyzing">wird analysiert …</span>
{% else if can_analyze && !has_document %}
<form method="post" action="/web/cases/{{ case_id }}/analyze"><button type="submit">Analysieren</button></form>
{% else if llm_missing && !has_document %}
<span class="llm-missing">LLM-Analyse nicht konfiguriert</span>
{% endif %}
{% if is_admin %}
<form method="post" action="/web/cases/{{ case_id }}/reset" style="display:inline"><button type="submit">Reset</button></form>
{% endif %}
<form method="post" action="/web/cases/{{ case_id }}/delete" style="margin-left:auto;display:inline"><button type="submit">Entfernen</button></form>
</div>
<h2>Aufnahmen ({{ recordings.len() }})</h2>
{% for rec in recordings %}
<div class="recording{% if rec.failed %} failed-row{% endif %}">
<div class="recording-head">
<span>{{ rec.filename }}</span>
<audio controls preload="none">
<source src="/web/audio/{{ slug }}/{{ case_id }}/{{ rec.filename }}" type="audio/mp4">
</audio>
</div>
{% if rec.failed %}
<div class="failed">Transkription fehlgeschlagen — .failed-Suffix entfernen zum Erneut-Versuchen.</div>
{% else %}
{% match rec.transcript %}
{% when Some with (t) %}
{% if t.is_empty() %}
<div class="pending">(stille Aufnahme)</div>
{% else %}
<div class="transcript">{{ t }}</div>
{% endif %}
{% when None %}
{% if transcribe_busy %}
<div class="pending">Transkription läuft…</div>
{% else %}
<div class="pending">Transkription ausstehend.</div>
{% endif %}
{% endmatch %}
{% endif %}
</div>
{% endfor %}
</body>
</html>