213 lines
14 KiB
HTML
213 lines
14 KiB
HTML
{% 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 %}
|