bifroest/app/templates/approval.html
2026-10-27 08:34:17 +01:00

92 lines
5.8 KiB
HTML

{% extends "base.html" %}
{% block title %}Freigabe #{{ a.id }}{% endblock %}
{% block content %}
{% set pre = a.preconditions %}
<div class="stack stack-sm">
<h1>{{ a.arguments.get('title') or a.tool }}</h1>
<p class="cluster text-sm">
<span class="badge {{ 'badge-warning' if a.status == 'pending' else 'badge-success' if a.status == 'executed' else 'badge-error' if a.status in ('failed', 'unknown') else 'badge-default' }}">{{ labels[a.status] }}</span>
<span class="text-muted">Antrag #{{ a.id }} · {{ users.get(a.requested_by) }} · {{ a.requested_at.strftime('%d.%m.%Y %H:%M') }}</span>
</p>
</div>
{% include "_flash.html" %}
<section class="card"><div class="card-body">
<dl class="aiw-facts text-sm">
<dt>Workspace</dt><dd>{{ workspaces.get(a.workspace_id) }}</dd>
<dt>Quelle</dt><dd>{{ sources.get(a.source_id, 'entfernt') }}</dd>
<dt>Tool</dt><dd><code>{{ a.tool }}</code> · Risikoklasse <code>{{ a.risk_class }}</code> · {{ a.reason }}</dd>
<dt>Entscheidet</dt><dd>{{ 'globaler Approver' if a.approval_level == 'global_approver' else 'Workspace-Admin oder Freigeber' }}{% if a.four_eyes %} · <span class="badge badge-info">Vier-Augen-Prinzip</span>{% endif %}</dd>
{% if pre.get('base') %}
<dt>Ziel</dt><dd>Branch <code>{{ pre.branch }}</code> → Pull Request nach <code>{{ pre.target }}</code></dd>
<dt>Vorbedingung</dt><dd>Basis-Commit <code>{{ pre.base[:12] }}</code>; bei der Ausführung wird geprüft, ob sich die Änderung noch konfliktfrei auf den aktuellen Stand von <code>{{ pre.target }}</code> anwenden lässt.</dd>
{% endif %}
{% if a.status == 'pending' %}<dt>Läuft ab</dt><dd>{{ a.expires_at.strftime('%d.%m.%Y %H:%M') }}</dd>{% endif %}
{% if a.decided_at and a.status != 'pending' %}
<dt>Entschieden</dt><dd>{{ users.get(a.decided_by, 'automatisch') }} · {{ a.decided_at.strftime('%d.%m.%Y %H:%M') }}{% if a.self_approved %} · <span class="badge badge-warning">Selbstfreigabe</span>{% endif %}</dd>
{% endif %}
{% if a.comment %}<dt>Kommentar</dt><dd>{{ a.comment }}</dd>{% endif %}
<dt>Snapshot</dt><dd><code class="text-xs">{{ a.hash[:16] }}</code></dd>
</dl>
</div></section>
{% if a.result %}
<section class="card"><div class="card-body stack stack-sm">
<h2>Ergebnis</h2>
<p>{{ a.result.get('output') }}</p>
{% set pr = a.result.get('pull_request') %}
{% if pr and pr.url and pr.url.startswith('https://') %}<p><a href="{{ pr.url }}" rel="noopener noreferrer" target="_blank">Pull Request #{{ pr.id }} öffnen</a></p>{% endif %}
</div></section>
{% endif %}
<section class="card"><div class="card-body stack stack-sm">
{% if pre.get('base') %}
<h2>Änderung</h2>
<p class="text-sm text-muted">Aus den echten Parametern erzeugt: genau dieser Diff wird nach der Freigabe gepusht.</p>
{% else %}
<h2>Aufruf</h2>
<p class="text-sm text-muted">Geprüfte Argumente: genau so wird {% if pre.get('server') %}der Aufruf nach der Freigabe an <code>{{ pre.get('server') }}</code> gesendet{% else %}<code>{{ a.tool }}</code> nach der Freigabe ausgeführt; das Ergebnis erhält die KI{% endif %}.</p>
{% endif %}
<pre class="aiw-diff text-xs">{% for line in a.preview.splitlines() %}<span class="{{ 'aiw-diff-add' if line.startswith('+') and not line.startswith('+++') else 'aiw-diff-del' if line.startswith('-') and not line.startswith('---') else 'aiw-diff-hunk' if line.startswith('@@') else '' }}">{{ line }}</span>
{% endfor %}</pre>
{% if a.model_summary %}
<div class="aiw-model-note">
<p class="text-xs text-muted">Beschreibung der KI (nicht geprüft, maßgeblich ist der Diff):</p>
<p class="text-sm aiw-message">{{ a.model_summary }}</p>
</div>
{% endif %}
</div></section>
{% if a.status == 'pending' and may_decide %}
<section class="card"><div class="card-body stack stack-sm">
<h2>Entscheiden</h2>
{% if a.requested_by == p.user_id %}<p class="text-sm text-muted">Du hast diese Aktion selbst ausgelöst; eine Freigabe wird im Audit als Selbstfreigabe vermerkt.</p>{% endif %}
<form method="post" action="/approvals/{{ a.id }}/decide" class="stack stack-sm">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<div class="form-group">
<label for="comment">Kommentar</label>
<textarea id="comment" name="comment" maxlength="2000" rows="2" placeholder="Bei Ablehnung Pflicht; die KI erhält den Kommentar."></textarea>
</div>
<div class="cluster">
<button class="btn btn-primary" type="submit" name="decision" value="approve" data-confirm="Aufruf freigeben? Danach wird er genau so ausgeführt.">Freigeben</button>
<button class="btn btn-secondary" type="submit" name="decision" value="reject">Ablehnen</button>
</div>
</form>
</div></section>
{% elif a.status == 'pending' %}
<p class="text-muted">Wartet auf die Entscheidung {{ 'eines globalen Approvers' if a.approval_level == 'global_approver' else 'eines Workspace-Admins oder Freigebers' }}.{% if blocked and (may_approve or a.approval_level == 'global_approver') %} {{ blocked }}{% endif %}</p>
{% elif a.status == 'expired' %}
<section class="card"><div class="card-body stack stack-sm">
{% if replaced_by %}
<p>Erneut beantragt als <a href="/approvals/{{ replaced_by }}">Antrag #{{ replaced_by }}</a>.</p>
{% elif may_approve or a.requested_by == p.user_id %}
<p class="text-sm">Abgelaufen ohne Entscheidung. Der Vorschlag kann erneut beantragt werden; Basis-Commit und Diff werden dabei gegen den aktuellen Stand neu gebaut.</p>
<form method="post" action="/approvals/{{ a.id }}/rerequest">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<button class="btn btn-secondary" type="submit">Erneut beantragen</button>
</form>
{% endif %}
</div></section>
{% endif %}
{% if run and run.chat_session_id %}<p class="text-sm"><a href="/chats/{{ run.chat_session_id }}">Zum Chat</a></p>{% endif %}
{% endblock %}