110 lines
8.8 KiB
HTML
110 lines
8.8 KiB
HTML
{% 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 %}
|