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