"feat: Update admin and printer templates with new design"

This commit is contained in:
Till Tomczak 2025-05-26 12:50:13 +02:00
parent e122fe0cc9
commit 2406aedf38
3 changed files with 427 additions and 70 deletions

View File

@ -1,15 +1,21 @@
import os
import sys
import logging
import threading
import time
import subprocess
import socket
import json
import secrets
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Any, Union
from functools import wraps
from typing import Optional, Dict, List, Tuple, Any, Union
from flask import Flask, request, jsonify, session, render_template, redirect, url_for, flash, Response
from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, send_file, Response
from flask_login import LoginManager, login_user, logout_user, login_required, current_user, UserMixin
from werkzeug.security import check_password_hash, generate_password_hash
from werkzeug.utils import secure_filename
import sqlalchemy.exc
import sqlalchemy
from PyP100 import PyP110
@ -119,6 +125,82 @@ def job_owner_required(f):
return f(job_id, *args, **kwargs)
return decorated_function
def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
"""
Überprüft den Status eines Druckers über Ping mit Timeout.
Args:
ip_address: IP-Adresse des Druckers
timeout: Timeout in Sekunden (Standard: 7)
Returns:
Tuple[str, bool]: (Status, Aktiv) - Status ist "online" oder "offline", Aktiv ist True/False
"""
if not ip_address:
return "offline", False
try:
# Windows-spezifischer Ping-Befehl mit Timeout
if os.name == 'nt': # Windows
cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address]
else: # Unix/Linux/macOS
cmd = ['ping', '-c', '1', '-W', str(timeout), ip_address]
# Ping ausführen mit Timeout
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout + 1 # Zusätzlicher Timeout für subprocess
)
# Erfolgreicher Ping (Return Code 0)
if result.returncode == 0:
return "online", True
else:
return "offline", False
except subprocess.TimeoutExpired:
printers_logger.warning(f"Ping-Timeout für Drucker {ip_address} nach {timeout} Sekunden")
return "offline", False
except Exception as e:
printers_logger.error(f"Fehler beim Ping für Drucker {ip_address}: {str(e)}")
return "offline", False
def check_multiple_printers_status(printers: List[Dict], timeout: int = 7) -> Dict[int, Tuple[str, bool]]:
"""
Überprüft den Status mehrerer Drucker parallel mit Timeout.
Args:
printers: Liste von Drucker-Dictionaries mit 'id' und 'ip_address'
timeout: Timeout in Sekunden pro Drucker (Standard: 7)
Returns:
Dict[int, Tuple[str, bool]]: Dictionary mit Drucker-ID als Key und (Status, Aktiv) als Value
"""
results = {}
# Parallel-Ausführung mit ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=min(len(printers), 10)) as executor:
# Futures für alle Drucker erstellen
future_to_printer = {
executor.submit(check_printer_status, printer.get('ip_address'), timeout): printer
for printer in printers
}
# Ergebnisse sammeln
for future in as_completed(future_to_printer, timeout=timeout + 2):
printer = future_to_printer[future]
try:
status, active = future.result()
results[printer['id']] = (status, active)
printers_logger.info(f"Drucker {printer['name']} ({printer.get('ip_address')}): {status}")
except Exception as e:
printers_logger.error(f"Fehler bei Status-Check für Drucker {printer['name']}: {str(e)}")
results[printer['id']] = ("offline", False)
return results
# UI-Routen
@app.route("/")
def index():
@ -216,12 +298,18 @@ def admin_page():
# Benutzeranzahl
stats["total_users"] = db_session.query(User).count()
# Druckeranzahl
stats["total_printers"] = db_session.query(Printer).count()
# Druckeranzahl und Online-Status
all_printers = db_session.query(Printer).all()
stats["total_printers"] = len(all_printers)
stats["online_printers"] = len([p for p in all_printers if p.status == "online"])
# Aktive Jobs
# Aktive Jobs und Warteschlange
stats["active_jobs"] = db_session.query(Job).filter(
Job.status.in_(["scheduled", "running"])
Job.status.in_(["printing", "running"])
).count()
stats["queued_jobs"] = db_session.query(Job).filter(
Job.status == "scheduled"
).count()
# Erfolgsrate
@ -259,11 +347,55 @@ def admin_page():
# System-Informationen laden
if active_tab == 'system':
import psutil
# CPU und Memory
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
# Uptime
boot_time = psutil.boot_time()
uptime_seconds = time.time() - boot_time
uptime_days = int(uptime_seconds // 86400)
uptime_hours = int((uptime_seconds % 86400) // 3600)
uptime_minutes = int((uptime_seconds % 3600) // 60)
# Datenbank-Status
db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'instance', 'database.db')
db_size = 0
if os.path.exists(db_path):
db_size = os.path.getsize(db_path) / (1024 * 1024) # MB
# Scheduler-Status
scheduler_running = False
scheduler_jobs = 0
try:
from utils.job_scheduler import scheduler
scheduler_running = scheduler.running
if hasattr(scheduler, 'get_jobs'):
scheduler_jobs = len(scheduler.get_jobs())
except:
pass
# Nächster Job
next_job = db_session.query(Job).filter(
Job.status == "scheduled"
).order_by(Job.created_at.asc()).first()
next_job_time = "Keine geplanten Jobs"
if next_job:
next_job_time = next_job.created_at.strftime("%d.%m.%Y %H:%M")
system_info = {
"cpu": psutil.cpu_percent(),
"memory": psutil.virtual_memory().percent,
"disk": psutil.disk_usage('/').percent,
"uptime": get_system_uptime_days()
"cpu_usage": round(cpu_percent, 1),
"memory_usage": round(memory.percent, 1),
"disk_usage": round((disk.used / disk.total) * 100, 1),
"uptime": f"{uptime_days}d {uptime_hours}h {uptime_minutes}m",
"db_size": f"{db_size:.1f} MB",
"db_connections": "Aktiv",
"scheduler_running": scheduler_running,
"scheduler_jobs": scheduler_jobs,
"next_job": next_job_time
}
# Logs laden
@ -586,36 +718,58 @@ def finish_job(job_id):
@app.route("/api/printers", methods=["GET"])
@login_required
def get_printers():
"""Gibt alle Drucker mit aktuellem Status zurück (mit Ping-Check und 7-Sekunden-Timeout)."""
db_session = get_db_session()
try:
printers = db_session.query(Printer).all()
# Optimierte Drucker-Liste mit schneller Status-Bestimmung
# Drucker-Daten für Status-Check vorbereiten
printer_data = []
for printer in printers:
printer_data.append({
'id': printer.id,
'name': printer.name,
'ip_address': printer.ip_address,
'location': printer.location
})
# Status aller Drucker parallel überprüfen mit 7-Sekunden-Timeout
printers_logger.info(f"Starte Drucker-Status-Check für {len(printer_data)} Drucker")
status_results = check_multiple_printers_status(printer_data, timeout=7)
# Drucker-Liste mit aktuellem Status erstellen
printer_list = []
for printer in printers:
# Bestimme Status basierend auf hardkodierten Druckern
printer_config = PRINTERS.get(printer.name)
if printer_config:
status = "available" # Drucker verfügbar
active = True
if printer.id in status_results:
status, active = status_results[printer.id]
# Mapping für Frontend-Kompatibilität
if status == "online":
frontend_status = "available"
else:
frontend_status = "offline"
else:
status = "offline"
# Fallback falls kein Ergebnis vorliegt
frontend_status = "offline"
active = False
# Aktualisiere Status in der Datenbank
printer.status = status
# Status in der Datenbank aktualisieren
printer.status = frontend_status
printer.active = active
printer_data = printer.to_dict()
printer_data["status"] = status
printer_data["status"] = frontend_status
printer_data["active"] = active
printer_data["last_checked"] = datetime.now().isoformat()
printer_list.append(printer_data)
# Speichere Updates
db_session.commit()
db_session.close()
online_count = len([p for p in printer_list if p["status"] == "available"])
printers_logger.info(f"Drucker-Status-Check abgeschlossen: {online_count} von {len(printer_list)} Drucker verfügbar")
return jsonify({
"printers": printer_list
})
@ -739,26 +893,35 @@ def get_activity():
@app.route("/api/printers/status", methods=["GET"])
@login_required
def get_printers_status():
"""Gibt den Status aller Drucker zurück - optimiert für schnelle Antwort."""
"""Gibt den Status aller Drucker zurück mit echtem Ping-Check und 7-Sekunden-Timeout."""
db_session = get_db_session()
try:
printers = db_session.query(Printer).all()
# Schnelle Status-Bestimmung basierend auf hardkodierten Druckern
# Drucker-Daten für Status-Check vorbereiten
printer_data = []
for printer in printers:
printer_data.append({
'id': printer.id,
'name': printer.name,
'ip_address': printer.ip_address,
'location': printer.location
})
# Status aller Drucker parallel überprüfen mit 7-Sekunden-Timeout
printers_logger.info(f"Starte Status-Check für {len(printer_data)} Drucker mit 7-Sekunden-Timeout")
status_results = check_multiple_printers_status(printer_data, timeout=7)
# Ergebnisse zusammenstellen und Datenbank aktualisieren
status_data = []
for printer in printers:
# Bestimme Status basierend auf IP-Adresse aus der Konfiguration
printer_config = PRINTERS.get(printer.name)
if printer_config:
# Drucker ist in der Konfiguration -> als online betrachten
status = "online"
active = True
if printer.id in status_results:
status, active = status_results[printer.id]
else:
# Drucker nicht in Konfiguration -> offline
status = "offline"
active = False
# Fallback falls kein Ergebnis vorliegt
status, active = "offline", False
# Aktualisiere den Status in der Datenbank für Konsistenz
# Status in der Datenbank aktualisieren
printer.status = status
printer.active = active
@ -768,13 +931,16 @@ def get_printers_status():
"status": status,
"active": active,
"ip_address": printer.ip_address,
"location": printer.location
"location": printer.location,
"last_checked": datetime.now().isoformat()
})
# Speichere die aktualisierten Status
db_session.commit()
db_session.close()
printers_logger.info(f"Status-Check abgeschlossen: {len([s for s in status_data if s['status'] == 'online'])} von {len(status_data)} Drucker online")
return jsonify(status_data)
except Exception as e:
db_session.rollback()

