From ad5bd4367e12bd1cf251653c24dea4b2a861701f Mon Sep 17 00:00:00 2001 From: root <root@localhost> Date: Wed, 12 Mar 2025 13:33:57 +0100 Subject: [PATCH] Verbesserte Dashboard-Ansicht mit Echtzeit-Informationen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neue übersichtliche Dashboard-Ansicht für aktive und wartende Jobs - Fortschrittsbalken für laufende Druckaufträge mit verbleibender Zeit - Liste der verfügbaren Drucker mit schnellem Zugriff auf Auftragserstellung - Freischaltungs-Funktionalität für wartende Jobs direkt vom Dashboard - Automatische Aktualisierung der Daten alle 60 Sekunden 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --- backend/templates/dashboard.html | 260 +++++++++++++++++++++++++++++-- 1 file changed, 248 insertions(+), 12 deletions(-) diff --git a/backend/templates/dashboard.html b/backend/templates/dashboard.html index 4cfb6ca..6f5f3af 100644 --- a/backend/templates/dashboard.html +++ b/backend/templates/dashboard.html @@ -34,15 +34,20 @@ <div class="col-md-6 mb-4"> <div class="card"> <div class="card-header"> - <h5 class="mb-0">API-Test: GET /api/me</h5> + <h5 class="mb-0">Aktive Druckaufträge</h5> </div> <div class="card-body"> - <form class="api-form" data-url="/api/me" data-method="GET" data-response="meResponse"> - <button type="submit" class="btn btn-primary">API aufrufen</button> + <form class="api-form mb-3" data-url="/api/jobs" data-method="GET" data-response="jobsResponse"> + <button type="submit" class="btn btn-primary">Aktualisieren</button> </form> - <div class="mt-3"> - <h6>Antwort:</h6> - <pre class="api-response" id="meResponse"></pre> + + <div id="activeJobsContainer"> + <div class="alert alert-info">Lade Druckaufträge...</div> + </div> + + <div class="d-none"> + <h6>API-Antwort:</h6> + <pre class="api-response" id="jobsResponse"></pre> </div> </div> </div> @@ -51,18 +56,249 @@ <div class="col-md-6 mb-4"> <div class="card"> <div class="card-header"> - <h5 class="mb-0">API-Test: GET /api/test</h5> + <h5 class="mb-0">Verfügbare Drucker</h5> </div> <div class="card-body"> - <form class="api-form" data-url="/api/test" data-method="GET" data-response="testResponse"> - <button type="submit" class="btn btn-primary">API aufrufen</button> + <form class="api-form mb-3" data-url="/api/printers" data-method="GET" data-response="printersResponse"> + <button type="submit" class="btn btn-primary">Aktualisieren</button> </form> - <div class="mt-3"> - <h6>Antwort:</h6> - <pre class="api-response" id="testResponse"></pre> + + <div id="availablePrintersContainer"> + <div class="alert alert-info">Lade Drucker...</div> + </div> + + <div class="d-none"> + <h6>API-Antwort:</h6> + <pre class="api-response" id="printersResponse"></pre> </div> </div> </div> </div> </div> + +<!-- Job freischalten Modal --> +<div class="modal fade" id="approveJobModal" tabindex="-1"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">Druckauftrag freischalten</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + <p>Möchten Sie diesen Druckauftrag jetzt freischalten und starten?</p> + <p><strong>Hinweis:</strong> Der Drucker muss verfügbar sein, damit der Auftrag gestartet werden kann.</p> + <form id="approveJobForm" class="api-form" data-method="POST" data-response="approveJobResponse" data-reload="true"> + <input type="hidden" id="approveJobId" name="jobId"> + </form> + <div class="mt-3"> + <h6>Antwort:</h6> + <pre class="api-response" id="approveJobResponse"></pre> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button> + <button type="submit" form="approveJobForm" class="btn btn-success">Freischalten</button> + </div> + </div> + </div> +</div> +{% endblock %} + +{% block scripts %} +<script> + document.addEventListener('DOMContentLoaded', function() { + // Aufträge und Drucker laden + document.querySelector('form[data-url="/api/jobs"]').dispatchEvent(new Event('submit')); + document.querySelector('form[data-url="/api/printers"]').dispatchEvent(new Event('submit')); + + // Tabellen aktualisieren, wenn Daten geladen werden + const jobsResponse = document.getElementById('jobsResponse'); + const printersResponse = document.getElementById('printersResponse'); + + // Observer für Jobs + const jobsObserver = new MutationObserver(function(mutations) { + try { + const jobs = JSON.parse(jobsResponse.textContent); + updateActiveJobs(jobs); + } catch (e) { + console.error('Fehler beim Parsen der Auftrags-Daten:', e); + } + }); + + jobsObserver.observe(jobsResponse, { childList: true, characterData: true, subtree: true }); + + // Observer für Drucker + const printersObserver = new MutationObserver(function(mutations) { + try { + const printers = JSON.parse(printersResponse.textContent); + updateAvailablePrinters(printers); + } catch (e) { + console.error('Fehler beim Parsen der Drucker-Daten:', e); + } + }); + + printersObserver.observe(printersResponse, { childList: true, characterData: true, subtree: true }); + + // Approve-Modal vorbereiten + document.getElementById('approveJobModal').addEventListener('show.bs.modal', function(event) { + const button = event.relatedTarget; + const jobId = button.getAttribute('data-job-id'); + + document.getElementById('approveJobId').value = jobId; + document.getElementById('approveJobForm').setAttribute('data-url', `/api/jobs/${jobId}/approve`); + }); + + // Automatische Aktualisierung alle 60 Sekunden + setInterval(() => { + document.querySelector('form[data-url="/api/jobs"]').dispatchEvent(new Event('submit')); + document.querySelector('form[data-url="/api/printers"]').dispatchEvent(new Event('submit')); + }, 60000); + }); + + function updateActiveJobs(jobs) { + const container = document.getElementById('activeJobsContainer'); + + // Filter für aktive und wartende Jobs + const activeJobs = jobs.filter(job => !job.aborted && job.remainingMinutes > 0 && !job.waitingApproval); + const waitingJobs = jobs.filter(job => !job.aborted && job.waitingApproval); + + if (activeJobs.length === 0 && waitingJobs.length === 0) { + container.innerHTML = '<div class="alert alert-info">Keine aktiven Druckaufträge vorhanden.</div>'; + return; + } + + let html = ''; + + // Aktive Jobs anzeigen + if (activeJobs.length > 0) { + html += '<h6 class="mt-3">Laufende Aufträge</h6>'; + html += '<div class="list-group mb-3">'; + + activeJobs.forEach(job => { + // Prozentsatz der abgelaufenen Zeit berechnen + const totalDuration = job.durationInMinutes; + const elapsed = totalDuration - job.remainingMinutes; + const percentage = Math.round((elapsed / totalDuration) * 100); + + html += ` + <div class="list-group-item"> + <div class="d-flex justify-content-between"> + <div> + <strong>Job ${job.id.substring(0, 8)}...</strong> (${job.durationInMinutes} Min) + <div class="small text-muted">Verbleibend: ${job.remainingMinutes} Min</div> + </div> + <div> + <span class="badge bg-warning">Aktiv</span> + </div> + </div> + <div class="progress mt-2" style="height: 10px;"> + <div class="progress-bar progress-bar-striped progress-bar-animated" + role="progressbar" + style="width: ${percentage}%;" + aria-valuenow="${percentage}" + aria-valuemin="0" + aria-valuemax="100"> + ${percentage}% + </div> + </div> + </div> + `; + }); + + html += '</div>'; + } + + // Wartende Jobs anzeigen + if (waitingJobs.length > 0) { + html += '<h6 class="mt-3">Wartende Aufträge</h6>'; + html += '<div class="list-group">'; + + waitingJobs.forEach(job => { + html += ` + <div class="list-group-item"> + <div class="d-flex justify-content-between"> + <div> + <strong>Job ${job.id.substring(0, 8)}...</strong> (${job.durationInMinutes} Min) + <div class="small text-muted">Drucker: ${job.socketId.substring(0, 8)}...</div> + </div> + <div> + <span class="badge bg-info">Wartet</span> + </div> + </div> + <div class="mt-2"> + <button type="button" class="btn btn-sm btn-success" + data-bs-toggle="modal" + data-bs-target="#approveJobModal" + data-job-id="${job.id}"> + Freischalten + </button> + </div> + </div> + `; + }); + + html += '</div>'; + } + + container.innerHTML = html; + } + + function updateAvailablePrinters(printers) { + const container = document.getElementById('availablePrintersContainer'); + + // Filter für verfügbare Drucker + const availablePrinters = printers.filter(printer => printer.status === 0); + + if (availablePrinters.length === 0) { + container.innerHTML = '<div class="alert alert-warning">Keine verfügbaren Drucker gefunden.</div>'; + return; + } + + let html = '<div class="list-group">'; + + availablePrinters.forEach(printer => { + html += ` + <div class="list-group-item"> + <div class="d-flex justify-content-between align-items-center"> + <div> + <strong>${printer.name}</strong> + <div class="small text-muted">${printer.description}</div> + </div> + <div> + <span class="badge bg-success">Verfügbar</span> + </div> + </div> + <div class="mt-2"> + <a href="/admin/jobs" class="btn btn-sm btn-primary">Auftrag erstellen</a> + </div> + </div> + `; + }); + + html += '</div>'; + + container.innerHTML = html; + + // Gesamtstatistik hinzufügen + const busyPrinters = printers.filter(printer => printer.status === 1).length; + const totalPrinters = printers.length; + + if (totalPrinters > 0) { + const statsHtml = ` + <div class="mt-3"> + <div class="d-flex justify-content-between"> + <small>Verfügbar: ${availablePrinters.length} / ${totalPrinters}</small> + <small>Belegt: ${busyPrinters} / ${totalPrinters}</small> + </div> + <div class="progress mt-1" style="height: 5px;"> + <div class="progress-bar bg-success" style="width: ${(availablePrinters.length / totalPrinters) * 100}%"></div> + <div class="progress-bar bg-warning" style="width: ${(busyPrinters / totalPrinters) * 100}%"></div> + </div> + </div> + `; + + container.innerHTML += statsHtml; + } + } +</script> {% endblock %} \ No newline at end of file