This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

832 lines
48 KiB
Python

"""
Admin Panel Component - HTML Markup
Extracted from admin_panel.py for maintainability.
Contains all HTML templates for the admin panel modal, tabs, forms, and dialogs.
"""
def get_admin_panel_html() -> str:
"""HTML fuer Admin Panel zurueckgeben"""
return """
<!-- Admin Panel Modal -->
<div id="admin-modal" class="admin-modal">
<div class="admin-modal-content">
<div class="admin-modal-header">
<h2><span>⚙️</span> Consent Admin Panel</h2>
<button id="admin-modal-close" class="legal-modal-close">&times;</button>
</div>
<div class="admin-tabs">
<button class="admin-tab active" data-tab="documents">Dokumente</button>
<button class="admin-tab" data-tab="versions">Versionen</button>
<button class="admin-tab" data-tab="cookies">Cookie-Kategorien</button>
<button class="admin-tab" data-tab="stats">Statistiken</button>
<button class="admin-tab" data-tab="emails">E-Mail Vorlagen</button>
<button class="admin-tab" data-tab="dsms">DSMS</button>
<button class="admin-tab" data-tab="gpu">GPU Infra</button>
</div>
<div class="admin-body">
<!-- Documents Tab -->
<div id="admin-documents" class="admin-content active">
<div class="admin-toolbar">
<div class="admin-toolbar-left">
<input type="text" class="admin-search" placeholder="Dokumente suchen..." id="admin-doc-search">
</div>
<button class="btn btn-primary btn-sm" onclick="showDocumentForm()">+ Neues Dokument</button>
</div>
<!-- Document Creation Form -->
<div id="admin-document-form" class="admin-form" style="display: none;">
<h3 class="admin-form-title" id="admin-document-form-title">Neues Dokument erstellen</h3>
<input type="hidden" id="admin-document-id">
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Dokumenttyp *</label>
<select class="admin-form-select" id="admin-document-type">
<option value="">-- Typ auswählen --</option>
<option value="terms">AGB (Allgemeine Geschäftsbedingungen)</option>
<option value="privacy">Datenschutzerklärung</option>
<option value="cookies">Cookie-Richtlinie</option>
<option value="community">Community Guidelines</option>
<option value="imprint">Impressum</option>
</select>
</div>
<div class="admin-form-group">
<label class="admin-form-label">Name *</label>
<input type="text" class="admin-form-input" id="admin-document-name" placeholder="z.B. Allgemeine Geschäftsbedingungen">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Beschreibung</label>
<input type="text" class="admin-form-input" id="admin-document-description" placeholder="Kurze Beschreibung des Dokuments">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">
<input type="checkbox" id="admin-document-mandatory" style="margin-right: 8px;">
Pflichtdokument (Nutzer müssen zustimmen)
</label>
</div>
</div>
<div class="admin-form-actions">
<button class="btn btn-ghost btn-sm" onclick="hideDocumentForm()">Abbrechen</button>
<button class="btn btn-primary btn-sm" onclick="saveDocument()">Dokument erstellen</button>
</div>
</div>
<div id="admin-doc-table-container">
<div class="admin-loading">Lade Dokumente...</div>
</div>
</div>
<!-- Versions Tab -->
<div id="admin-versions" class="admin-content">
<div class="admin-toolbar">
<div class="admin-toolbar-left">
<select class="admin-form-select" id="admin-version-doc-select" onchange="loadVersionsForDocument()">
<option value="">-- Dokument auswählen --</option>
</select>
</div>
<button class="btn btn-primary btn-sm" onclick="showVersionForm()" id="btn-new-version" disabled>+ Neue Version</button>
</div>
<div id="admin-version-form" class="admin-form">
<h3 class="admin-form-title" id="admin-version-form-title">Neue Version erstellen</h3>
<input type="hidden" id="admin-version-id">
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Version *</label>
<input type="text" class="admin-form-input" id="admin-version-number" placeholder="z.B. 1.0.0">
</div>
<div class="admin-form-group">
<label class="admin-form-label">Sprache *</label>
<select class="admin-form-select" id="admin-version-lang">
<option value="de">Deutsch</option>
<option value="en">English</option>
</select>
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Titel *</label>
<input type="text" class="admin-form-input" id="admin-version-title" placeholder="Titel der Version">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Zusammenfassung</label>
<input type="text" class="admin-form-input" id="admin-version-summary" placeholder="Kurze Zusammenfassung der Änderungen">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Inhalt *</label>
<div class="editor-container">
<div class="editor-toolbar">
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="formatDoc('bold')" title="Fett (Strg+B)"><b>B</b></button>
<button type="button" class="editor-btn" onclick="formatDoc('italic')" title="Kursiv (Strg+I)"><i>I</i></button>
<button type="button" class="editor-btn" onclick="formatDoc('underline')" title="Unterstrichen (Strg+U)"><u>U</u></button>
</div>
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="formatBlock('h1')" title="Überschrift 1">H1</button>
<button type="button" class="editor-btn" onclick="formatBlock('h2')" title="Überschrift 2">H2</button>
<button type="button" class="editor-btn" onclick="formatBlock('h3')" title="Überschrift 3">H3</button>
<button type="button" class="editor-btn" onclick="formatBlock('p')" title="Absatz">P</button>
</div>
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="formatDoc('insertUnorderedList')" title="Aufzählung">• Liste</button>
<button type="button" class="editor-btn" onclick="formatDoc('insertOrderedList')" title="Nummerierung">1. Liste</button>
</div>
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="insertLink()" title="Link einfügen">🔗</button>
<button type="button" class="editor-btn" onclick="formatDoc('formatBlock', 'blockquote')" title="Zitat">❝</button>
<button type="button" class="editor-btn" onclick="formatDoc('insertHorizontalRule')" title="Trennlinie">—</button>
</div>
<div class="editor-toolbar-group">
<button type="button" class="editor-btn editor-btn-upload" onclick="document.getElementById('word-upload').click()" title="Word-Dokument importieren">📄 Word Import</button>
<input type="file" id="word-upload" class="word-upload-input" accept=".docx,.doc" onchange="handleWordUpload(event)">
</div>
</div>
<div id="admin-version-editor" class="editor-content" contenteditable="true" placeholder="Schreiben Sie hier den Inhalt..."></div>
<div class="editor-status">
<span id="editor-char-count">0 Zeichen</span> |
<span style="color: var(--bp-text-muted);">Tipp: Sie können direkt aus Word kopieren und einfügen!</span>
</div>
</div>
<input type="hidden" id="admin-version-content">
</div>
</div>
<div class="admin-form-actions">
<button class="btn btn-ghost btn-sm" onclick="hideVersionForm()">Abbrechen</button>
<button class="btn btn-primary btn-sm" onclick="saveVersion()">Speichern</button>
</div>
</div>
<div id="admin-version-table-container">
<div class="admin-empty">Wählen Sie ein Dokument aus, um dessen Versionen anzuzeigen.</div>
</div>
<!-- Approval Dialog -->
<div id="approval-dialog" class="admin-dialog">
<div class="admin-dialog-content">
<h3>Version genehmigen</h3>
<p class="admin-dialog-info">
Legen Sie einen Veröffentlichungszeitpunkt fest. Die Version wird automatisch zum gewählten Zeitpunkt veröffentlicht.
</p>
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Veröffentlichungsdatum *</label>
<input type="date" class="admin-form-input" id="approval-date" required>
</div>
<div class="admin-form-group">
<label class="admin-form-label">Uhrzeit</label>
<input type="time" class="admin-form-input" id="approval-time" value="00:00">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Kommentar (optional)</label>
<input type="text" class="admin-form-input" id="approval-comment" placeholder="z.B. Genehmigt nach rechtlicher Prüfung">
</div>
</div>
<div class="admin-dialog-actions">
<button class="btn btn-ghost btn-sm" onclick="hideApprovalDialog()">Abbrechen</button>
<button class="btn btn-primary btn-sm" onclick="submitApproval()">Genehmigen & Planen</button>
</div>
</div>
</div>
</div>
<!-- Version Compare View (Full Screen Overlay) -->
<div id="version-compare-view" class="version-compare-overlay">
<div class="version-compare-header">
<h2>Versionsvergleich</h2>
<div class="version-compare-info">
<span id="compare-published-info"></span>
<span class="compare-vs">vs</span>
<span id="compare-draft-info"></span>
</div>
<button class="btn btn-ghost" onclick="hideCompareView()">Schließen</button>
</div>
<div class="version-compare-container">
<div class="version-compare-panel">
<div class="version-compare-panel-header">
<span class="compare-label compare-label-published">Veröffentlichte Version</span>
<span id="compare-published-version"></span>
</div>
<div class="version-compare-content" id="compare-content-left"></div>
</div>
<div class="version-compare-panel">
<div class="version-compare-panel-header">
<span class="compare-label compare-label-draft">Neue Version</span>
<span id="compare-draft-version"></span>
</div>
<div class="version-compare-content" id="compare-content-right"></div>
</div>
</div>
<div class="version-compare-footer">
<div id="compare-history-container"></div>
<div id="compare-actions-container" style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 12px;"></div>
</div>
</div>
<!-- Cookie Categories Tab -->
<div id="admin-cookies" class="admin-content">
<div class="admin-toolbar">
<div class="admin-toolbar-left">
<span style="color: var(--bp-text-muted);">Cookie-Kategorien verwalten</span>
</div>
<button class="btn btn-primary btn-sm" onclick="showCookieForm()">+ Neue Kategorie</button>
</div>
<div id="admin-cookie-form" class="admin-form">
<h3 class="admin-form-title">Neue Cookie-Kategorie</h3>
<input type="hidden" id="admin-cookie-id">
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Technischer Name *</label>
<input type="text" class="admin-form-input" id="admin-cookie-name" placeholder="z.B. analytics">
</div>
<div class="admin-form-group">
<label class="admin-form-label">Anzeigename (DE) *</label>
<input type="text" class="admin-form-input" id="admin-cookie-display-de" placeholder="z.B. Analyse-Cookies">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Anzeigename (EN)</label>
<input type="text" class="admin-form-input" id="admin-cookie-display-en" placeholder="z.B. Analytics Cookies">
</div>
<div class="admin-form-group">
<label class="admin-form-label">
<input type="checkbox" id="admin-cookie-mandatory"> Notwendig (kann nicht deaktiviert werden)
</label>
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Beschreibung (DE)</label>
<input type="text" class="admin-form-input" id="admin-cookie-desc-de" placeholder="Beschreibung auf Deutsch">
</div>
</div>
<div class="admin-form-actions">
<button class="btn btn-ghost btn-sm" onclick="hideCookieForm()">Abbrechen</button>
<button class="btn btn-primary btn-sm" onclick="saveCookieCategory()">Speichern</button>
</div>
</div>
<div id="admin-cookie-table-container">
<div class="admin-loading">Lade Cookie-Kategorien...</div>
</div>
</div>
<!-- Statistics Tab -->
<div id="admin-stats" class="admin-content">
<div id="admin-stats-container">
<div class="admin-loading">Lade Statistiken...</div>
</div>
</div>
""" + _get_email_templates_html() + """
""" + _get_dsms_html() + """
""" + _get_gpu_html() + """
</div>
</div>
</div>
""" + _get_dsms_webui_modal_html() + """
"""
def _get_email_templates_html() -> str:
"""HTML fuer E-Mail Templates Tab"""
return """
<!-- E-Mail Templates Tab -->
<div id="admin-emails" class="admin-content">
<div class="admin-toolbar">
<div class="admin-toolbar-left">
<select class="admin-form-select" id="email-template-select" onchange="loadEmailTemplateVersions()">
<option value="">-- E-Mail-Vorlage auswählen --</option>
</select>
</div>
<button class="btn btn-ghost btn-sm" onclick="initializeEmailTemplates()">Templates initialisieren</button>
<button class="btn btn-primary btn-sm" onclick="showEmailVersionForm()" id="btn-new-email-version" disabled>+ Neue Version</button>
</div>
<!-- E-Mail Template Info Card -->
<div id="email-template-info" style="display: none; margin-bottom: 16px;">
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 16px; border: 1px solid var(--bp-border);">
<div style="display: flex; justify-content: space-between; align-items: start;">
<div>
<h3 style="margin: 0 0 8px 0; font-size: 16px;" id="email-template-name">-</h3>
<p style="margin: 0; color: var(--bp-text-muted); font-size: 13px;" id="email-template-description">-</p>
</div>
<div style="text-align: right;">
<div class="admin-badge" id="email-template-type-badge">-</div>
</div>
</div>
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--bp-border);">
<span style="font-size: 12px; color: var(--bp-text-muted);">Variablen: </span>
<span id="email-template-variables" style="font-size: 12px; font-family: monospace; color: var(--bp-primary);"></span>
</div>
</div>
</div>
<!-- E-Mail Version Form -->
<div id="email-version-form" class="admin-form" style="display: none;">
<h3 class="admin-form-title" id="email-version-form-title">Neue E-Mail-Version erstellen</h3>
<input type="hidden" id="email-version-id">
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Version *</label>
<input type="text" class="admin-form-input" id="email-version-number" placeholder="z.B. 1.0.0">
</div>
<div class="admin-form-group">
<label class="admin-form-label">Sprache *</label>
<select class="admin-form-select" id="email-version-lang">
<option value="de">Deutsch</option>
<option value="en">English</option>
</select>
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Betreff *</label>
<input type="text" class="admin-form-input" id="email-version-subject" placeholder="E-Mail Betreff (kann Variablen enthalten)">
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">HTML-Inhalt *</label>
<div class="editor-container">
<div class="editor-toolbar">
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="formatEmailDoc('bold')" title="Fett"><b>B</b></button>
<button type="button" class="editor-btn" onclick="formatEmailDoc('italic')" title="Kursiv"><i>I</i></button>
<button type="button" class="editor-btn" onclick="formatEmailDoc('underline')" title="Unterstrichen"><u>U</u></button>
</div>
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="formatEmailBlock('h1')" title="Überschrift 1">H1</button>
<button type="button" class="editor-btn" onclick="formatEmailBlock('h2')" title="Überschrift 2">H2</button>
<button type="button" class="editor-btn" onclick="formatEmailBlock('p')" title="Absatz">P</button>
</div>
<div class="editor-toolbar-group">
<button type="button" class="editor-btn" onclick="insertEmailVariable()" title="Variable einfügen">{{var}}</button>
<button type="button" class="editor-btn" onclick="insertEmailLink()" title="Link einfügen">🔗</button>
<button type="button" class="editor-btn" onclick="insertEmailButton()" title="Button einfügen">🔘</button>
</div>
</div>
<div id="email-version-editor" class="editor-content" contenteditable="true" placeholder="HTML-Inhalt der E-Mail..." style="min-height: 200px;"></div>
</div>
</div>
</div>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Text-Version (Plain Text)</label>
<textarea class="admin-form-input" id="email-version-text" rows="5" placeholder="Plain-Text-Version der E-Mail (optional, wird aus HTML generiert falls leer)"></textarea>
</div>
</div>
<div class="admin-form-actions">
<button class="btn btn-ghost btn-sm" onclick="hideEmailVersionForm()">Abbrechen</button>
<button class="btn btn-ghost btn-sm" onclick="previewEmailVersion()">Vorschau</button>
<button class="btn btn-primary btn-sm" onclick="saveEmailVersion()">Speichern</button>
</div>
</div>
<!-- E-Mail Versions Table -->
<div id="email-version-table-container">
<div class="admin-empty">Wählen Sie eine E-Mail-Vorlage aus, um deren Versionen anzuzeigen.</div>
</div>
<!-- E-Mail Preview Dialog -->
<div id="email-preview-dialog" class="admin-dialog" style="display: none;">
<div class="admin-dialog-content" style="max-width: 700px; max-height: 80vh; overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3 style="margin: 0;">E-Mail Vorschau</h3>
<button class="btn btn-ghost btn-sm" onclick="hideEmailPreview()">Schließen</button>
</div>
<div style="margin-bottom: 12px;">
<strong>Betreff:</strong> <span id="email-preview-subject"></span>
</div>
<div style="border: 1px solid var(--bp-border); border-radius: 8px; padding: 16px; background: white; color: #333;">
<div id="email-preview-content"></div>
</div>
<div style="margin-top: 16px; display: flex; gap: 8px;">
<input type="email" class="admin-form-input" id="email-test-address" placeholder="Test-E-Mail-Adresse" style="flex: 1;">
<button class="btn btn-primary btn-sm" onclick="sendTestEmail()">Test-E-Mail senden</button>
</div>
</div>
</div>
<!-- E-Mail Approval Dialog -->
<div id="email-approval-dialog" class="admin-dialog" style="display: none;">
<div class="admin-dialog-content">
<h3>E-Mail-Version genehmigen</h3>
<div class="admin-form-row">
<div class="admin-form-group full-width">
<label class="admin-form-label">Kommentar (optional)</label>
<input type="text" class="admin-form-input" id="email-approval-comment" placeholder="z.B. Genehmigt nach Marketing-Prüfung">
</div>
</div>
<div class="admin-dialog-actions">
<button class="btn btn-ghost btn-sm" onclick="hideEmailApprovalDialog()">Abbrechen</button>
<button class="btn btn-primary btn-sm" onclick="submitEmailApproval()">Genehmigen</button>
</div>
</div>
</div>
</div>
"""
def _get_dsms_html() -> str:
"""HTML fuer DSMS Tab"""
return """
<!-- DSMS Tab -->
<div id="admin-dsms" class="admin-content">
<div class="admin-toolbar">
<div class="admin-toolbar-left">
<span style="font-weight: 600; color: var(--bp-primary);">Dezentrales Speichersystem (IPFS)</span>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-ghost btn-sm" onclick="openDsmsWebUI()">DSMS WebUI</button>
<button class="btn btn-primary btn-sm" onclick="loadDsmsData()">Aktualisieren</button>
</div>
</div>
<!-- DSMS Status Cards -->
<div id="dsms-status-cards" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px;">
<div class="admin-loading">Lade DSMS Status...</div>
</div>
<!-- DSMS Tabs -->
<div style="display: flex; gap: 8px; margin-bottom: 16px; border-bottom: 1px solid var(--bp-border); padding-bottom: 8px;">
<button class="dsms-subtab active" data-dsms-tab="archives" onclick="switchDsmsTab('archives')">Archivierte Dokumente</button>
<button class="dsms-subtab" data-dsms-tab="verify" onclick="switchDsmsTab('verify')">Verifizierung</button>
<button class="dsms-subtab" data-dsms-tab="settings" onclick="switchDsmsTab('settings')">Einstellungen</button>
</div>
<!-- Archives Sub-Tab -->
<div id="dsms-archives" class="dsms-content active">
<div class="admin-toolbar" style="margin-bottom: 16px;">
<div class="admin-toolbar-left">
<input type="text" class="admin-search" placeholder="CID suchen..." id="dsms-cid-search" style="width: 300px;">
</div>
<button class="btn btn-primary btn-sm" onclick="showArchiveForm()">+ Dokument archivieren</button>
</div>
<!-- Archive Form -->
<div id="dsms-archive-form" class="admin-form" style="display: none; margin-bottom: 16px;">
<h3 class="admin-form-title">Dokument im DSMS archivieren</h3>
<div class="admin-form-row">
<div class="admin-form-group">
<label class="admin-form-label">Dokument auswählen *</label>
<select class="admin-form-select" id="dsms-archive-doc-select">
<option value="">-- Dokument wählen --</option>
</select>
</div>
<div class="admin-form-group">
<label class="admin-form-label">Version *</label>
<select class="admin-form-select" id="dsms-archive-version-select" disabled>
<option value="">-- Erst Dokument wählen --</option>
</select>
</div>
</div>
<div class="admin-form-actions">
<button class="btn btn-ghost btn-sm" onclick="hideArchiveForm()">Abbrechen</button>
<button class="btn btn-primary btn-sm" onclick="archiveDocumentToDsms()">Archivieren</button>
</div>
</div>
<div id="dsms-archives-table">
<div class="admin-loading">Lade archivierte Dokumente...</div>
</div>
</div>
<!-- Verify Sub-Tab -->
<div id="dsms-verify" class="dsms-content" style="display: none;">
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 24px; border: 1px solid var(--bp-border);">
<h3 style="margin: 0 0 16px 0; font-size: 16px;">Dokumentenintegrität prüfen</h3>
<p style="color: var(--bp-text-muted); margin-bottom: 16px; font-size: 14px;">
Geben Sie einen CID (Content Identifier) ein, um die Integrität eines archivierten Dokuments zu verifizieren.
</p>
<div style="display: flex; gap: 12px; margin-bottom: 16px;">
<input type="text" class="admin-form-input" id="dsms-verify-cid" placeholder="Qm... oder bafy..." style="flex: 1;">
<button class="btn btn-primary btn-sm" onclick="verifyDsmsDocument()">Verifizieren</button>
</div>
<div id="dsms-verify-result" style="display: none;"></div>
</div>
</div>
<!-- Settings Sub-Tab -->
<div id="dsms-settings" class="dsms-content" style="display: none;">
<div style="display: grid; gap: 16px;">
<!-- Node Info -->
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 24px; border: 1px solid var(--bp-border);">
<h3 style="margin: 0 0 16px 0; font-size: 16px;">Node-Informationen</h3>
<div id="dsms-node-info">
<div class="admin-loading">Lade Node-Info...</div>
</div>
</div>
<!-- Quick Links -->
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 24px; border: 1px solid var(--bp-border);">
<h3 style="margin: 0 0 16px 0; font-size: 16px;">Schnellzugriff</h3>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
<button class="btn btn-primary btn-sm" onclick="openDsmsWebUI()">DSMS WebUI</button>
<a href="http://localhost:8082/docs" target="_blank" class="btn btn-ghost btn-sm">DSMS API Docs</a>
<a href="http://localhost:8085" target="_blank" class="btn btn-ghost btn-sm">IPFS Gateway</a>
</div>
</div>
<!-- Lizenzhinweise -->
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 24px; border: 1px solid var(--bp-border);">
<h3 style="margin: 0 0 16px 0; font-size: 16px;">Open Source Lizenzen</h3>
<p style="color: var(--bp-text-muted); font-size: 13px; margin-bottom: 12px;">
DSMS verwendet folgende Open-Source-Komponenten:
</p>
<ul style="color: var(--bp-text-muted); font-size: 13px; margin: 0; padding-left: 20px;">
<li><strong>IPFS Kubo</strong> - MIT + Apache 2.0 (Dual License) - Protocol Labs, Inc.</li>
<li><strong>IPFS WebUI</strong> - MIT License - Protocol Labs, Inc.</li>
<li><strong>FastAPI</strong> - MIT License</li>
</ul>
<p style="color: var(--bp-text-muted); font-size: 12px; margin-top: 12px; font-style: italic;">
Alle Komponenten erlauben kommerzielle Nutzung.
</p>
</div>
</div>
</div>
</div>
"""
def _get_gpu_html() -> str:
"""HTML fuer GPU Infrastructure Tab"""
return """
<!-- GPU Infrastructure Tab -->
<div id="admin-content-gpu" class="admin-content">
<div class="gpu-control-panel">
<div class="gpu-status-header">
<h3 style="margin: 0; font-size: 16px;">vast.ai GPU Instance</h3>
<div id="gpu-status-badge" class="gpu-status-badge stopped">
<span class="gpu-status-dot"></span>
<span id="gpu-status-text">Unbekannt</span>
</div>
</div>
<div class="gpu-info-grid">
<div class="gpu-info-card">
<div class="gpu-info-label">GPU</div>
<div id="gpu-name" class="gpu-info-value">-</div>
</div>
<div class="gpu-info-card">
<div class="gpu-info-label">Kosten/Stunde</div>
<div id="gpu-dph" class="gpu-info-value cost">-</div>
</div>
<div class="gpu-info-card">
<div class="gpu-info-label">Session</div>
<div id="gpu-session-time" class="gpu-info-value time">-</div>
</div>
<div class="gpu-info-card">
<div class="gpu-info-label">Gesamt</div>
<div id="gpu-total-cost" class="gpu-info-value cost">-</div>
</div>
</div>
<div id="gpu-endpoint" class="gpu-endpoint-url" style="display: none;">
Endpoint: <span id="gpu-endpoint-url">-</span>
</div>
<div id="gpu-shutdown-warning" class="gpu-shutdown-warning" style="display: none;">
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<span>Auto-Shutdown in <strong id="gpu-shutdown-minutes">0</strong> Minuten (bei Inaktivitaet)</span>
</div>
<div class="gpu-controls">
<button id="gpu-btn-start" class="gpu-btn gpu-btn-start" onclick="gpuPowerOn()">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3l14 9-14 9V3z"/>
</svg>
Starten
</button>
<button id="gpu-btn-stop" class="gpu-btn gpu-btn-stop" onclick="gpuPowerOff()" disabled>
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"/>
</svg>
Stoppen
</button>
<button class="gpu-btn gpu-btn-refresh" onclick="gpuRefreshStatus()" title="Status aktualisieren">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
</button>
</div>
<div class="gpu-cost-summary">
<h4>Kosten-Zusammenfassung</h4>
<div class="gpu-info-grid">
<div class="gpu-info-card">
<div class="gpu-info-label">Laufzeit Gesamt</div>
<div id="gpu-total-runtime" class="gpu-info-value time">0h 0m</div>
</div>
<div class="gpu-info-card">
<div class="gpu-info-label">Kosten Gesamt</div>
<div id="gpu-total-cost-all" class="gpu-info-value cost">$0.00</div>
</div>
</div>
</div>
<details style="margin-top: 20px;">
<summary style="cursor: pointer; color: var(--bp-text-muted); font-size: 13px;">
Audit Log (letzte Aktionen)
</summary>
<div id="gpu-audit-log" class="gpu-audit-log">
<div class="gpu-audit-entry">Keine Eintraege</div>
</div>
</details>
</div>
<div style="padding: 12px; background: var(--bp-surface-elevated); border-radius: 8px; font-size: 12px; color: var(--bp-text-muted);">
<strong>Hinweis:</strong> Die GPU-Instanz wird automatisch nach 30 Minuten Inaktivitaet gestoppt.
Bei jedem LLM-Request wird die Aktivitaet aufgezeichnet und der Timer zurueckgesetzt.
</div>
</div>
"""
def _get_dsms_webui_modal_html() -> str:
"""HTML fuer DSMS WebUI Modal"""
return """
<!-- DSMS WebUI Modal -->
<div id="dsms-webui-modal" class="legal-modal" style="display: none;">
<div class="legal-modal-content" style="max-width: 1200px; width: 95%; height: 90vh;">
<div class="legal-modal-header" style="border-bottom: 1px solid var(--bp-border);">
<h2 style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 24px;">&#127760;</span> DSMS WebUI
</h2>
<button id="dsms-webui-modal-close" class="legal-modal-close" onclick="closeDsmsWebUI()">&times;</button>
</div>
<div style="display: flex; height: calc(100% - 60px);">
<!-- Sidebar -->
<div style="width: 200px; background: var(--bp-surface); border-right: 1px solid var(--bp-border); padding: 16px;">
<nav style="display: flex; flex-direction: column; gap: 4px;">
<button class="dsms-webui-nav active" data-section="overview" onclick="switchDsmsWebUISection('overview')">
<span>&#128200;</span> Übersicht
</button>
<button class="dsms-webui-nav" data-section="files" onclick="switchDsmsWebUISection('files')">
<span>&#128193;</span> Dateien
</button>
<button class="dsms-webui-nav" data-section="explore" onclick="switchDsmsWebUISection('explore')">
<span>&#128269;</span> Erkunden
</button>
<button class="dsms-webui-nav" data-section="peers" onclick="switchDsmsWebUISection('peers')">
<span>&#127760;</span> Peers
</button>
<button class="dsms-webui-nav" data-section="config" onclick="switchDsmsWebUISection('config')">
<span>&#9881;</span> Konfiguration
</button>
</nav>
</div>
<!-- Main Content -->
<div style="flex: 1; overflow-y: auto; padding: 24px;" id="dsms-webui-content">
<!-- Overview Section (default) -->
<div id="dsms-webui-overview" class="dsms-webui-section active">
<h3 style="margin: 0 0 24px 0;">Node Übersicht</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px;">
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Status</div>
<div class="dsms-webui-stat-value" id="webui-status">--</div>
</div>
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Node ID</div>
<div class="dsms-webui-stat-value" id="webui-node-id" style="font-size: 11px; word-break: break-all;">--</div>
</div>
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Protokoll</div>
<div class="dsms-webui-stat-value" id="webui-protocol">--</div>
</div>
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Agent</div>
<div class="dsms-webui-stat-value" id="webui-agent">--</div>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px;">
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Repo Größe</div>
<div class="dsms-webui-stat-value" id="webui-repo-size">--</div>
<div class="dsms-webui-stat-sub" id="webui-storage-info">--</div>
</div>
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Objekte</div>
<div class="dsms-webui-stat-value" id="webui-num-objects">--</div>
</div>
<div class="dsms-webui-stat-card">
<div class="dsms-webui-stat-label">Gepinnte Dokumente</div>
<div class="dsms-webui-stat-value" id="webui-pinned-count">--</div>
</div>
</div>
<div style="margin-top: 24px;">
<h4 style="margin: 0 0 12px 0;">Adressen</h4>
<div id="webui-addresses" style="background: var(--bp-input-bg); border-radius: 8px; padding: 12px; font-family: monospace; font-size: 12px; max-height: 150px; overflow-y: auto;">
Lade...
</div>
</div>
</div>
<!-- Files Section -->
<div id="dsms-webui-files" class="dsms-webui-section" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;">
<h3 style="margin: 0;">Dateien hochladen</h3>
</div>
<div class="dsms-webui-upload-zone" id="dsms-upload-zone" ondrop="handleDsmsFileDrop(event)" ondragover="handleDsmsDragOver(event)" ondragleave="handleDsmsDragLeave(event)">
<div style="text-align: center;">
<div style="font-size: 48px; margin-bottom: 16px;">&#128229;</div>
<p style="color: var(--bp-text); margin-bottom: 8px;">Dateien hierher ziehen</p>
<p style="color: var(--bp-text-muted); font-size: 13px;">oder</p>
<input type="file" id="dsms-file-input" style="display: none;" onchange="handleDsmsFileSelect(event)" multiple>
<button class="btn btn-primary btn-sm" onclick="document.getElementById('dsms-file-input').click()">Dateien auswählen</button>
</div>
</div>
<div id="dsms-upload-progress" style="display: none; margin-top: 16px;">
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 16px;">
<div id="dsms-upload-status">Hochladen...</div>
<div style="background: var(--bp-border); border-radius: 4px; height: 8px; margin-top: 8px; overflow: hidden;">
<div id="dsms-upload-bar" style="background: var(--bp-primary); height: 100%; width: 0%; transition: width 0.3s;"></div>
</div>
</div>
</div>
<div id="dsms-upload-results" style="margin-top: 24px;"></div>
</div>
<!-- Explore Section -->
<div id="dsms-webui-explore" class="dsms-webui-section" style="display: none;">
<h3 style="margin: 0 0 24px 0;">IPFS Explorer</h3>
<div style="display: flex; gap: 12px; margin-bottom: 24px;">
<input type="text" class="admin-search" placeholder="CID eingeben (z.B. QmXyz...)" id="webui-explore-cid" style="flex: 1;">
<button class="btn btn-primary btn-sm" onclick="exploreDsmsCid()">Erkunden</button>
</div>
<div id="dsms-explore-result" style="display: none;">
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 16px;">
<div id="dsms-explore-content"></div>
</div>
</div>
</div>
<!-- Peers Section -->
<div id="dsms-webui-peers" class="dsms-webui-section" style="display: none;">
<h3 style="margin: 0 0 24px 0;">Verbundene Peers</h3>
<p style="color: var(--bp-text-muted); margin-bottom: 16px;">
Hinweis: In einem privaten DSMS-Netzwerk sind normalerweise keine externen Peers verbunden.
</p>
<div id="webui-peers-list">
<div class="admin-loading">Lade Peers...</div>
</div>
</div>
<!-- Config Section -->
<div id="dsms-webui-config" class="dsms-webui-section" style="display: none;">
<h3 style="margin: 0 0 24px 0;">Konfiguration</h3>
<div style="display: grid; gap: 16px;">
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 16px;">
<h4 style="margin: 0 0 12px 0;">API Endpoints</h4>
<table style="width: 100%; font-size: 13px;">
<tr>
<td style="padding: 8px 0; color: var(--bp-text-muted);">IPFS API</td>
<td style="padding: 8px 0; font-family: monospace;">http://localhost:5001</td>
</tr>
<tr>
<td style="padding: 8px 0; color: var(--bp-text-muted);">DSMS Gateway</td>
<td style="padding: 8px 0; font-family: monospace;">http://localhost:8082</td>
</tr>
<tr>
<td style="padding: 8px 0; color: var(--bp-text-muted);">IPFS Gateway</td>
<td style="padding: 8px 0; font-family: monospace;">http://localhost:8085</td>
</tr>
<tr>
<td style="padding: 8px 0; color: var(--bp-text-muted);">Swarm P2P</td>
<td style="padding: 8px 0; font-family: monospace;">:4001</td>
</tr>
</table>
</div>
<div style="background: var(--bp-surface-elevated); border-radius: 8px; padding: 16px;">
<h4 style="margin: 0 0 12px 0;">Aktionen</h4>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
<button class="btn btn-ghost btn-sm" onclick="runDsmsGarbageCollection()">&#128465; Garbage Collection</button>
<button class="btn btn-ghost btn-sm" onclick="loadDsmsWebUIData()">&#8635; Daten aktualisieren</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
"""