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

213 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Repository · {{ workspace.name }}{% endblock %}
{% block content %}
{% set wbase = '/workspaces/' ~ workspace.id %}
{% set weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'] %}
{% set q = '?source=' ~ source.name|urlencode %}
<div class="stack stack-sm">
<div class="cluster aiw-repo-head">
<h1 class="flex-1">Repository</h1>
<form method="post" action="{{ wbase }}/repo/refresh" class="cluster">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="tab" value="{{ tab }}">
{% if age is not none %}<span class="text-xs text-muted">Stand vor {{ (age // 60)|int }} Min.</span>{% endif %}
<button class="btn btn-ghost btn-sm" type="submit">Aktualisieren</button>
</form>
</div>
<p class="text-sm text-muted"><code>{{ source.config.url }}</code> · Ziel-Branch <code>{{ source.config.branch }}</code>{% if head %} · Stand <code>{{ head[:10] }}</code>{% endif %}</p>
{% if sources|length > 1 %}
<nav aria-label="Quelle"><ul class="tab-list">{% for s in sources %}<li><a href="{{ wbase }}/repo?source={{ s.name|urlencode }}&tab={{ tab }}" {% if s.id == source.id %}class="active" aria-current="page"{% endif %}>{{ s.name }}</a></li>{% endfor %}</ul></nav>
{% endif %}
</div>
{% include "_flash.html" %}
<nav aria-label="Bereiche"><ul class="tab-list">
{% for key, label in tabs.items() %}<li><a href="{{ wbase }}/repo{{ q }}&tab={{ key }}" {% if tab == key %}class="active" aria-current="page"{% endif %}>{{ label }}</a></li>{% endfor %}
</ul></nav>
{% if tab == 'new' and head %}
<section class="card">
<div class="card-body stack stack-sm">
{% if news.since is none %}
<p class="text-sm">Dein erster Besuch hier. Ab jetzt zeigt Bifröst oben, was seit deinem letzten Besuch neu ist.</p>
{% elif news.commits %}
<div class="cluster">
<p class="flex-1"><strong>Seit deinem letzten Besuch</strong> ({{ news.since.astimezone().strftime('%d.%m.%Y %H:%M') }}): {{ news.commits }} Commit{{ 's' if news.commits != 1 }}, {{ news.files }} Datei{{ 'en' if news.files != 1 }} geändert.</p>
<form method="post" action="{{ wbase }}/repo/chat">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="action" value="summarize">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="start" value="{{ news.base }}"><input type="hidden" name="end" value="{{ head }}">
<button class="btn btn-primary btn-sm" type="submit">Zusammenfassen lassen</button>
</form>
</div>
{% else %}
<p class="text-sm text-muted">Nichts Neues seit deinem letzten Besuch ({{ news.since.astimezone().strftime('%d.%m.%Y %H:%M') }}).</p>
{% endif %}
<form method="get" action="{{ wbase }}/repo" class="cluster aiw-repo-filter">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="tab" value="new">
<select name="period" aria-label="Zeitraum" class="aiw-repo-period">{% for key, label in periods.items() %}<option value="{{ key }}" {% if key == period %}selected{% endif %}>{{ label }}</option>{% endfor %}</select>
<input name="author" value="{{ author }}" placeholder="Autor" aria-label="Autor">
<input name="path" value="{{ filter_path }}" placeholder="Pfad, z. B. src/api" aria-label="Pfad">
<button class="btn btn-secondary btn-sm" type="submit">Filtern</button>
{% if filtered %}<a class="btn btn-ghost btn-sm" href="{{ wbase }}/repo{{ q }}&tab=new&period={{ period }}">Zurücksetzen</a>{% endif %}
</form>
</div>
</section>
<section class="card">
<div class="card-header cluster"><h2 class="text-lg flex-1">Commits auf {{ source.config.branch }}{% if count %} <span class="badge badge-default">{{ count }}</span>{% endif %}</h2>
{% if range_start %}
<form method="post" action="{{ wbase }}/repo/chat">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="action" value="summarize">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="start" value="{{ range_start }}"><input type="hidden" name="end" value="{{ head }}">
<button class="btn btn-ghost btn-sm" type="submit">Zeitraum zusammenfassen lassen</button>
</form>
{% endif %}
</div>
<div class="card-body stack">
{% for d in days %}
<div class="stack stack-sm">
<h3 class="text-sm text-muted">{{ weekdays[d.day.weekday()] }}, {{ d.day.strftime('%d.%m.%Y') }}</h3>
<ul class="aiw-commit-list">
{% for c in d.commits %}
<li class="aiw-commit">
<div class="aiw-commit-main">
<a href="{{ wbase }}/repo/commits/{{ c.sha }}{{ q }}" class="aiw-commit-subject">{% if c.new %}<span class="badge badge-info">neu</span> {% endif %}{{ c.subject }}</a>
<span class="text-xs text-muted">{{ c.author }} · {{ c.date.astimezone().strftime('%H:%M') }} · <code>{{ c.short }}</code>{% if c.files %} · {{ c.files }} Datei{{ 'en' if c.files != 1 }} <span class="aiw-diff-add">+{{ c.insertions }}</span> <span class="aiw-diff-del">−{{ c.deletions }}</span>{% endif %}</span>
</div>
<form method="post" action="{{ wbase }}/repo/chat">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="action" value="commit">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="sha" value="{{ c.sha }}">
<button class="btn btn-ghost btn-xs" type="submit" title="Neuen Chat starten, der diesen Commit erklärt">Im Chat erklären</button>
</form>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<p class="text-muted text-sm">Keine Commits {% if filtered %}für diesen Filter{% else %}in diesem Zeitraum{% endif %}.</p>
{% endfor %}
</div>
</section>
{% endif %}
{% if tab == 'branches' and head %}
<section class="card">
<div class="card-body stack stack-sm">
<p class="text-sm text-muted">Alle Branches auf dem Git-Server, zuletzt geänderte zuerst. Vorsprung und Rückstand beziehen sich auf <code>{{ source.config.branch }}</code>. <em>Chat auf diesem Branch</em> startet einen Chat, der den Branch liest; Änderungen der KI landen auf einem eigenen Branch <code>ai/…</code>, der davon abzweigt, und ein Pull Request geht zurück in diesen Branch.</p>
{% if branches %}
<div class="table-wrap"><table>
<thead><tr><th>Branch</th><th>Letzter Commit</th><th title="Vorsprung / Rückstand gegenüber {{ source.config.branch }}">↑ / ↓</th><th></th></tr></thead>
<tbody>
{% for b in branches %}
<tr>
<td><code>{{ b.name }}</code>{% if b.target %} <span class="badge badge-info">Ziel-Branch</span>{% endif %}
{% if b.chat_id %}<p class="text-xs"><a href="/chats/{{ b.chat_id }}">Chat: {{ b.chat_title }}</a>{% if b.pr_state %} · {% if b.pr_url %}<a href="{{ b.pr_url }}" rel="noopener noreferrer" target="_blank">PR {{ b.pr_state|lower }}</a>{% else %}PR {{ b.pr_state|lower }}{% endif %}{% endif %}</p>{% elif b.name.startswith('ai/') %}<p class="text-xs text-muted">Chat-Branch ohne Chat in diesem Workspace</p>{% endif %}</td>
<td class="text-sm">{{ b.subject }}<p class="text-xs text-muted">{{ b.author }} · {{ b.date.astimezone().strftime('%d.%m.%Y %H:%M') }} · <code>{{ b.sha[:10] }}</code></p></td>
<td class="text-sm">{% if b.target %}–{% else %}<span title="{{ b.ahead }} Commits, die {{ source.config.branch }} nicht hat">↑{{ b.ahead }}</span> <span title="{{ b.behind }} Commits von {{ source.config.branch }} fehlen">↓{{ b.behind }}</span>{% endif %}</td>
<td class="cluster">
{% if not b.target and b.ahead %}<a class="btn btn-ghost btn-xs" href="{{ wbase }}/repo/compare{{ q }}&base={{ source.config.branch|urlencode }}&head={{ b.name|urlencode }}">Diff</a>{% endif %}
<form method="post" action="{{ wbase }}/repo/chat">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="action" value="branch">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="branch" value="{{ b.name }}">
<button class="btn btn-ghost btn-xs" type="submit">Chat auf diesem Branch</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table></div>
{% elif not error %}<p class="text-muted text-sm">Keine Branches gefunden.</p>{% endif %}
</div>
</section>
{% endif %}
{% if tab == 'prs' and head %}
<section class="card">
<div class="card-body stack stack-sm">
{% if prs is none and not error %}
<p class="text-sm text-muted">Für diese Quelle ist kein Pull-Request-Anbieter eingerichtet. Workspace-Admins wählen ihn unter <em>Daten → Quelle bearbeiten</em> (<em>Pull Requests: Bitbucket Data Center</em>).</p>
{% elif prs %}
<p class="text-sm text-muted">Offene Pull Requests im Git-Server, zuletzt geänderte zuerst. <em>Diff</em> zeigt die Änderungen wie im Pull Request; <em>Im Chat reviewen</em> startet einen Chat, der den Branch liest und ein Review schreibt, ohne etwas zu ändern.</p>
<ul class="aiw-commit-list">
{% for row in prs %}{% set pr = row.pr %}
<li class="aiw-commit">
<div class="aiw-commit-main">
<span class="aiw-commit-subject">{% if pr.url %}<a href="{{ pr.url }}" rel="noopener noreferrer" target="_blank">#{{ pr.id }} {{ pr.title }}</a>{% else %}#{{ pr.id }} {{ pr.title }}{% endif %}{% if pr.draft %} <span class="badge badge-default">Entwurf</span>{% endif %}</span>
<span class="text-xs text-muted">{{ pr.author }} · <code>{{ pr.source }}</code> → <code>{{ pr.target }}</code>{% if pr.updated %} · {{ pr.updated.astimezone().strftime('%d.%m.%Y %H:%M') }}{% endif %}{% if pr.comments %} · {{ pr.comments }} Kommentar{{ 'e' if pr.comments != 1 }}{% endif %}</span>
{% if pr.reviewers %}<span class="text-xs">{% for r in pr.reviewers %}{{ r.name }}: {{ {'APPROVED': '✓ freigegeben', 'NEEDS_WORK': '✗ Nacharbeit', 'UNAPPROVED': 'offen'}.get(r.status, r.status|lower) }}{{ ', ' if not loop.last }}{% endfor %}</span>{% endif %}
{% if row.chat_id %}<span class="text-xs"><a href="/chats/{{ row.chat_id }}">Chat: {{ row.chat_title }}</a></span>{% endif %}
</div>
<a class="btn btn-ghost btn-xs" href="{{ wbase }}/repo/compare{{ q }}&base={{ pr.target|urlencode }}&head={{ pr.source|urlencode }}&pr={{ pr.id }}">Diff</a>
<form method="post" action="{{ wbase }}/repo/chat">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="action" value="review">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="branch" value="{{ pr.source }}">
<input type="hidden" name="base" value="{{ pr.target }}"><input type="hidden" name="pr" value="{{ pr.id }}">
<button class="btn btn-ghost btn-xs" type="submit">Im Chat reviewen</button>
</form>
</li>
{% endfor %}
</ul>
{% elif not error %}<p class="text-sm text-muted">Keine offenen Pull Requests.</p>{% endif %}
</div>
</section>
{% endif %}
{% if tab == 'files' and head %}
{% set fq = q ~ '&tab=files&ref=' ~ (ref|default(''))|urlencode %}
<section class="card">
<div class="card-header cluster">
<nav class="breadcrumbs flex-1" aria-label="Verzeichnis"><ol>
<li><a href="{{ wbase }}/repo{{ fq }}">{{ source.name }}</a></li>
{% for name, sub in crumbs %}<li>{% if loop.last %}<span aria-current="page">{{ name }}</span>{% else %}<a href="{{ wbase }}/repo{{ fq }}&at={{ sub|urlencode }}">{{ name }}</a>{% endif %}</li>{% endfor %}
</ol></nav>
{% if refs %}
<form method="get" action="{{ wbase }}/repo" class="cluster aiw-repo-ref">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="tab" value="files"><input type="hidden" name="at" value="{{ at }}">
<select name="ref" aria-label="Branch" class="aiw-repo-period">{% for r in refs %}<option value="{{ r }}" {% if r == ref %}selected{% endif %}>{{ r }}</option>{% endfor %}</select>
<button class="btn btn-secondary btn-sm" type="submit">Wechseln</button>
</form>
{% endif %}
</div>
<div class="card-body stack stack-sm">
{% if is_file and file %}
<div class="cluster">
<p class="text-sm text-muted flex-1">{{ file.size|filesizeformat }}{% if file.lines %} · {{ file.lines|length }}{% if file.truncated %}+{% endif %} Zeilen{% endif %} · Branch <code>{{ ref }}</code></p>
<form method="post" action="{{ wbase }}/repo/chat">
<input type="hidden" name="_csrf" value="{{ p.csrf }}"><input type="hidden" name="action" value="file">
<input type="hidden" name="source" value="{{ source.name }}"><input type="hidden" name="path" value="{{ file.path }}"><input type="hidden" name="branch" value="{{ ref }}">
<button class="btn btn-primary btn-sm" type="submit">Datei erklären</button>
</form>
</div>
{% if file.binary %}<p class="text-sm text-muted">Binärdatei; keine Vorschau.</p>
{% elif file.too_large %}<p class="text-sm text-muted">{{ file.too_large }}</p>
{% else %}
<ol class="aiw-code text-xs">{% for line in file.lines %}<li>{{ line }}</li>{% endfor %}</ol>
{% if file.truncated %}<p class="text-xs text-muted">Nur die ersten {{ file.lines|length }} Zeilen.</p>{% endif %}
{% endif %}
{% if file.history %}
<h3 class="text-sm">Verlauf</h3>
<ul class="aiw-commit-list">
{% for c in file.history %}
<li class="aiw-commit"><div class="aiw-commit-main">
<a href="{{ wbase }}/repo/commits/{{ c.sha }}{{ q }}" class="aiw-commit-subject">{{ c.subject }}</a>
<span class="text-xs text-muted">{{ c.author }} · {{ c.date.astimezone().strftime('%d.%m.%Y %H:%M') }} · <code>{{ c.short }}</code></span>
</div></li>
{% endfor %}
</ul>
{% endif %}
{% else %}
<ul class="aiw-tree">
{% for e in entries %}
<li class="cluster">
{% if e.kind == 'tree' %}<a class="flex-1" href="{{ wbase }}/repo{{ fq }}&at={{ e.path|urlencode }}">📁 {{ e.name }}/</a>
{% elif e.kind == 'blob' %}<a class="flex-1" href="{{ wbase }}/repo{{ fq }}&at={{ e.path|urlencode }}">{{ e.name }}</a><span class="text-xs text-muted">{{ e.size|filesizeformat }}</span>
{% else %}<span class="flex-1 text-muted">{{ e.name }} (Submodul)</span>{% endif %}
</li>
{% else %}
{% if not error %}<li class="text-sm text-muted">Leeres Verzeichnis.</li>{% endif %}
{% endfor %}
</ul>
{% endif %}
</div>
</section>
{% endif %}
{% endblock %}