munin/templates/keys.html
2026-10-19 10:13:56 +02:00

161 lines
6.5 KiB
HTML

{{template "header" .}}
<div class="page-header">
<div class="page-header-title">
<h1>Zugänge {{template "helplink" "zugaenge"}}</h1>
<span class="help">Ein eigener Key je Mensch und je Agent. Nur so steht im
Protokoll, wer etwas geschrieben hat — und ein einzelner Zugang lässt
sich zurückziehen, ohne alle anderen zu stören.</span>
</div>
</div>
{{if .Data.NewKey}}
<div class="card key-display">
<strong>Key für „{{.Data.NewKeyName}}“</strong>
<code class="key-value">{{.Data.NewKey}}</code>
<span class="help">Wird nur jetzt angezeigt — gespeichert ist nur eine Prüfsumme.
Geht der Key verloren, muss ein neuer erzeugt werden.</span>
{{/* Drei Clients, drei Schreibweisen für denselben Endpunkt. Der Block steht
hier ausgeschrieben, weil der Klartext-Key nur auf dieser Seite ein
einziges Mal zu sehen ist — wer ihn erst aus dem README zusammensetzen
müsste, hätte ihn dann schon nicht mehr. */}}
<details class="key-instructions">
<summary>MCP-Client einrichten</summary>
<strong>Claude Code</strong>
<pre><code>claude mcp add --transport http munin-{{.Project.Slug}} {{.Data.ServerURL}}/mcp \
--header "Authorization: Bearer {{.Data.NewKey}}"</code></pre>
<strong>Cline</strong> <span class="help">(<code>cline_mcp_settings.json</code>)</span>
<pre><code>{
"mcpServers": {
"munin-{{.Project.Slug}}": {
"type": "streamableHttp",
"url": "{{.Data.ServerURL}}/mcp",
"headers": { "Authorization": "Bearer {{.Data.NewKey}}" },
"autoApprove": ["recall", "get", "list", "related"]
}
}
}</code></pre>
<strong>OpenCode</strong> <span class="help">(<code>opencode.json</code>)</span>
<pre><code>{
"mcp": {
"munin-{{.Project.Slug}}": {
"type": "remote",
"url": "{{.Data.ServerURL}}/mcp",
"enabled": true,
"headers": { "Authorization": "Bearer {{.Data.NewKey}}" }
}
}
}</code></pre>
</details>
</div>
{{end}}
{{if .Data.Unused}}
<div class="message error">
{{if eq .Data.Unused 1}}Ein aktiver Zugang wurde{{else}}{{.Data.Unused}} aktive Zugänge wurden{{end}}
seit über 90 Tagen nicht benutzt. Jeder davon ist ein offener Zugang zu diesem
Projekt. Wenn niemand mehr weiß, wofür ein Key da ist, ist Zurückziehen meist
die richtige Wahl — ein neuer ist schnell angelegt.
</div>
{{end}}
{{if .Data.ExpiringSoon}}
<div class="message hint">
{{if eq .Data.ExpiringSoon 1}}Ein Zugang läuft{{else}}{{.Data.ExpiringSoon}} Zugänge laufen{{end}}
in den nächsten 14 Tagen ab. Wird er weiter gebraucht: einen neuen anlegen und in den
Clients austauschen, bevor der alte nicht mehr gilt.
</div>
{{end}}
{{if .Data.Keys}}
<div class="table-wrap">
<table class="styled-table">
<thead>
<tr>
<th>Name</th>
<th>Rolle</th>
<th>Angelegt</th>
<th>Zuletzt benutzt</th>
<th>Gültig bis</th>
<th>Status</th>
<th class="narrow right"></th>
</tr>
</thead>
<tbody>
{{$csrf := .CSRFToken}}{{$slug := .Project.Slug}}{{$jetzt := .Data.Now}}
{{range .Data.Keys}}
<tr {{if not (.Valid $jetzt)}}class="muted"{{end}}>
<td>{{.Name}}</td>
<td><code>{{.Role}}</code></td>
<td>{{datetime .CreatedAt}}</td>
<td>{{if .LastUsedAt.IsZero}}<span class="help">nie</span>{{else}}{{datetime .LastUsedAt}}{{end}}</td>
<td>{{if .ExpiresAt.IsZero}}<span class="help">unbegrenzt</span>{{else}}{{lastDay .ExpiresAt}}{{end}}</td>
<td>
{{if .Revoked}}zurückgezogen{{else if .Expired $jetzt}}abgelaufen{{else}}aktiv{{end}}
{{if .ExpiresSoon $jetzt}}<span class="badge warning" title="Läuft in den nächsten 14 Tagen ab">läuft bald ab</span>{{end}}
{{if .Unused $jetzt}}<span class="badge warning" title="Seit über 90 Tagen nicht benutzt">ungenutzt</span>{{end}}
</td>
<td class="narrow right">
{{if .Valid $jetzt}}
<form method="POST" action="/p/{{$slug}}/keys/revoke" class="row"
data-confirm="Key „{{.Name}}“ zurückziehen? Alle Clients, die ihn benutzen, verlieren sofort den Zugang.">
<input type="hidden" name="csrf_token" value="{{$csrf}}">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="warning">Zurückziehen</button>
</form>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="empty">
<p>Noch kein Zugang angelegt.</p>
<p class="help">Ohne Key kann sich niemand mit diesem Projekt verbinden.</p>
</div>
{{end}}
<section class="section">
<h2>Neuer Zugang</h2>
<div class="card">
<form method="POST" action="/p/{{.Project.Slug}}/keys/create" class="form">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<div class="field medium">
<label for="name">Name</label>
<span class="help">Wer oder was benutzt diesen Key?</span>
<input type="text" id="name" name="name" required placeholder="z.B. tom-cline oder ci-pipeline">
</div>
<div class="field medium">
<label for="role">Rolle</label>
<select id="role" name="role">
<option value="read">read — nur lesen und suchen</option>
<option value="write" selected>write — zusätzlich schreiben</option>
<option value="admin">admin — zusätzlich Zugänge verwalten</option>
</select>
</div>
<div class="field short">
<label for="expires">Gültig bis</label>
<span class="help">Optional. Leer = unbegrenzt. Gilt bis einschließlich dieses Tages,
danach wie zurückgezogen. Sinnvoll für Zugänge auf Zeit, etwa für ein Projekt oder
eine Person, die nur eine Weile dabei ist.</span>
<input type="date" id="expires" name="expires" min="{{.Data.Today}}">
</div>
<div class="form-actions">
<button type="submit">Key erzeugen</button>
</div>
</form>
</div>
</section>
{{template "footer" .}}