Files
ssh-jumphost/templates/admin.html

761 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jumphost Admin</title>
<link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/js/vendor/xterm.css">
</head>
<body>
<div class="topbar">
<div class="brand">Jumphost &middot; Admin</div>
<div>
<span class="hint" id="whoami"></span>
<a href="/dashboard">Zum Dashboard</a>
<a href="/docs" target="_blank" rel="noopener">API-Dokumentation</a>
<button id="logout-btn">Abmelden</button>
</div>
</div>
<div class="container wide">
<div id="banner-box"></div>
<div class="tabs" id="tabs">
<button type="button" class="tab-btn active" data-tab="users">Benutzer</button>
<button type="button" class="tab-btn" data-tab="groups">Benutzergruppen</button>
<button type="button" class="tab-btn" data-tab="hostgroups">Hostgruppen</button>
<button type="button" class="tab-btn" data-tab="servers">Server</button>
<button type="button" class="tab-btn" data-tab="credentials">Zugangsdaten</button>
<button type="button" class="tab-btn" data-tab="roles">Rollen</button>
<button type="button" class="tab-btn" data-tab="tokens">API-Tokens</button>
<button type="button" class="tab-btn hidden" data-tab="tenants" id="tenants-tab-btn">Mandanten</button>
<button type="button" class="tab-btn hidden" data-tab="sessions" id="sessions-tab-btn">Sessions</button>
<button type="button" class="tab-btn hidden" data-tab="connlog" id="connlog-tab-btn">Verbindungslog</button>
<button type="button" class="tab-btn" data-tab="audit">Audit-Log</button>
</div>
<!-- ================= Benutzer ================= -->
<div class="tab-panel" id="tab-users">
<div class="panel">
<h2>Neuen Benutzer anlegen</h2>
<form id="user-create-form" class="form-grid">
<div>
<label for="uc-username">Benutzername</label>
<input type="text" id="uc-username" required minlength="3" maxlength="64">
</div>
<div>
<label for="uc-password">Initiales Passwort (min. 12 Zeichen)</label>
<input type="text" id="uc-password" required minlength="12" maxlength="256">
</div>
<div class="checkbox-row" id="uc-is-admin-row">
<input type="checkbox" id="uc-is-admin">
<label for="uc-is-admin">Globaler Super-Admin</label>
</div>
<button type="submit">Anlegen</button>
</form>
</div>
<div class="panel">
<h2>Benutzer</h2>
<div class="table-wrap">
<table class="data-table" id="users-table">
<thead>
<tr>
<th>ID</th><th>Benutzername</th><th>Admin</th><th>Aktiv</th>
<th>TOTP</th><th>Mandant</th><th>Erstellt</th><th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="user-edit-panel">
<h2>Benutzer bearbeiten: <span id="user-edit-name"></span></h2>
<form id="user-edit-form" class="form-grid">
<div class="checkbox-row" id="ue-is-admin-row">
<input type="checkbox" id="ue-is-admin">
<label for="ue-is-admin">Globaler Super-Admin</label>
</div>
<div class="checkbox-row">
<input type="checkbox" id="ue-is-active">
<label for="ue-is-active">Aktiv</label>
</div>
<div>
<label for="ue-new-password">Neues Passwort setzen (optional, min. 12 Zeichen)</label>
<input type="text" id="ue-new-password" minlength="12" maxlength="256">
</div>
<button type="submit">Speichern</button>
</form>
</div>
</div>
<!-- ================= Benutzergruppen ================= -->
<div class="tab-panel hidden" id="tab-groups">
<div class="panel">
<h2>Neue Benutzergruppe anlegen</h2>
<form id="group-create-form" class="form-grid">
<div>
<label for="gc-name">Name</label>
<input type="text" id="gc-name" required maxlength="128">
</div>
<div>
<label for="gc-description">Beschreibung (optional)</label>
<input type="text" id="gc-description" maxlength="1024">
</div>
<div id="gc-tenant-box">
<label for="gc-tenant">Mandant</label>
<select id="gc-tenant" required></select>
</div>
<button type="submit">Anlegen</button>
</form>
</div>
<div class="panel">
<h2>Benutzergruppen</h2>
<p class="hint">Rollen fuer eine Gruppe (= Verbindungen mit der ganzen Gruppe teilen) werden im Tab "Rollen" vergeben.</p>
<div class="table-wrap">
<table class="data-table" id="groups-table">
<thead><tr><th>ID</th><th>Name</th><th>Beschreibung</th><th>Mandant</th><th>Mitglieder</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="group-edit-panel">
<h2>Gruppe bearbeiten: <span id="group-edit-name"></span></h2>
<form id="group-edit-form" class="form-grid">
<div>
<label for="ge-name">Name</label>
<input type="text" id="ge-name" required maxlength="128">
</div>
<div>
<label for="ge-description">Beschreibung</label>
<input type="text" id="ge-description" maxlength="1024">
</div>
<button type="submit">Speichern</button>
</form>
</div>
<div class="panel hidden" id="group-members-panel">
<h2>Mitglieder von <span id="group-members-name"></span></h2>
<div class="table-wrap">
<table class="data-table" id="group-members-table">
<thead><tr><th>User-ID</th><th>Benutzername</th><th>Hinzugefuegt</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
<h3>Mitglied hinzufuegen</h3>
<form id="group-member-add-form" class="form-grid">
<div>
<label for="gm-user-select">Benutzer</label>
<select id="gm-user-select" required></select>
</div>
<button type="submit">Hinzufuegen</button>
</form>
</div>
</div>
<!-- ================= Hostgruppen ================= -->
<div class="tab-panel hidden" id="tab-hostgroups">
<div class="panel">
<h2>Neue Hostgruppe anlegen</h2>
<form id="hostgroup-create-form" class="form-grid">
<div>
<label for="hgc-name">Name</label>
<input type="text" id="hgc-name" required maxlength="128">
</div>
<div>
<label for="hgc-description">Beschreibung (optional)</label>
<input type="text" id="hgc-description" maxlength="1024">
</div>
<div id="hgc-tenant-box">
<label for="hgc-tenant">Mandant</label>
<select id="hgc-tenant" required></select>
</div>
<button type="submit">Anlegen</button>
</form>
<h3>Hostgruppen</h3>
<div class="table-wrap">
<table class="data-table" id="hostgroups-table">
<thead><tr><th>ID</th><th>Name</th><th>Beschreibung</th><th>Mandant</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="hostgroup-edit-panel">
<h2>Hostgruppe bearbeiten: <span id="hostgroup-edit-name"></span></h2>
<form id="hostgroup-edit-form" class="form-grid">
<div>
<label for="hge-name">Name</label>
<input type="text" id="hge-name" required maxlength="128">
</div>
<div>
<label for="hge-description">Beschreibung</label>
<input type="text" id="hge-description" maxlength="1024">
</div>
<button type="submit">Speichern</button>
</form>
</div>
</div>
<!-- ================= Server ================= -->
<div class="tab-panel hidden" id="tab-servers">
<div class="panel">
<h2>Neue Verbindung (Host) anlegen</h2>
<form id="host-create-form" class="form-grid">
<div>
<label for="hc-hostgroup">Hostgruppe</label>
<select id="hc-hostgroup" required></select>
</div>
<div>
<label for="hc-hostname">Hostname (Label)</label>
<input type="text" id="hc-hostname" required maxlength="128" pattern="[a-zA-Z0-9][a-zA-Z0-9._-]*">
</div>
<div>
<label for="hc-address">Adresse (IP/DNS)</label>
<input type="text" id="hc-address" required maxlength="255">
</div>
<div>
<label for="hc-protocol">Protokoll</label>
<select id="hc-protocol">
<option value="ssh">SSH</option>
<option value="rdp">RDP</option>
</select>
</div>
<div>
<label for="hc-port">Port</label>
<input type="text" id="hc-port" inputmode="numeric" value="22" required maxlength="5">
</div>
<div>
<label for="hc-os-type">Betriebssystem</label>
<select id="hc-os-type">
<option value="linux">Linux</option>
<option value="windows">Windows</option>
</select>
</div>
<p class="hint full-width">Der Benutzername wird nicht mehr am Server gepflegt, sondern bei den Zugangsdaten: bei SSH am SSH-Key, bei RDP/Windows zusammen mit dem Passwort (Reiter "Zugangsdaten").</p>
<div class="checkbox-row"><input type="checkbox" id="hc-clipboard" checked><label for="hc-clipboard">Zwischenablage erlaubt</label></div>
<div class="checkbox-row"><input type="checkbox" id="hc-filetransfer" checked><label for="hc-filetransfer">Dateitransfer erlaubt</label></div>
<div class="checkbox-row hidden" id="hc-nla-box"><input type="checkbox" id="hc-nla" checked><label for="hc-nla">RDP: NLA erforderlich</label></div>
<div class="checkbox-row hidden" id="hc-ignorecert-box"><input type="checkbox" id="hc-ignorecert" checked><label for="hc-ignorecert">RDP: Serverzertifikat nicht pruefen</label></div>
<p class="hint full-width hidden" id="hc-ignorecert-hint">Windows-Ziele ohne eigene PKI weisen sich mit einem selbstsignierten Zertifikat aus; guacd bricht die Verbindung sonst ab, bevor ein Bild entsteht. Nur deaktivieren, wenn das Ziel ein Zertifikat aus einer dem Jumphost bekannten CA verwendet.</p>
<button type="submit">Verbindung anlegen</button>
</form>
</div>
<div class="panel">
<h2>Vorhandene Hosts</h2>
<div class="table-wrap">
<table class="data-table" id="hosts-table">
<thead>
<tr><th>ID</th><th>Hostname</th><th>Adresse</th><th>Protokoll</th><th>OS</th><th>Hostgruppe</th><th>Mandant</th><th>Status</th><th></th></tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="host-detail-panel">
<h2>Verbindung: <span id="host-detail-name"></span></h2>
<div id="host-detail-banner"></div>
<h3>Felder bearbeiten</h3>
<form id="host-edit-form" class="form-grid">
<div>
<label for="he-hostname">Hostname (Label)</label>
<input type="text" id="he-hostname" required maxlength="128" pattern="[a-zA-Z0-9][a-zA-Z0-9._-]*">
</div>
<div>
<label for="he-address">Adresse (IP/DNS)</label>
<input type="text" id="he-address" required maxlength="255">
</div>
<div>
<label for="he-port">Port</label>
<input type="text" id="he-port" inputmode="numeric" required maxlength="5">
</div>
<p class="hint full-width" id="he-username-hint"></p>
<div class="checkbox-row"><input type="checkbox" id="he-clipboard"><label for="he-clipboard">Zwischenablage erlaubt</label></div>
<div class="checkbox-row"><input type="checkbox" id="he-filetransfer"><label for="he-filetransfer">Dateitransfer erlaubt</label></div>
<div class="checkbox-row hidden" id="he-nla-box"><input type="checkbox" id="he-nla"><label for="he-nla">RDP: NLA erforderlich</label></div>
<div class="checkbox-row hidden" id="he-ignorecert-box"><input type="checkbox" id="he-ignorecert"><label for="he-ignorecert">RDP: Serverzertifikat nicht pruefen</label></div>
<div class="checkbox-row"><input type="checkbox" id="he-active"><label for="he-active">Aktiv (verbindbar)</label></div>
<button type="submit">Speichern</button>
</form>
<div id="host-detail-ssh">
<h3>SSH-Host-Key</h3>
<p class="hint">Holt den Host-Key des Ziels EINMALIG ohne Anmeldung (reiner Key-Exchange) und speichert Fingerprint und Schluessel fuer das Pinning kuenftiger Verbindungen. Bewusste Vertrauensentscheidung, wird prominent im Audit-Log vermerkt. Ohne hinterlegten Host-Key wird jede SSH-Verbindung zu diesem Host abgelehnt.</p>
<button type="button" class="btn-secondary" id="host-discover-key-btn">Host-Key ermitteln</button>
<div id="host-key-result" class="reveal-box hidden"></div>
<h3>Zugeordnete SSH-Keys</h3>
<div class="table-wrap">
<table class="data-table" id="host-ssh-keys-table">
<thead><tr><th>ID</th><th>Label</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
<form id="host-key-map-form" class="form-grid">
<div>
<label for="hkm-key-select">SSH-Key zuordnen</label>
<select id="hkm-key-select" required></select>
</div>
<button type="submit">Zuordnen</button>
</form>
<h3>SSH-Passwort (Alternative zum Schluessel)</h3>
<p class="hint" id="ssh-pw-cred-status"></p>
<p class="hint">Wird nur verwendet, wenn diesem Host KEIN SSH-Key zugeordnet ist -- ein zugeordneter Schluessel hat immer Vorrang.</p>
<form id="host-ssh-pw-form" class="form-grid">
<div>
<label for="hspw-username">Benutzername</label>
<input type="text" id="hspw-username" required maxlength="128" autocomplete="off">
</div>
<div class="full-width">
<label for="hspw-password">SSH-Passwort setzen/aendern</label>
<input type="text" id="hspw-password" required maxlength="512">
</div>
<p class="hint full-width">Passwort wird AES-256-GCM-verschluesselt gespeichert und nie wieder ausgegeben; beim Aendern bitte beide Felder ausfuellen.</p>
<button type="submit">Speichern</button>
<button type="button" class="btn-danger" id="hspw-delete-btn">Loeschen</button>
</form>
</div>
<div id="host-detail-rdp" class="hidden">
<h3>RDP-Zugangsdaten</h3>
<p class="hint">RDP-Zugangsdaten sind eigenstaendige, wiederverwendbare Objekte -- angelegt und bearbeitet im Reiter "Zugangsdaten". Hier wird nur zugewiesen, welches Objekt fuer diesen Server gilt.</p>
<p class="hint" id="rdp-cred-status"></p>
<form id="host-rdp-cred-map-form" class="form-grid">
<div>
<label for="hrcm-select">RDP-Zugangsdaten zuweisen</label>
<select id="hrcm-select" required></select>
</div>
<button type="submit">Zuweisen</button>
<button type="button" class="btn-danger" id="hrc-unassign-btn">Zuordnung entfernen</button>
</form>
</div>
</div>
</div>
<!-- ================= Zugangsdaten ================= -->
<div class="tab-panel hidden" id="tab-credentials">
<div class="panel">
<h2>Neuen SSH-Key anlegen</h2>
<form id="ssh-key-create-form" class="form-grid">
<div>
<label for="skc-label">Label</label>
<input type="text" id="skc-label" required maxlength="128">
</div>
<div>
<label for="skc-username">Benutzername am Zielsystem</label>
<input type="text" id="skc-username" maxlength="128" autocomplete="off">
</div>
<div>
<label for="skc-type">Key-Typ</label>
<select id="skc-type">
<option value="ed25519">ed25519</option>
<option value="rsa-3072">rsa-3072</option>
<option value="rsa-4096">rsa-4096</option>
<option value="ca-cert">ca-cert</option>
</select>
</div>
<div id="skc-tenant-box">
<label for="skc-tenant">Mandant</label>
<select id="skc-tenant" required></select>
</div>
<div class="full-width">
<div class="ft-btn-row">
<button type="button" class="btn-secondary btn-small" id="skc-generate-btn">Schluessel automatisch generieren</button>
<span class="hint" id="skc-generate-hint">Erzeugt ein neues Schluesselpaar serverseitig und fuellt die beiden Felder unten aus (fuer ca-cert nicht moeglich). Der private Schluessel wird NICHT gespeichert, bevor "Anlegen" gedrueckt wird.</span>
</div>
</div>
<div class="full-width">
<label for="skc-private">Privater Schluessel (PEM)</label>
<textarea id="skc-private" required></textarea>
</div>
<div class="full-width">
<label for="skc-public">Oeffentlicher Schluessel</label>
<textarea id="skc-public" required></textarea>
</div>
<div class="full-width">
<label for="skc-passphrase">Passphrase (nur bei verschluesseltem Schluessel)</label>
<input type="password" id="skc-passphrase" maxlength="1024" autocomplete="new-password">
<p class="hint">Beginnt der private Schluessel mit "-----BEGIN OPENSSH PRIVATE KEY-----" und wurde er mit einer Passphrase erzeugt, muss sie hier hinterlegt werden -- sonst laesst sich der Schluessel beim Verbindungsaufbau nicht laden. Sie wird wie das Schluesselmaterial selbst AES-256-GCM-verschluesselt gespeichert und niemals wieder ausgegeben. Leer lassen, wenn der Schluessel unverschluesselt ist.</p>
</div>
<button type="submit">Anlegen</button>
</form>
<h3>Vorhandene SSH-Keys</h3>
<div class="table-wrap">
<table class="data-table" id="ssh-keys-table">
<thead><tr><th>ID</th><th>Label</th><th>Benutzer</th><th>Typ</th><th>Mandant</th><th>Passphrase</th><th>Erstellt</th><th>Zuletzt ersetzt</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="ssh-key-edit-panel">
<h2>SSH-Key bearbeiten: <span id="ssh-key-edit-name"></span></h2>
<form id="ssh-key-edit-form" class="form-grid">
<div>
<label for="ske-label">Label</label>
<input type="text" id="ske-label" maxlength="128">
</div>
<div>
<label for="ske-username">Benutzername am Zielsystem</label>
<input type="text" id="ske-username" maxlength="128" autocomplete="off">
</div>
<button type="submit">Speichern (Label/Benutzername)</button>
</form>
<h3>Passphrase</h3>
<p class="hint" id="ske-passphrase-state"></p>
<p class="hint">Traegt die Passphrase zu dem bereits gespeicherten Schluessel nach, ohne ihn neu hochzuladen. Sie wird sofort gegen das hinterlegte Schluesselmaterial geprueft -- passt sie nicht, wird nichts gespeichert. Leeres Feld + "Passphrase entfernen" loescht eine hinterlegte Passphrase (nur sinnvoll bei unverschluesseltem Schluessel).</p>
<form id="ssh-key-passphrase-form" class="form-grid">
<div class="full-width">
<label for="skp-passphrase">Passphrase</label>
<input type="password" id="skp-passphrase" maxlength="1024" autocomplete="new-password">
</div>
<button type="submit">Passphrase speichern</button>
<button type="button" id="skp-clear-btn" class="btn-secondary">Passphrase entfernen</button>
</form>
<h3>Neuen Schluessel fuer diesen Eintrag speichern</h3>
<p class="hint">Ersetzt das Schluesselmaterial dieses Eintrags vollstaendig (auch "Rotieren" genannt) -- es entsteht KEIN neuer Eintrag, bereits zugeordnete Hosts nutzen danach automatisch den neuen Schluessel.</p>
<form id="ssh-key-rotate-form" class="form-grid">
<div>
<label for="skr-type">Key-Typ</label>
<select id="skr-type">
<option value="ed25519">ed25519</option>
<option value="rsa-3072">rsa-3072</option>
<option value="rsa-4096">rsa-4096</option>
<option value="ca-cert">ca-cert</option>
</select>
</div>
<div class="full-width">
<label for="skr-private">Neuer privater Schluessel (PEM)</label>
<textarea id="skr-private" required></textarea>
</div>
<div class="full-width">
<label for="skr-public">Neuer oeffentlicher Schluessel</label>
<textarea id="skr-public" required></textarea>
</div>
<div class="full-width">
<label for="skr-passphrase">Passphrase des neuen Schluessels (optional)</label>
<input type="password" id="skr-passphrase" maxlength="1024" autocomplete="new-password">
</div>
<button type="submit" class="btn-secondary">Neuen Schluessel speichern</button>
</form>
</div>
<div class="panel">
<h2>Neue RDP-Zugangsdaten anlegen</h2>
<p class="hint">Eigenstaendiges, wiederverwendbares Objekt (analog einem SSH-Key) -- kann nach dem Anlegen im Reiter "Server" beliebigen Hosts zugewiesen werden, statt es je Server neu einzutippen.</p>
<form id="rdp-cred-create-form" class="form-grid">
<div>
<label for="rcc-label">Label</label>
<input type="text" id="rcc-label" required maxlength="128">
</div>
<div>
<label for="rcc-username">Windows-Benutzername</label>
<input type="text" id="rcc-username" required maxlength="128" autocomplete="off">
</div>
<div>
<label for="rcc-domain">Domaene (optional)</label>
<input type="text" id="rcc-domain" maxlength="128" autocomplete="off">
</div>
<div id="rcc-tenant-box">
<label for="rcc-tenant">Mandant</label>
<select id="rcc-tenant" required></select>
</div>
<div class="full-width">
<label for="rcc-password">Passwort</label>
<input type="text" id="rcc-password" required maxlength="512">
</div>
<p class="hint full-width">Das Passwort wird AES-256-GCM-verschluesselt gespeichert und ist nach dem Anlegen nicht mehr im Klartext abrufbar.</p>
<button type="submit">Anlegen</button>
</form>
<h3>Vorhandene RDP-Zugangsdaten</h3>
<div class="table-wrap">
<table class="data-table" id="rdp-creds-table">
<thead><tr><th>ID</th><th>Label</th><th>Benutzer</th><th>Domaene</th><th>Mandant</th><th>Zugewiesene Server</th><th>Zuletzt rotiert</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="rdp-cred-edit-panel">
<h2>RDP-Zugangsdaten bearbeiten: <span id="rdp-cred-edit-name"></span></h2>
<form id="rdp-cred-edit-form" class="form-grid">
<div>
<label for="rce-label">Label</label>
<input type="text" id="rce-label" maxlength="128">
</div>
<div>
<label for="rce-username">Windows-Benutzername</label>
<input type="text" id="rce-username" maxlength="128" autocomplete="off">
</div>
<div>
<label for="rce-domain">Domaene</label>
<input type="text" id="rce-domain" maxlength="128" autocomplete="off">
</div>
<button type="submit">Speichern (Label/Benutzer/Domaene)</button>
</form>
<h3>Passwort setzen/aendern</h3>
<p class="hint">Rotiert NUR das Passwort dieses Eintrags -- es entsteht KEIN neuer Eintrag, alle zugewiesenen Hosts nutzen danach automatisch das neue Passwort.</p>
<form id="rdp-cred-rotate-form" class="form-grid">
<div class="full-width">
<label for="rcr-password">Neues Passwort</label>
<input type="text" id="rcr-password" required maxlength="512">
</div>
<button type="submit" class="btn-secondary">Neues Passwort speichern</button>
</form>
</div>
</div>
<!-- ================= Rollen ================= -->
<div class="tab-panel hidden" id="tab-roles">
<div class="panel">
<h2>Rolle(n) an Benutzer vergeben</h2>
<p class="hint">Mehrfachauswahl moeglich -- alle ausgewaehlten Rollen werden in einem Schritt vergeben.</p>
<form id="role-grant-form" class="form-grid">
<div>
<label for="rg-user">Benutzer</label>
<select id="rg-user" required></select>
</div>
<div>
<label for="rg-hostgroup">Hostgruppe</label>
<select id="rg-hostgroup" required></select>
</div>
<div class="full-width">
<label>Rollen</label>
<div class="scope-grid" id="rg-role-grid"></div>
</div>
<div>
<label for="rg-expires">Ablaufdatum (optional, ISO)</label>
<input type="text" id="rg-expires" placeholder="2026-12-31T00:00:00.000Z" maxlength="40">
</div>
<button type="submit">Vergeben</button>
</form>
<div class="table-wrap">
<table class="data-table" id="role-grants-table">
<thead><tr><th>Benutzer</th><th>Hostgruppe</th><th>Rolle</th><th>Ablauf</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel">
<h2>Rolle(n) an Benutzergruppe vergeben (Verbindung mit Gruppe teilen)</h2>
<p class="hint">Volle Rollen-Vererbung: jedes aktuelle und zukuenftige Mitglied der Gruppe erhaelt diese Rollen automatisch. Mehrfachauswahl moeglich.</p>
<form id="group-role-grant-form" class="form-grid">
<div>
<label for="grg-group">Benutzergruppe</label>
<select id="grg-group" required></select>
</div>
<div>
<label for="grg-hostgroup">Hostgruppe</label>
<select id="grg-hostgroup" required></select>
</div>
<div class="full-width">
<label>Rollen</label>
<div class="scope-grid" id="grg-role-grid"></div>
</div>
<div>
<label for="grg-expires">Ablaufdatum (optional, ISO)</label>
<input type="text" id="grg-expires" placeholder="2026-12-31T00:00:00.000Z" maxlength="40">
</div>
<button type="submit">Vergeben</button>
</form>
<div class="table-wrap">
<table class="data-table" id="group-role-grants-table">
<thead><tr><th>Gruppe</th><th>Hostgruppe</th><th>Rolle</th><th>Ablauf</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<!-- ================= API-Tokens ================= -->
<div class="tab-panel hidden" id="tab-tokens">
<div class="panel">
<h2>Neues API-Token erstellen</h2>
<p class="hint">Tokens wirken NUR auf die Verwaltungs-API (Benutzer/Hosts/Rollen/...), nie auf SSH-/RDP-Sitzungen selbst. "rw" schliesst "ro" auf derselben Ressource automatisch mit ein. Jedes Token gehoert genau einem Mandanten.</p>
<form id="token-create-form" class="form-grid">
<div>
<label for="tc-label">Bezeichnung</label>
<input type="text" id="tc-label" required maxlength="128">
</div>
<div>
<label for="tc-user">Fuer Benutzer</label>
<select id="tc-user" required></select>
</div>
<div id="tc-tenant-box">
<label for="tc-tenant">Mandant</label>
<select id="tc-tenant" required></select>
</div>
<div>
<label for="tc-expires">Ablaufdatum (optional, ISO)</label>
<input type="text" id="tc-expires" placeholder="2026-12-31T00:00:00.000Z" maxlength="40">
</div>
<div class="full-width">
<label>Scopes</label>
<div class="scope-grid" id="tc-scope-grid"></div>
</div>
<button type="submit">Token erstellen</button>
</form>
<div id="token-reveal-box" class="reveal-box hidden"></div>
</div>
<div class="panel">
<h2>Vorhandene Tokens</h2>
<div class="table-wrap">
<table class="data-table" id="tokens-table">
<thead>
<tr><th>ID</th><th>Bezeichnung</th><th>Benutzer</th><th>Praefix</th><th>Mandant</th><th>Scopes</th><th>Zuletzt genutzt</th><th>Status</th><th></th></tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<!-- ================= Mandanten (nur Super-Admin) ================= -->
<div class="tab-panel hidden" id="tab-tenants">
<div class="panel">
<h2>Neuen Mandanten anlegen</h2>
<form id="tenant-create-form" class="form-grid">
<div>
<label for="tnc-name">Name</label>
<input type="text" id="tnc-name" required maxlength="128">
</div>
<div>
<label for="tnc-description">Beschreibung (optional)</label>
<input type="text" id="tnc-description" maxlength="1024">
</div>
<button type="submit">Anlegen</button>
</form>
<h3>Mandanten</h3>
<div class="table-wrap">
<table class="data-table" id="tenants-table">
<thead><tr><th>ID</th><th>Name</th><th>Beschreibung</th><th>Aktiv</th><th>Hostgruppen</th><th>Benutzergruppen</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="panel hidden" id="tenant-edit-panel">
<h2>Mandant bearbeiten: <span id="tenant-edit-name"></span></h2>
<form id="tenant-edit-form" class="form-grid">
<div>
<label for="tne-name">Name</label>
<input type="text" id="tne-name" required maxlength="128">
</div>
<div>
<label for="tne-description">Beschreibung</label>
<input type="text" id="tne-description" maxlength="1024">
</div>
<div class="checkbox-row"><input type="checkbox" id="tne-active"><label for="tne-active">Aktiv</label></div>
<button type="submit">Speichern</button>
</form>
</div>
<div class="panel hidden" id="tenant-admins-panel">
<h2>Mandanten-Admins von <span id="tenant-admins-name"></span></h2>
<div class="table-wrap">
<table class="data-table" id="tenant-admins-table">
<thead><tr><th>User-ID</th><th>Benutzername</th><th>Seit</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
<h3>Mandanten-Admin hinzufuegen</h3>
<form id="tenant-admin-add-form" class="form-grid">
<div>
<label for="ta-user-select">Benutzer</label>
<select id="ta-user-select" required></select>
</div>
<button type="submit">Hinzufuegen</button>
</form>
</div>
</div>
<!-- ================= Sessions (nur Super-Admin) ================= -->
<div class="tab-panel hidden" id="tab-sessions">
<div class="panel">
<h2>Sitzungen</h2>
<p class="hint">Aktive und historische SSH/RDP-Sitzungen. 'Beenden' trennt eine laufende Sitzung zwangsweise -- funktioniert nur, wenn die Sitzung auf demselben Server-Prozess laeuft, der diese Anfrage bearbeitet.</p>
<div class="checkbox-row">
<input type="checkbox" id="sessions-active-only" checked>
<label for="sessions-active-only">Nur aktive Sitzungen</label>
</div>
<div class="table-wrap">
<table class="data-table" id="sessions-table">
<thead><tr><th>ID</th><th>Benutzer</th><th>Host</th><th>Protokoll</th><th>Start</th><th>Status</th><th>Client-IP</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<!-- ================= Verbindungslog (nur Super-Admin) ================= -->
<div class="tab-panel hidden" id="tab-connlog">
<div class="panel">
<h2>Verbindungslog (Live)</h2>
<p class="hint">Live-Mitschnitt der Anwendungslogs dieses Server-Prozesses, inkl. Debug-Details zu SSH/RDP-Verbindungsaufbau. Rein prozesslokal (kein Verlauf vor dem Oeffnen dieses Tabs) und nur fuer Super-Admins sichtbar.</p>
<div class="form-grid">
<div>
<label for="connlog-filter">Filter (Client-seitig)</label>
<input type="text" id="connlog-filter" placeholder="z.B. Hostname oder 'ERROR'">
</div>
<button type="button" class="btn-secondary" id="connlog-clear-btn">Leeren</button>
</div>
<p class="hint">Status: <span id="connlog-status">getrennt</span></p>
<pre id="connlog-output" class="log-view"></pre>
</div>
</div>
<!-- ================= Audit-Log ================= -->
<div class="tab-panel hidden" id="tab-audit">
<div class="panel">
<h2>Audit-Log</h2>
<button type="button" class="btn-secondary" id="audit-verify-btn">Ketten-Integritaet pruefen</button>
<div id="audit-verify-result" class="hidden"></div>
<div class="table-wrap">
<table class="data-table" id="audit-table">
<thead><tr><th>ID</th><th>Zeit</th><th>User-ID</th><th>IP</th><th>Ereignis</th><th>Details</th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- ================= Sitzungs-Wiedergabe (Overlay) ================= -->
<div class="ft-overlay hidden" id="rec-overlay">
<div class="ft-panel rec-panel">
<h2 id="rec-title">Wiedergabe
<button type="button" class="btn-secondary btn-small" id="rec-close-btn">Schliessen</button>
</h2>
<div id="rec-verify-banner" class="hidden"></div>
<div id="rec-loading" class="hint">Aufzeichnung wird geladen ...</div>
<div id="rec-stage" class="rec-stage hidden"></div>
<div class="rec-controls hidden" id="rec-controls">
<button type="button" class="btn-secondary btn-small" id="rec-playpause-btn">Abspielen</button>
<input type="range" id="rec-seek" min="0" max="0" value="0" step="1">
<span class="hint" id="rec-time">0:00 / 0:00</span>
<select id="rec-speed" class="hidden">
<option value="0.5">0.5x</option>
<option value="1" selected>1x</option>
<option value="2">2x</option>
<option value="4">4x</option>
</select>
</div>
</div>
</div>
<script src="/static/js/vendor/xterm.js"></script>
<script src="/static/js/vendor/xterm-addon-fit.js"></script>
<script src="/static/js/vendor/guacamole-common.js"></script>
<script src="/static/js/admin.js"></script>
</body>
</html>