View File

@ -442,7 +442,7 @@
{% if job.status == 'queued' %}
<button class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h8m2-10h.01M5 20h14a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h8m2-10h.01M5 20h14a2 2 0 002-2V7a2 2 0 00-2-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2zM9 9h6v6H9V9z"/>
</svg>
</button>
{% endif %}
@ -486,15 +486,15 @@
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Uptime:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.uptime }}</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.uptime if system_info.uptime else 'Unbekannt' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">CPU:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu_usage }}%</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu_usage if system_info.cpu_usage else 0 }}%</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">RAM:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory_usage }}%</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory_usage if system_info.memory_usage else 0 }}%</span>
</div>
</div>
</div>
@ -510,11 +510,11 @@
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Größe:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_size }}</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_size if system_info.db_size else 'Unbekannt' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Verbindungen:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_connections }}</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_connections if system_info.db_connections else 'Unbekannt' }}</span>
</div>
</div>
</div>
@ -530,11 +530,11 @@
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Jobs:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.scheduler_jobs }}</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.scheduler_jobs if system_info.scheduler_jobs else 0 }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Nächster Job:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.next_job }}</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.next_job if system_info.next_job else 'Keine geplanten Jobs' }}</span>
</div>
</div>
</div>
@ -545,13 +545,13 @@
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Wartung</h3>
<div class="space-y-3">
<button class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium">
<button onclick="clearCache()" class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium">
Cache leeren
</button>
<button class="w-full px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition-colors text-sm font-medium">
<button onclick="optimizeDatabase()" class="w-full px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition-colors text-sm font-medium">
Datenbank optimieren
</button>
<button class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium">
<button onclick="createBackup()" class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium">
Backup erstellen
</button>
</div>
@ -560,13 +560,13 @@
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Konfiguration</h3>
<div class="space-y-3">
<button class="w-full px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors text-sm font-medium">
<button onclick="editSettings()" class="w-full px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors text-sm font-medium">
Einstellungen bearbeiten
</button>
<button class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium">
<button onclick="updatePrinters()" class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium">
Drucker aktualisieren
</button>
<button class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium">
<button onclick="restartSystem()" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium">
System neustarten
</button>
</div>
@ -651,28 +651,158 @@
</div>
<script>
// Admin Panel JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh für Live-Daten
setInterval(function() {
// Hier können Sie AJAX-Calls für Live-Updates hinzufügen
updateStats();
}, 30000); // Alle 30 Sekunden
// CSRF Token für AJAX-Anfragen
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Hilfsfunktion für API-Aufrufe
async function makeApiCall(url, method = 'GET', data = null) {
const options = {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
};
function updateStats() {
// Implementierung für Live-Stats Updates
console.log('Updating stats...');
if (data) {
options.body = JSON.stringify(data);
}
// Smooth Scrolling für bessere UX
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
try {
const response = await fetch(url, options);
const result = await response.json();
if (response.ok) {
showNotification(result.message || 'Aktion erfolgreich ausgeführt', 'success');
return result;
} else {
showNotification(result.error || 'Ein Fehler ist aufgetreten', 'error');
return null;
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
return null;
}
}
// Notification anzeigen
function showNotification(message, type = 'info') {
// Erstelle Notification-Element falls nicht vorhanden
let notification = document.getElementById('admin-notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'admin-notification';
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full';
document.body.appendChild(notification);
}
// Setze Farbe basierend auf Typ
const colors = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-yellow-500 text-white',
info: 'bg-blue-500 text-white'
};
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type] || colors.info}`;
notification.textContent = message;
// Zeige Notification
notification.style.transform = 'translateX(0)';
// Verstecke nach 5 Sekunden
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
}, 5000);
}
// Cache leeren
async function clearCache() {
if (confirm('Möchten Sie wirklich den Cache leeren?')) {
const result = await makeApiCall('/api/admin/cache/clear', 'POST');
if (result) {
setTimeout(() => location.reload(), 2000);
}
}
}
// Datenbank optimieren
async function optimizeDatabase() {
if (confirm('Möchten Sie wirklich die Datenbank optimieren? Dies kann einige Minuten dauern.')) {
const result = await makeApiCall('/api/admin/database/optimize', 'POST');
if (result) {
setTimeout(() => location.reload(), 2000);
}
}
}
// Backup erstellen
async function createBackup() {
if (confirm('Möchten Sie wirklich ein Backup erstellen?')) {
const result = await makeApiCall('/api/admin/backup/create', 'POST');
}
}
// Drucker aktualisieren
async function updatePrinters() {
if (confirm('Möchten Sie alle Drucker-Verbindungen aktualisieren?')) {
const result = await makeApiCall('/api/admin/printers/update', 'POST');
if (result) {
setTimeout(() => location.reload(), 2000);
}
}
}
// System neustarten
async function restartSystem() {
if (confirm('WARNUNG: Möchten Sie wirklich das System neustarten? Alle aktiven Verbindungen werden getrennt.')) {
const result = await makeApiCall('/api/admin/system/restart', 'POST');
if (result) {
showNotification('System wird neugestartet...', 'warning');
setTimeout(() => {
window.location.href = '/';
}, 3000);
}
}
}
// Einstellungen bearbeiten
function editSettings() {
window.location.href = '/settings';
}
// Systemstatus automatisch aktualisieren
async function updateSystemStatus() {
if (window.location.search.includes('tab=system')) {
const result = await makeApiCall('/api/admin/system/status');
if (result) {
// Aktualisiere die Anzeige
const elements = {
'cpu_usage': result.cpu_usage + '%',
'memory_usage': result.memory_usage + '%',
'disk_usage': result.disk_usage + '%',
'uptime': result.uptime,
'db_size': result.db_size,
'scheduler_jobs': result.scheduler_jobs,
'next_job': result.next_job
};
Object.keys(elements).forEach(key => {
const element = document.querySelector(`[data-status="${key}"]`);
if (element) {
element.textContent = elements[key];
}
});
});
});
}
}
}
// Auto-Update alle 30 Sekunden
setInterval(updateSystemStatus, 30000);
// Initial load
document.addEventListener('DOMContentLoaded', function() {
updateSystemStatus();
});
</script>
{% endblock %}

View File

@ -446,16 +446,77 @@
}
}
// Refresh printers
// Refresh printers mit Status-Check
function refreshPrinters() {
const grid = document.getElementById('printers-grid');
const refreshBtn = document.querySelector('button[onclick="refreshPrinters()"]');
// Button deaktivieren und Loading-State anzeigen
if (refreshBtn) {
refreshBtn.disabled = true;
refreshBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
Überprüfe Status...
`;
}
// Loading-State im Grid anzeigen
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Lade Drucker...</p>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Überprüfe Drucker-Status...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies kann bis zu 7 Sekunden dauern</p>
</div>
`;
loadPrinters();
// Drucker laden mit Status-Check
loadPrintersWithStatusCheck().finally(() => {
// Button wieder aktivieren
if (refreshBtn) {
refreshBtn.disabled = false;
refreshBtn.innerHTML = `
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
Aktualisieren
`;
}
});
}
// Erweiterte Funktion zum Laden der Drucker mit Status-Check
async function loadPrintersWithStatusCheck() {
try {
const response = await fetch('/api/printers/status');
if (!response.ok) {
throw new Error('Fehler beim Laden der Drucker-Status');
}
const statusData = await response.json();
// Drucker-Daten mit Status-Informationen anreichern
printers = statusData.map(printer => ({
...printer,
status: printer.status === 'online' ? 'available' : 'offline'
}));
renderPrinters();
// Erfolgs-Nachricht anzeigen
const onlineCount = printers.filter(p => p.status === 'available').length;
const totalCount = printers.length;
showStatusMessage(
`Status-Check abgeschlossen: ${onlineCount} von ${totalCount} Drucker online`,
onlineCount === totalCount ? 'success' : 'info'
);
} catch (error) {
console.error('Error loading printer status:', error);
showError('Fehler beim Überprüfen der Drucker-Status');
}
}
// Initialize