new layouts

This commit is contained in:
team2
2026-02-18 19:31:46 +01:00
parent b6e7c7cbab
commit 88cce79e29
3 changed files with 399 additions and 344 deletions

View File

@@ -1,207 +1,213 @@
{% extends 'admin/base.html.twig' %} {% extends 'admin/base.html.twig' %}
{% block title %}System Dashboard{% endblock %} {% block title %}System-Dashboard{% endblock %}
{% block body %} {% block body %}
<div class="container-fluid"> <div class="container-fluid">
{# ===================================================== #} <!-- ===================================================== -->
{# HEADER #} <!-- HEADER -->
{# ===================================================== #} <!-- ===================================================== -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">System Overview</h1> <h1 class="h3 mb-0">Systemübersicht</h1>
<span class="badge bg-secondary">RAG Enterprise</span> <span class="badge bg-secondary">RAG Enterprise</span>
</div> </div>
{# ===================================================== #} <!-- ===================================================== -->
{# GOVERNANCE BLOCK #} <!-- KPI ROW -->
{# ===================================================== #} <!-- ===================================================== -->
<div class="card bg-black border-secondary text-light mb-4"> <div class="row g-4 mb-4">
<div class="card-body">
<h5 class="text-info mb-3">System Governance</h5> {% if vectorHealth is defined %}
{% set status = vectorHealth.status %}
{% set badgeClass =
status starts with 'OK'
? 'bg-success'
: (status == 'INCONSISTENT_MISSING_VECTOR'
? 'bg-warning text-dark'
: 'bg-danger')
%}
{% endif %}
<div class="row"> <!-- Vector Status -->
<div class="col-md-6"> <div class="col-lg-6 col-xl-4">
<strong>Current User:</strong><br> <div class="card bg-black border-secondary text-light h-100">
{{ app.user.userIdentifier }} <div class="card-body">
</div>
<div class="col-md-6"> <div class="small text-secondary mb-2">Vektor-Infrastruktur</div>
<strong>Roles:</strong><br>
{{ app.user.roles|join(', ') }}
</div>
</div>
</div> {% if vectorHealth is defined %}
</div> <h4 class="mb-2">
{# ===================================================== #}
{# VECTOR INFRASTRUCTURE #}
{# ===================================================== #}
{% if vectorHealth is defined %}
{% set status = vectorHealth.status %}
{% set badgeClass =
status starts with 'OK'
? 'bg-success'
: (status == 'INCONSISTENT_MISSING_VECTOR'
? 'bg-warning text-dark'
: 'bg-danger')
%}
<div class="card bg-black border-secondary text-light mb-4">
<div class="card-body">
<h5 class="text-info mb-3">Vector Infrastructure</h5>
<div class="row mb-3">
<div class="col-md-4">
<strong>Status</strong><br>
<span class="badge {{ badgeClass }}"> <span class="badge {{ badgeClass }}">
{{ status }} {{ vectorHealth.status }}
</span>
</h4>
<div class="small text-secondary">
NDJSON-Chunks: {{ vectorHealth.ndjson_chunk_count|number_format(0, ',', '.') }}
<br>
Vektor-Index-Chunks: {{ vectorHealth.vector_chunk_count|number_format(0, ',', '.') }}
</div>
{% else %}
<div class="text-secondary small">
Keine Infrastrukturdaten verfügbar.
</div>
{% endif %}
</div>
</div>
</div>
<!-- Knowledge Capacity -->
{% set percent = chunkLimit > 0 ? (chunkCount / chunkLimit * 100)|round(1) : 0 %}
<div class="col-lg-6 col-xl-4">
<div class="card bg-black border-secondary text-light h-100">
<div class="card-body">
<div class="small text-secondary mb-2">Wissenskapazität</div>
<h4 class="mb-2">
{{ chunkCount|number_format(0, ',', '.') }}
<span class="text-secondary small">
/ {{ chunkLimit|number_format(0, ',', '.') }}
</span> </span>
</h4>
<div class="progress bg-dark mb-2" style="height: 14px;">
<div
class="progress-bar
{% if percent >= 95 %}
bg-danger
{% elseif percent >= 85 %}
bg-warning text-dark
{% else %}
bg-success
{% endif %}"
style="width: {{ percent }}%;">
</div>
</div> </div>
<div class="col-md-4"> <div class="small text-secondary">
<strong>NDJSON Chunks</strong><br> {{ percent }} % ausgelastet
{{ vectorHealth.ndjson_chunk_count|number_format(0, ',', '.') }}
</div>
<div class="col-md-4">
<strong>Vector Index Chunks</strong><br>
{{ vectorHealth.vector_chunk_count|number_format(0, ',', '.') }}
</div> </div>
</div> </div>
{% if status starts with 'OK' %}
<div class="small text-success">
Infrastructure is consistent.
</div>
{% elseif status == 'INCONSISTENT_MISSING_VECTOR' %}
<div class="small text-warning">
Vector index missing. Rebuild recommended.
</div>
{% else %}
<div class="small text-danger">
Index inconsistency detected. Immediate review required.
</div>
{% endif %}
</div> </div>
</div> </div>
{% endif %} <!-- Governance Snapshot -->
<div class="col-lg-6 col-xl-4">
<div class="card bg-black border-secondary text-light h-100">
<div class="card-body">
<div class="small text-secondary mb-2">System-Governance</div>
{# ===================================================== #} <div class="small">
{# KNOWLEDGE CAPACITY #} <strong>Benutzer</strong><br>
{# ===================================================== #} {{ app.user.userIdentifier }}
</div>
{% set percent = chunkLimit > 0 ? (chunkCount / chunkLimit * 100)|round(1) : 0 %} <div class="small mt-3">
<strong>Rollen</strong><br>
{{ app.user.roles|join(', ') }}
</div>
<div class="card bg-black border-secondary text-light mb-4">
<div class="card-body">
<h5 class="text-info mb-3">Knowledge Capacity</h5>
<div class="mb-2">
<strong>Chunks:</strong>
{{ chunkCount|number_format(0, ',', '.') }}
/
{{ chunkLimit|number_format(0, ',', '.') }}
</div>
<div class="progress bg-dark" style="height: 20px;">
<div
class="progress-bar
{% if percent >= 95 %}
bg-danger
{% elseif percent >= 85 %}
bg-warning text-dark
{% else %}
bg-success
{% endif %}"
role="progressbar"
style="width: {{ percent }}%;"
aria-valuenow="{{ percent }}"
aria-valuemin="0"
aria-valuemax="100"
>
{{ percent }}%
</div> </div>
</div> </div>
<div class="mt-3 small text-secondary">
System optimized for maximum
{{ chunkLimit|number_format(0, ',', '.') }} chunks.
{% if percent >= 95 %}
<br><strong class="text-danger">Capacity limit nearly reached.</strong>
{% endif %}
</div>
</div> </div>
</div> </div>
<!-- ===================================================== -->
<!-- DETAIL ROW -->
<!-- ===================================================== -->
{# ===================================================== #} <div class="row g-4">
{# CRITICAL OPERATIONS (SUPER ADMIN ONLY) #}
{# ===================================================== #}
{% if is_granted('ROLE_SUPER_ADMIN') %} {% if vectorHealth is defined %}
<div class="card bg-black border-danger text-light"> <!-- Vector Detail -->
<div class="card-body"> <div class="col-lg-6">
<div class="card bg-black border-secondary text-light h-100">
<div class="card-body">
<h5 class="text-danger mb-3">Critical Operations</h5> <h5 class="text-info mb-3">Details zur Vektor-Infrastruktur</h5>
<div class="small mb-3"> <div class="row">
Full system reset removes: <div class="col-6">
<ul> <div class="small text-secondary">NDJSON-Chunks</div>
<li>All documents & versions</li> <div class="h5">
<li>NDJSON index</li> {{ vectorHealth.ndjson_chunk_count|number_format(0, ',', '.') }}
<li>FAISS vector index</li> </div>
<li>All ingest jobs</li> </div>
</ul>
<strong>This action is irreversible.</strong>
</div>
{% for label, messages in app.flashes %} <div class="col-6">
{% for message in messages %} <div class="small text-secondary">Vektor-Index-Chunks</div>
<div class="alert alert-{{ label }}"> <div class="h5">
{{ message }} {{ vectorHealth.vector_chunk_count|number_format(0, ',', '.') }}
</div>
</div>
</div> </div>
{% endfor %}
{% endfor %}
<form method="post"
action="{{ path('admin_document_reset') }}"
onsubmit="return confirm('Confirm full system reset? This cannot be undone.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('system_reset') }}">
<button type="submit"
class="btn btn-outline-danger">
Execute Full System Reset
</button>
</form>
</div>
</div>
</div> </div>
</div>
{% endif %} {% endif %}
{% if is_granted('ROLE_SUPER_ADMIN') %}
<!-- Critical Operations -->
<div class="col-lg-6">
<div class="card bg-black border-danger text-light h-100">
<div class="card-body">
<h5 class="text-danger mb-3">Kritische Systemoperationen</h5>
<div class="small mb-3 text-secondary">
Ein vollständiger System-Reset entfernt:
<ul>
<li>Alle Dokumente und Versionen</li>
<li>Den NDJSON-Index</li>
<li>Den FAISS-Vektorindex</li>
<li>Alle Ingest-Jobs</li>
</ul>
<strong>Diese Aktion ist nicht rückgängig zu machen.</strong>
</div>
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="alert alert-{{ label }}">
{{ message }}
</div>
{% endfor %}
{% endfor %}
<form method="post"
action="{{ path('admin_document_reset') }}"
onsubmit="return confirm('System vollständig zurücksetzen? Diese Aktion kann nicht rückgängig gemacht werden.');">
<input type="hidden"
name="_token"
value="{{ csrf_token('system_reset') }}">
<button type="submit"
class="btn btn-outline-danger">
Vollständigen System-Reset ausführen
</button>
</form>
</div>
</div>
</div>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -12,6 +12,13 @@
</a> </a>
</div> </div>
<div class="alert alert-warning border-warning bg-dark text-light mb-4">
<strong>Wichtiger Hinweis:</strong><br>
Änderungen am Indexierungsprofil beeinflussen die Struktur des Vektor-Indexes
(Chunking, Embedding, Scoring). Nach Aktivierung ist ein vollständiger
Global Reindex erforderlich.
</div>
<div class="card bg-black border-secondary text-light"> <div class="card bg-black border-secondary text-light">
<div class="card-body"> <div class="card-body">
@@ -22,18 +29,20 @@
<div class="row g-4"> <div class="row g-4">
<!-- ===================== --> <!-- ================================================= -->
<!-- Chunking Section --> <!-- CHUNKING -->
<!-- ===================== --> <!-- ================================================= -->
<div class="col-12"> <div class="col-12">
<h5 class="text-info">Chunking</h5> <h5 class="text-info">Chunking-Konfiguration</h5>
<hr class="border-secondary"> <hr class="border-secondary">
<p class="text-secondary small">
Definiert, wie Dokumente in semantische Textabschnitte (Chunks) zerlegt werden.
Diese Struktur beeinflusst Retrieval-Qualität, Kontextstabilität und Indexgröße.
</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label"> <label class="form-label">Chunk Size</label>
Chunk Size
</label>
<select name="chunk_size" <select name="chunk_size"
class="form-select bg-dark text-light border-secondary" class="form-select bg-dark text-light border-secondary"
required> required>
@@ -44,14 +53,18 @@
{% endfor %} {% endfor %}
</select> </select>
<div class="form-text text-secondary"> <div class="form-text text-secondary">
Größere Werte = weniger Chunks, mehr Kontext pro Chunk. Maximale Wortanzahl pro Chunk.
<br><br>
<strong>Kleinere Werte:</strong> Mehr Chunks, höhere Granularität, präziseres Retrieval.
<br>
<strong>Größere Werte:</strong> Weniger Chunks, mehr Kontext pro Treffer.
<br><br>
Empfehlung für Produkt- und Wissensdaten: 6001000.
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label"> <label class="form-label">Chunk Overlap</label>
Chunk Overlap
</label>
<select name="chunk_overlap" <select name="chunk_overlap"
class="form-select bg-dark text-light border-secondary" class="form-select bg-dark text-light border-secondary"
required> required>
@@ -62,64 +75,83 @@
{% endfor %} {% endfor %}
</select> </select>
<div class="form-text text-secondary"> <div class="form-text text-secondary">
Überlappung zwischen Chunks zur Kontextstabilisierung. Anzahl überlappender Wörter zwischen zwei Chunks.
<br><br>
Verhindert Kontextabbrüche an Chunk-Grenzen.
<br>
Typischer Bereich: 1020 % der Chunk Size.
</div> </div>
</div> </div>
<!-- ===================== --> <!-- ================================================= -->
<!-- Embedding Section --> <!-- EMBEDDING -->
<!-- ===================== --> <!-- ================================================= -->
<div class="col-12 mt-4"> <div class="col-12 mt-5">
<h5 class="text-info">Embedding</h5> <h5 class="text-info">Embedding-Konfiguration</h5>
<hr class="border-secondary"> <hr class="border-secondary">
<p class="text-secondary small">
Definiert das Modell zur Vektorisierung der Textabschnitte.
Embeddings bestimmen die semantische Ähnlichkeitsberechnung im FAISS-Index.
</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label"> <label class="form-label">Embedding Model</label>
Embedding Model
</label>
<select name="embedding_model" <select name="embedding_model"
class="form-select bg-dark text-light border-secondary" class="form-select bg-dark text-light border-secondary"
required> required>
<option value="all-MiniLM-L6-v2"> <option value="all-MiniLM-L6-v2">
all-MiniLM-L6-v2 (384) all-MiniLM-L6-v2 (384 Dimensionen)
</option> </option>
</select> </select>
<div class="form-text text-secondary">
Das Embedding-Modell erzeugt numerische Vektoren aus Text.
Modellwechsel erfordert zwingend einen Global Reindex.
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label"> <label class="form-label">Embedding Dimension</label>
Embedding Dimension
</label>
<input type="number" <input type="number"
name="embedding_dimension" name="embedding_dimension"
value="384" value="384"
class="form-control bg-dark text-light border-secondary" class="form-control bg-dark text-light border-secondary"
readonly> readonly>
<div class="form-text text-secondary"> <div class="form-text text-secondary">
Muss mit dem Embedding-Modell übereinstimmen. Muss exakt zur Dimension des gewählten Embedding-Modells passen.
Falsche Werte führen zu inkonsistentem Vektorindex.
</div> </div>
</div> </div>
<!-- ===================== --> <!-- ================================================= -->
<!-- Scoring Section --> <!-- SCORING -->
<!-- ===================== --> <!-- ================================================= -->
<div class="col-12 mt-4"> <div class="col-12 mt-5">
<h5 class="text-info">Scoring</h5> <h5 class="text-info">Scoring & Retrieval</h5>
<hr class="border-secondary"> <hr class="border-secondary">
<p class="text-secondary small">
Definiert die Bewertungslogik für Retrieval-Ergebnisse.
Änderungen wirken sich direkt auf die Gewichtung von Keyword- und
Vektor-Treffern aus.
</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label"> <label class="form-label">Scoring Version</label>
Scoring Version
</label>
<input type="number" <input type="number"
name="scoring_version" name="scoring_version"
value="1" value="1"
class="form-control bg-dark text-light border-secondary" class="form-control bg-dark text-light border-secondary"
readonly> readonly>
<div class="form-text text-secondary"> <div class="form-text text-secondary">
Erhöhung erzwingt Global Reindex. Versionskennzeichnung der Scoring-Logik.
<br><br>
Eine Erhöhung erzwingt:
<ul class="mt-2">
<li>Neuaufbau des NDJSON-Index</li>
<li>Vollständigen FAISS-Rebuild</li>
</ul>
Gewährleistet reproduzierbare Retrieval-Ergebnisse.
</div> </div>
</div> </div>
@@ -139,7 +171,9 @@
</div> </div>
<div class="mt-4 small text-secondary"> <div class="mt-4 small text-secondary">
Hinweis: Änderungen am Indexierungsprofil wirken sich auf die Struktur des Governance-Hinweis:
Vektor-Indexes aus. Nach Aktivierung ist ein vollständiger Reindex erforderlich. Ein Indexierungsprofil ist versioniert und deterministisch.
Änderungen sollten nur geplant erfolgen, da sie die komplette
Wissensstruktur neu generieren.
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -4,171 +4,186 @@
{% block body %} {% block body %}
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="container-fluid">
<h1 class="h3">System Prompt</h1>
</div>
{# ============================= #}
{# Flash Messages #}
{# ============================= #}
{% for message in app.flashes('success') %}
<div class="alert alert-success">{{ message }}</div>
{% endfor %}
{% for message in app.flashes('danger') %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{# ============================= #}
{# Versionen Übersicht #}
{# ============================= #}
<div class="card bg-black border-secondary">
<div class="card-body">
<h5 class="text-info mb-3">Versionen</h5>
<table class="table table-dark table-striped table-hover align-middle">
<thead class="table-secondary text-dark">
<tr>
<th>Version</th>
<th>Status</th>
<th>Kommentar</th>
<th>Erstellt</th>
<th class="text-end">Aktionen</th>
</tr>
</thead>
<tbody>
{% for p in all %}
<tr>
<td>v{{ p.version }}</td>
<td>
{% if p.active %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-dark border border-secondary">
Inaktiv
</span>
{% endif %}
</td>
<td>{{ p.comment ?? '-' }}</td>
<td>{{ p.createdAt|date('d.m.Y H:i:s') }}</td>
<td class="text-end">
{% if not p.active and is_granted('ROLE_SUPER_ADMIN') %}
{# Aktivieren #}
<form method="post"
action="{{ path('admin_system_prompt_activate', {id: p.id}) }}"
class="d-inline"
onsubmit="return confirm('Diese Version aktivieren?');">
<input type="hidden"
name="_token"
value="{{ csrf_token('activate_system_prompt_' ~ p.id) }}">
<button class="btn btn-sm btn-outline-success me-2">
Aktivieren
</button>
</form>
{# Löschen #}
<form method="post"
action="{{ path('admin_system_prompt_delete', {id: p.id}) }}"
class="d-inline"
onsubmit="return confirm('Version wirklich löschen?');">
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_system_prompt_' ~ p.id) }}">
<button class="btn btn-sm btn-outline-danger">
Löschen
</button>
</form>
{% else %}
<span class="text-secondary">—</span>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center text-secondary py-4">
Keine Versionen vorhanden.
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- HEADER -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">System Prompt Verwaltung</h1>
</div> </div>
</div>
<div class="mt-4 small text-secondary"> <!-- Flash Messages -->
Hinweis: Der aktive System Prompt beeinflusst das Antwortverhalten {% for message in app.flashes('success') %}
des LLM unmittelbar. Änderungen sollten dokumentiert und versioniert erfolgen. <div class="alert alert-success">{{ message }}</div>
</div> {% endfor %}
{% for message in app.flashes('danger') %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{# ============================= #} <div class="row g-4">
{# Neue Version erstellen #}
{# ============================= #}
<div class="card bg-black border-secondary mb-5 text-light"> <!-- ===================================================== -->
<div class="card-body"> <!-- LEFT SIDE Versionen (50%) -->
<!-- ===================================================== -->
<h5 class="text-info mb-3">Neue Version erstellen</h5> <div class="col-lg-6">
<form method="post"> <div class="card bg-black border-secondary text-light h-100">
<input type="hidden" <div class="card-body">
name="_token"
value="{{ csrf_token('create_system_prompt') }}"> <h5 class="text-info mb-3">Versionen</h5>
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle">
<thead class="table-secondary text-dark">
<tr>
<th>Version</th>
<th>Status</th>
<th>Kommentar</th>
<th>Erstellt</th>
<th class="text-end">Aktionen</th>
</tr>
</thead>
<tbody>
{% for p in all %}
<tr>
<td>v{{ p.version }}</td>
<td>
{% if p.active %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-dark border border-secondary">
Inaktiv
</span>
{% endif %}
</td>
<td>{{ p.comment ?? '-' }}</td>
<td>{{ p.createdAt|date('d.m.Y H:i:s') }}</td>
<td class="text-end">
{% if not p.active and is_granted('ROLE_SUPER_ADMIN') %}
<form method="post"
action="{{ path('admin_system_prompt_activate', {id: p.id}) }}"
class="d-inline"
onsubmit="return confirm('Diese Version aktivieren?');">
<input type="hidden"
name="_token"
value="{{ csrf_token('activate_system_prompt_' ~ p.id) }}">
<button class="btn btn-sm btn-outline-success me-2">
Aktivieren
</button>
</form>
<form method="post"
action="{{ path('admin_system_prompt_delete', {id: p.id}) }}"
class="d-inline"
onsubmit="return confirm('Version wirklich löschen?');">
<input type="hidden"
name="_token"
value="{{ csrf_token('delete_system_prompt_' ~ p.id) }}">
<button class="btn btn-sm btn-outline-danger">
Löschen
</button>
</form>
{% else %}
<span class="text-secondary">—</span>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center text-secondary py-4">
Keine Versionen vorhanden.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<hr class="border-secondary mt-4">
<div class="small text-secondary">
<strong>Governance-Hinweis:</strong><br>
Der aktive System Prompt beeinflusst das globale Antwortverhalten des LLM unmittelbar.
Änderungen sollten dokumentiert, versioniert und nachvollziehbar erfolgen.
</div>
<div class="mb-3">
<label class="form-label">Kommentar (optional)</label>
<input type="text"
name="comment"
class="form-control bg-dark text-light border-secondary"
placeholder="Warum wurde der Prompt geändert?">
<div class="form-text text-secondary">
Dokumentation der Änderung für spätere Nachvollziehbarkeit.
</div> </div>
</div> </div>
<div class="mb-3"> </div>
<label class="form-label">
Prompt-Inhalt <!-- ===================================================== -->
</label> <!-- RIGHT SIDE Neue Version (50%) -->
<div class="form-text text-secondary mb-2"> <!-- ===================================================== -->
Verfügbare Variable:
<code>{% verbatim %}{% now %}{% endverbatim %}</code> <div class="col-lg-6">
(aktuelles Datum/Zeit)
<div class="card bg-black border-secondary text-light h-100">
<div class="card-body">
<h5 class="text-info mb-3">Neue Version erstellen</h5>
<form method="post">
<input type="hidden"
name="_token"
value="{{ csrf_token('create_system_prompt') }}">
<div class="mb-3">
<label class="form-label">Kommentar (optional)</label>
<input type="text"
name="comment"
class="form-control bg-dark text-light border-secondary"
placeholder="Warum wurde der Prompt geändert?">
<div class="form-text text-secondary">
Dokumentation der Änderung für spätere Nachvollziehbarkeit.
</div>
</div>
<div class="mb-3">
<label class="form-label">Prompt-Inhalt</label>
<div class="form-text text-secondary mb-2">
Verfügbare Variable:
<code>{% verbatim %}{% now %}{% endverbatim %}</code>
(aktuelles Datum / Zeit)
</div>
<textarea name="content"
rows="18"
class="form-control bg-dark text-light border-secondary"
required>{{ active ? active.content : '' }}</textarea>
</div>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<div class="d-flex justify-content-end">
<button class="btn btn-outline-info">
Neue Version speichern
</button>
</div>
{% endif %}
</form>
</div> </div>
<textarea name="content"
rows="16"
class="form-control bg-dark text-light border-secondary"
required>{{ active ? active.content : '' }}</textarea>
</div> </div>
{% if is_granted('ROLE_SUPER_ADMIN') %} </div>
<div class="d-flex justify-content-end">
<button class="btn btn-outline-info">
Neue Version speichern
</button>
</div>
{% endif %}
</form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}