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

110 lines
8.8 KiB
HTML
Raw 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 %}{{ server.label }}{% endblock %}
{% block content %}
<div class="stack stack-sm">
{% set driver = server.role == 'database' %}
<h1>{{ server.label }} <code class="text-base">{{ server.name }}</code></h1>
{% if driver %}<p class="text-sm text-muted">Datenbank-Treiber · genutzt von {{ uses }} Datenbank{{ 'en' if uses != 1 }}. Die Einstufung der Tools hier gilt für alle Datenbanken dieses Typs.</p>{% endif %}
{% if server.server_info %}<p class="text-sm text-muted">{{ server.server_info.get('name') }} {{ server.server_info.get('version') }}{% if server.role == 'memory' %} · Gedächtnis{% endif %}</p>
{% if server.server_info.get('instructions') %}<details class="text-sm"><summary>Anleitung des Servers (geht in den System-Prompt)</summary><pre class="text-xs aiw-message">{{ server.server_info.instructions }}</pre></details>{% endif %}{% endif %}
</div>
{% include "_flash.html" %}
{% for n in notes %}<div class="alert alert-warning" role="status"><div class="alert-content"><p>Übersprungen: {{ n }}</p></div></div>{% endfor %}
{% if server.status == 'error' and not error %}<div class="alert alert-danger" role="alert"><div class="alert-content"><p>{{ server.last_error }}</p></div></div>{% endif %}
<section class="card"><div class="card-body stack stack-sm">
<div class="cluster">
<h2 class="flex-1">Tools</h2>
<form method="post" action="/admin/mcp/{{ server.id }}/discover">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<button class="btn btn-secondary btn-sm" type="submit">Tools abrufen</button>
</form>
</div>
{% if server.discovered_at %}<p class="text-xs text-muted">Zuletzt abgerufen {{ server.discovered_at.strftime('%d.%m.%Y %H:%M') }}</p>{% endif %}
{% if tools %}
<div class="table-wrap"><table>
<thead><tr><th>Tool</th><th>Hinweise des Servers</th><th>Einstufung</th></tr></thead>
<tbody>
{% for t in tools %}{% set d = definitions.get(t.name) %}
<tr>
<td><code>{{ t.name }}</code><p class="text-xs text-muted">{{ t.description }}</p></td>
<td class="text-xs">{% for k, v in t.annotations.items() %}<code>{{ k }}={{ v }}</code> {% else %}–{% endfor %}
<p class="text-muted">Vorschlag: {{ suggest(t.annotations) }} (nicht geprüft)</p></td>
<td>{% if d and d.classified %}<span class="badge badge-success">{{ d.risk_class }} · {{ d.decision }}</span>{% elif d and d.classified_hash %}<span class="badge badge-warning">geändert</span>{% else %}<span class="badge badge-default">nicht eingestuft</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table></div>
<p class="text-sm"><a href="/admin/tools">Zur Einstufung unter „Tools“</a></p>
{% if server.role == 'memory' %}
<div class="alert alert-info" role="status"><div class="alert-content stack stack-sm">
<p>Empfehlung für Munin (Konzept 7.6): {% for name, (risk, decision) in memory_defaults.items() %}<code>{{ name }}</code> {{ risk }}/{{ decision }}{% if not loop.last %}, {% endif %}{% endfor %}. <code>forget</code> bekommt die KI damit nie; Einträge löschen nur Menschen im Workspace unter „Gedächtnis“.</p>
<form method="post" action="/admin/mcp/{{ server.id }}/memory-defaults" data-confirm="Die gefundenen Munin-Tools nach der Empfehlung einstufen?">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<button class="btn btn-secondary btn-sm" type="submit">Empfehlung übernehmen</button>
</form>
</div></div>
{% endif %}
{% if server.role == 'documents' %}
{% set cfg = server.config or {} %}
<div class="stack stack-sm aiw-doc-config">
<h3 class="text-base">Dokumenten-Auslese</h3>
<p class="text-sm text-muted">Liest Office, PDF und Bilder aus abgeglichenen Ordnern aus. Bifröst schickt den Dateiinhalt als Base64 an das gewählte Tool und speichert den zurückgegebenen Text. Gilt für alle Workspaces.{% if not cfg.get('tool') %} <strong>Noch nicht festgelegt.</strong>{% endif %}</p>
<form method="post" action="/admin/mcp/{{ server.id }}/documents" class="stack stack-sm">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<div class="form-group"><label for="doc-tool">Tool</label>
<select id="doc-tool" name="tool">{% for t in tools %}<option value="{{ t.remote_name }}" {% if cfg.get('tool') == t.remote_name %}selected{% endif %}>{{ t.remote_name }}</option>{% endfor %}</select></div>
{% set params = [] %}{% for t in tools %}{% for name in (t.input_schema or {}).get('properties', {}) %}{% if name not in params %}{% set _ = params.append(name) %}{% endif %}{% endfor %}{% endfor %}
<div class="form-group"><label for="doc-content">Parameter für den Inhalt (Base64)</label>
<select id="doc-content" name="content">{% for name in params %}<option value="{{ name }}" {% if cfg.get('content') == name %}selected{% endif %}>{{ name }}</option>{% endfor %}</select></div>
<div class="form-group"><label for="doc-filename">Parameter für den Dateinamen (optional)</label>
<select id="doc-filename" name="filename"><option value="">– keiner –</option>{% for name in params %}<option value="{{ name }}" {% if cfg.get('filename') == name %}selected{% endif %}>{{ name }}</option>{% endfor %}</select>
<p class="form-hint">Hilft dem Dienst, das Format zu erkennen (z. B. <code>bericht.docx</code>).</p></div>
<div><button class="btn btn-secondary btn-sm" type="submit">Speichern</button></div>
</form>
</div>
{% endif %}
{% else %}<p class="text-muted">Noch keine Tools abgerufen.</p>{% endif %}
</div></section>
{% if server.workspace_id %}
<section class="card"><div class="card-body stack stack-sm">
<h2>Verbindung eines Workspace</h2>
<p class="text-sm">Endpunkt <code>{{ server.url }}</code>{% if server.template_id %} (Datenbank über einen Treiber; ihre Tools stuft man am Treiber ein){% endif %} · Freigabe: {{ {'approved': 'freigegeben', 'pending': 'wartet', 'rejected': 'abgelehnt'}[server.approval] }}{% if server.approval_note %} ({{ server.approval_note }}){% endif %}{% if server.header_names %} · Header: {{ server.header_names|join(', ') }}{% endif %}</p>
<p class="text-sm text-muted">Bezeichnung, Token und Header pflegt der Workspace-Admin. Löschen nimmt dem Workspace die Verbindung.</p>
<form method="post" action="/admin/mcp/{{ server.id }}/delete" data-confirm="Verbindung löschen?">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<button class="btn btn-ghost btn-sm" type="submit">Verbindung löschen</button>
</form>
</div></section>
{% else %}
<section class="card"><div class="card-body stack stack-sm">
<h2>Einstellungen</h2>
<form method="post" action="/admin/mcp/{{ server.id }}" class="stack stack-sm">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<div class="form-group"><label for="label">Bezeichnung</label><input id="label" name="label" maxlength="200" value="{{ server.label }}"></div>
<div class="form-group"><label for="url">URL</label><input id="url" name="url" type="url" required value="{{ server.url }}"></div>
<div class="form-group">
<label for="token">{{ 'Schlüssel des Dienstes' if driver else 'Token' }} {% if server.secret_hint %}(gesetzt: …{{ server.secret_hint }}){% endif %}</label>
<input id="token" name="token" type="password" autocomplete="off" placeholder="leer lassen = unverändert">
{% if server.secret_hint %}<label class="checkbox"><input type="checkbox" name="remove_token" value="1"> Token entfernen</label>{% endif %}
</div>
{% if driver %}
<input type="hidden" name="role" value="database">
{% else %}
<div class="form-group">
<label for="role">Art</label>
<select id="role" name="role">{% for k, v in roles.items() %}<option value="{{ k }}" {% if (server.role) == k %}selected{% endif %}>{{ v }}</option>{% endfor %}</select>
<p class="form-hint">Ein Gedächtnis-Server (z. B. Munin) bekommt im Workspace eine Ansicht der Einträge; löschen können dort nur Menschen.</p>
</div>
{% endif %}
<label class="checkbox"><input type="checkbox" name="active" value="1" {% if server.active %}checked{% endif %}> aktiv ({{ 'inaktive Treiber: ihre Datenbanken bieten keine Tools an' if driver else 'inaktive Server bieten keine Tools an' }})</label>
<div><button class="btn btn-primary" type="submit">Speichern</button></div>
</form>
<form method="post" action="/admin/mcp/{{ server.id }}/delete" data-confirm="{{ 'Treiber löschen?' if driver else 'Server löschen? Workspaces verlieren den Zugriff auf seine Tools.' }}">
<input type="hidden" name="_csrf" value="{{ p.csrf }}">
<button class="btn btn-ghost btn-sm" type="submit" {% if driver and uses %}disabled title="Erst die Datenbanken entfernen, die ihn nutzen"{% endif %}>{{ 'Treiber löschen' if driver else 'Server löschen' }}</button>
</form>
</div></section>
{% endif %}
{% endblock %}