optimize tag and rebuilding

This commit is contained in:
team2
2026-02-23 08:51:21 +01:00
parent 8c58d6777d
commit dceeaeee52
8 changed files with 678 additions and 167 deletions

View File

@@ -4,6 +4,76 @@
{% block body %}
{# ============================================= #}
{# Tag-Rebuild Status (Echte Live-Anzeige) #}
{# ============================================= #}
<div id="rebuild-status"></div>
<script>
let polling = null;
function renderStatus(status) {
const el = document.getElementById('rebuild-status');
if (!status) {
el.innerHTML = '';
return;
}
if (status === 'RUNNING') {
el.innerHTML = `
<div class="alert alert-info d-flex justify-content-between align-items-center">
<div><strong>Rebuild läuft…</strong></div>
<div class="spinner-border spinner-border-sm"></div>
</div>
`;
} else if (status === 'QUEUED') {
el.innerHTML = `
<div class="alert alert-secondary">
Rebuild in Warteschlange…
</div>
`;
} else if (status === 'COMPLETED') {
el.innerHTML = `
<div class="alert alert-success">
Rebuild abgeschlossen.
</div>
`;
stopPolling();
} else if (status === 'FAILED') {
el.innerHTML = `
<div class="alert alert-danger">
Rebuild fehlgeschlagen.
</div>
`;
stopPolling();
}
}
function checkStatus() {
fetch('{{ path('admin_tags_status') }}')
.then(r => r.json())
.then(data => renderStatus(data.status))
.catch(() => stopPolling());
}
function startPolling() {
polling = setInterval(checkStatus, 2000);
}
function stopPolling() {
if (polling) {
clearInterval(polling);
polling = null;
}
}
// Start polling sofort
checkStatus();
startPolling();
</script>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
Tags für Dokument
@@ -33,8 +103,8 @@
<div class="d-flex flex-wrap gap-2">
{% for tag in document.tags %}
<span class="badge bg-info text-dark px-3 py-2">
{{ tag.label }}
</span>
{{ tag.label }}
</span>
{% endfor %}
</div>
{% endif %}

View File

@@ -5,80 +5,179 @@
{% block body %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">Tags</h1>
<h1 class="h3 mb-0">Tag-Management</h1>
</div>
{# ========================================================= #}
{# Flash Messages #}
{# ========================================================= #}
{% for message in app.flashes('success') %}
<div class="alert alert-success">{{ message }}</div>
<div class="alert alert-success shadow-sm">{{ message }}</div>
{% endfor %}
{% for message in app.flashes('danger') %}
<div class="alert alert-danger">{{ message }}</div>
<div class="alert alert-danger shadow-sm">{{ message }}</div>
{% endfor %}
<div class="card bg-black border-secondary mb-4 text-light">
{# ========================================================= #}
{# LIVE REBUILD STATUS (SSE) #}
{# ========================================================= #}
<div id="rebuild-status">
{% if latestJob %}
<div class="alert alert-secondary shadow-sm">
Status wird geladen…
</div>
{% endif %}
</div>
<script>
const statusBox = document.getElementById('rebuild-status');
const source = new EventSource("{{ path('admin_tags_rebuild_stream') }}");
source.onmessage = function (event) {
const data = JSON.parse(event.data);
let html = '';
if (data.status === '{{ statusRunning }}') {
html = `
<div class="alert alert-info shadow-sm d-flex justify-content-between align-items-center">
<div>
<strong>Rebuild läuft</strong><br>
${data.startedAt ? 'Gestartet: ' + new Date(data.startedAt).toLocaleString() : ''}
</div>
<div class="spinner-border spinner-border-sm"></div>
</div>
`;
}
else if (data.status === '{{ statusQueued }}') {
html = `
<div class="alert alert-secondary shadow-sm">
<strong>Rebuild in Warteschlange</strong>
</div>
`;
}
else if (data.status === '{{ statusCompleted }}') {
html = `
<div class="alert alert-success shadow-sm">
<strong>Rebuild erfolgreich abgeschlossen</strong>
</div>
`;
}
else if (data.status === '{{ statusFailed }}') {
html = `
<div class="alert alert-danger shadow-sm">
<strong>Rebuild fehlgeschlagen</strong><br>
${data.error ? '<code>' + data.error + '</code>' : ''}
</div>
`;
}
statusBox.innerHTML = html;
};
source.onerror = function () {
console.warn('SSE Verbindung verloren');
};
</script>
{# ========================================================= #}
{# Create Tag Card #}
{# ========================================================= #}
<div class="card bg-black border-secondary text-light mb-4 shadow-sm">
<div class="card-body">
<h5 class="text-info mb-3">Neuen Tag erstellen</h5>
<form method="post" action="{{ path('admin_tags_create') }}" class="row g-2">
<form method="post" action="{{ path('admin_tags_create') }}" class="row g-3">
<input type="hidden" name="_token" value="{{ csrf_token('admin_tag_create') }}"/>
<div class="col-md-3">
<input class="form-control form-control-sm" name="label" placeholder="Label (z.B. Testomat 808)" required />
<label class="form-label small text-muted">Label</label>
<input class="form-control form-control-sm"
name="label"
placeholder="z. B. Testomat 808"
required />
</div>
<div class="col-md-3">
<input class="form-control form-control-sm" name="slug" placeholder="Slug (z.B. testomat-808)" required />
<label class="form-label small text-muted">Slug</label>
<input class="form-control form-control-sm"
name="slug"
placeholder="z. B. testomat-808"
required />
</div>
<div class="col-md-4">
<input class="form-control form-control-sm" name="description" placeholder="Beschreibung (optional)" />
<label class="form-label small text-muted">Beschreibung</label>
<input class="form-control form-control-sm"
name="description"
placeholder="Optional" />
</div>
<div class="col-md-2 d-grid">
<button class="btn btn-sm btn-outline-info" type="submit">Anlegen</button>
</div>
<div class="col-12">
<small class="text-light">
Hinweis: Nach Änderungen an Tags/Zuweisungen bitte <code>bin/console mto:agent:tags:rebuild</code> ausführen.
</small>
<div class="col-md-2 d-grid align-items-end">
<button class="btn btn-sm btn-outline-info">
Anlegen
</button>
</div>
</form>
</div>
</div>
<div class="card bg-black border-secondary text-light">
{# ========================================================= #}
{# Tag Table #}
{# ========================================================= #}
<div class="card bg-black border-secondary text-light shadow-sm">
<div class="card-body p-0">
<div class="px-3 py-2 border-bottom border-secondary">
<strong>Vorhandene Tags</strong>
<span class="text-muted small ms-2">
{{ tags|length }} Einträge
</span>
</div>
<table class="table table-dark table-striped table-hover mb-0 align-middle">
<thead class="table-secondary text-dark">
<tr>
<th>Label</th>
<th>Slug</th>
<th>Beschreibung</th>
<th class="text-end">Aktion</th>
<th style="width: 25%">Label</th>
<th style="width: 25%">Slug</th>
<th style="width: 35%">Beschreibung</th>
<th class="text-end" style="width: 15%">Aktion</th>
</tr>
</thead>
<tbody>
{% for tag in tags %}
<tr>
<td>{{ tag.label }}</td>
<td class="fw-semibold">{{ tag.label }}</td>
<td><code>{{ tag.slug }}</code></td>
<td class="text-light">{{ tag.description ?: '' }}</td>
<td>{{ tag.description ?: '-' }}</td>
<td class="text-end">
<form method="post" action="{{ path('admin_tags_delete', {id: tag.id}) }}" style="display:inline">
<input type="hidden" name="_token" value="{{ csrf_token('admin_tag_delete_' ~ tag.id) }}"/>
<button class="btn btn-sm btn-outline-danger" type="submit"
onclick="return confirm('Tag wirklich löschen? (Zuweisungen werden mit gelöscht)')">
<form method="post"
action="{{ path('admin_tags_delete', {id: tag.id}) }}">
<input type="hidden"
name="_token"
value="{{ csrf_token('admin_tag_delete_' ~ tag.id) }}"/>
<button class="btn btn-sm btn-outline-danger"
onclick="return confirm('Tag wirklich löschen? Zuweisungen werden entfernt.')">
Löschen
</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="text-light p-3">Noch keine Tags vorhanden.</td></tr>
<tr>
<td colspan="4" class="p-4 text-center text-muted">
Noch keine Tags vorhanden.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>