feat: Hinzufügen neuer API-Endpunkte zur erweiterten Drucker-Status-Verwaltung und Verbesserung der Benutzeroberfläche durch optimierte Lade- und Filtermechanismen. Implementierung von Caching für Online-Drucker und Live-Status sowie Auto-Update-Funktionalität zur Echtzeit-Überwachung. Anpassungen in den Templates zur Anzeige von Status-Übersichten und Filteroptionen für eine verbesserte Benutzererfahrung.

This commit is contained in:
Till Tomczak 2025-05-27 11:38:50 +02:00
parent c39595382c
commit cbe1864678
4 changed files with 985 additions and 142 deletions

View File

@ -3226,6 +3226,307 @@ def admin_update_printer_form(printer_id):
flash("Fehler beim Aktualisieren des Druckers.", "error")
return redirect(url_for("admin_printer_settings_page", printer_id=printer_id))
# Neue API-Endpunkte für erweiterte Drucker-Status-Verwaltung hinzufügen
@app.route("/api/printers/online", methods=["GET"])
@login_required
def get_online_printers():
"""Gibt nur die online/verfügbaren Drucker zurück - optimiert für schnelle Anzeige."""
db_session = get_db_session()
printers_logger = get_logger("printers")
try:
# Session-Cache für Online-Drucker prüfen
cache_key = f"online_printers_{current_user.id}"
cached_data = session.get(cache_key)
cache_timestamp = session.get(f"{cache_key}_timestamp")
# Cache ist 30 Sekunden gültig
if cached_data and cache_timestamp:
cache_age = (datetime.now() - datetime.fromisoformat(cache_timestamp)).total_seconds()
if cache_age < 30:
printers_logger.info(f"Online-Drucker aus Session-Cache geladen (Alter: {cache_age:.1f}s)")
return jsonify({
"printers": cached_data,
"count": len(cached_data),
"cached": True,
"cache_age": cache_age
})
# Nur verfügbare/online Drucker aus Datenbank laden
printers = db_session.query(Printer).filter(
Printer.status.in_(["available", "online", "idle"]),
Printer.active == True
).all()
current_time = datetime.now()
online_printers = []
for printer in printers:
printer_data = {
"id": printer.id,
"name": printer.name,
"model": printer.model or 'Unbekanntes Modell',
"location": printer.location or 'Unbekannter Standort',
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": printer.status,
"active": printer.active,
"ip_address": printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None),
"created_at": printer.created_at.isoformat() if printer.created_at else current_time.isoformat(),
"last_checked": printer.last_checked.isoformat() if hasattr(printer, 'last_checked') and printer.last_checked else None,
"is_online": True # Alle Drucker in dieser Liste sind online
}
online_printers.append(printer_data)
# In Session-Cache speichern
session[cache_key] = online_printers
session[f"{cache_key}_timestamp"] = current_time.isoformat()
session.permanent = True
db_session.close()
printers_logger.info(f"Online-Drucker geladen: {len(online_printers)} verfügbare Drucker")
return jsonify({
"printers": online_printers,
"count": len(online_printers),
"cached": False,
"message": f"{len(online_printers)} online Drucker gefunden"
})
except Exception as e:
db_session.rollback()
db_session.close()
printers_logger.error(f"Fehler beim Abrufen der Online-Drucker: {str(e)}")
return jsonify({
"error": f"Fehler beim Laden der Online-Drucker: {str(e)}",
"printers": []
}), 500
@app.route("/api/printers/status/live", methods=["GET"])
@login_required
def get_live_printer_status():
"""Gibt Live-Status aller Drucker zurück mit Session-Caching und Echtzeit-Updates."""
db_session = get_db_session()
printers_logger = get_logger("printers")
try:
# Session-Cache für Live-Status prüfen
cache_key = f"live_printer_status_{current_user.id}"
cached_data = session.get(cache_key)
cache_timestamp = session.get(f"{cache_key}_timestamp")
# Cache ist 15 Sekunden gültig für Live-Status
if cached_data and cache_timestamp:
cache_age = (datetime.now() - datetime.fromisoformat(cache_timestamp)).total_seconds()
if cache_age < 15:
printers_logger.info(f"Live-Status aus Session-Cache geladen (Alter: {cache_age:.1f}s)")
return jsonify({
"printers": cached_data,
"cached": True,
"cache_age": cache_age,
"next_update": 15 - cache_age
})
# Alle Drucker aus der Datenbank laden
printers = db_session.query(Printer).all()
if not printers:
return jsonify({
"printers": [],
"count": 0,
"message": "Keine Drucker in der Datenbank gefunden"
})
# 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.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None)
})
# Paralleler Status-Check mit kürzerem Timeout für Live-Updates
try:
status_results = check_multiple_printers_status(printer_data, timeout=3)
except Exception as e:
printers_logger.warning(f"Status-Check fehlgeschlagen, verwende letzte bekannte Status: {str(e)}")
# Fallback: verwende letzte bekannte Status
status_results = {p['id']: (p.get('last_status', 'offline'), False) for p in printer_data}
# Live-Status-Daten zusammenstellen
live_status_data = []
current_time = datetime.now()
online_count = 0
for printer in printers:
if printer.id in status_results:
status, active = status_results[printer.id]
frontend_status = "available" if status == "online" else "offline"
if frontend_status == "available":
online_count += 1
else:
frontend_status = printer.status or "offline"
active = printer.active if hasattr(printer, 'active') else False
# Status in Datenbank aktualisieren (asynchron)
printer.status = frontend_status
printer.active = active
if hasattr(printer, 'last_checked'):
printer.last_checked = current_time
live_status_data.append({
"id": printer.id,
"name": printer.name,
"model": printer.model or 'Unbekanntes Modell',
"location": printer.location or 'Unbekannter Standort',
"mac_address": printer.mac_address,
"plug_ip": printer.plug_ip,
"status": frontend_status,
"active": active,
"ip_address": printer.plug_ip if printer.plug_ip else getattr(printer, 'ip_address', None),
"created_at": printer.created_at.isoformat() if printer.created_at else current_time.isoformat(),
"last_checked": current_time.isoformat(),
"is_online": frontend_status == "available",
"status_changed": True # Für Frontend-Animationen
})
# Änderungen in Datenbank speichern
try:
db_session.commit()
except Exception as e:
printers_logger.warning(f"Fehler beim Speichern der Live-Status-Updates: {str(e)}")
# In Session-Cache speichern
session[cache_key] = live_status_data
session[f"{cache_key}_timestamp"] = current_time.isoformat()
session.permanent = True
# Online-Drucker-Cache invalidieren
online_cache_key = f"online_printers_{current_user.id}"
if online_cache_key in session:
del session[online_cache_key]
del session[f"{online_cache_key}_timestamp"]
db_session.close()
printers_logger.info(f"Live-Status aktualisiert: {online_count} von {len(live_status_data)} Drucker online")
return jsonify({
"printers": live_status_data,
"count": len(live_status_data),
"online_count": online_count,
"offline_count": len(live_status_data) - online_count,
"cached": False,
"timestamp": current_time.isoformat(),
"next_update": 15
})
except Exception as e:
db_session.rollback()
db_session.close()
printers_logger.error(f"Fehler beim Live-Status-Check: {str(e)}")
return jsonify({
"error": f"Fehler beim Live-Status-Check: {str(e)}",
"printers": []
}), 500
@app.route("/api/printers/status/summary", methods=["GET"])
@login_required
def get_printer_status_summary():
"""Gibt eine Zusammenfassung des Drucker-Status zurück - sehr schnell."""
db_session = get_db_session()
try:
# Session-Cache für Status-Zusammenfassung
cache_key = f"printer_summary_{current_user.id}"
cached_data = session.get(cache_key)
cache_timestamp = session.get(f"{cache_key}_timestamp")
# Cache ist 60 Sekunden gültig
if cached_data and cache_timestamp:
cache_age = (datetime.now() - datetime.fromisoformat(cache_timestamp)).total_seconds()
if cache_age < 60:
return jsonify({
**cached_data,
"cached": True,
"cache_age": cache_age
})
# Status-Zusammenfassung aus Datenbank
total_printers = db_session.query(Printer).count()
online_printers = db_session.query(Printer).filter(
Printer.status.in_(["available", "online", "idle"]),
Printer.active == True
).count()
offline_printers = total_printers - online_printers
# Letzte Aktualisierung ermitteln
last_checked = db_session.query(func.max(Printer.last_checked)).scalar()
summary_data = {
"total": total_printers,
"online": online_printers,
"offline": offline_printers,
"percentage_online": round((online_printers / total_printers * 100) if total_printers > 0 else 0, 1),
"last_checked": last_checked.isoformat() if last_checked else None,
"timestamp": datetime.now().isoformat()
}
# In Session-Cache speichern
session[cache_key] = summary_data
session[f"{cache_key}_timestamp"] = datetime.now().isoformat()
session.permanent = True
db_session.close()
return jsonify({
**summary_data,
"cached": False
})
except Exception as e:
db_session.close()
return jsonify({
"error": f"Fehler beim Laden der Status-Zusammenfassung: {str(e)}",
"total": 0,
"online": 0,
"offline": 0
}), 500
# Session-Cache-Management
@app.route("/api/printers/cache/clear", methods=["POST"])
@login_required
def clear_printer_cache():
"""Löscht den Drucker-Cache für den aktuellen Benutzer."""
try:
cache_keys = [
f"online_printers_{current_user.id}",
f"live_printer_status_{current_user.id}",
f"printer_summary_{current_user.id}"
]
cleared_count = 0
for key in cache_keys:
if key in session:
del session[key]
cleared_count += 1
timestamp_key = f"{key}_timestamp"
if timestamp_key in session:
del session[timestamp_key]
return jsonify({
"message": f"Cache erfolgreich geleert ({cleared_count} Einträge)",
"cleared_keys": cleared_count
})
except Exception as e:
return jsonify({
"error": f"Fehler beim Löschen des Cache: {str(e)}"
}), 500
# ===== STARTUP UND MAIN =====
if __name__ == "__main__":
import sys
@ -3282,4 +3583,4 @@ if __name__ == "__main__":
except Exception as e:
app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}")
sys.exit(1)
sys.exit(1)

View File

@ -0,0 +1,177 @@
/* Erweiterte Drucker-Styles für MYP Platform */
/* Filter-Button-Styles */
.filter-btn {
transition: all 0.2s ease-in-out;
}
.filter-btn.active {
background-color: white;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
color: #374151;
}
.dark .filter-btn.active {
background-color: #475569;
color: #f1f5f9;
}
/* Online-Drucker-Hervorhebung */
.printer-card-online {
background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%);
border-color: #bbf7d0;
box-shadow: 0 1px 3px 0 rgba(34, 197, 94, 0.1), 0 1px 2px 0 rgba(34, 197, 94, 0.06);
}
.dark .printer-card-online {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, #1e293b 100%);
border-color: #166534;
box-shadow: 0 1px 3px 0 rgba(34, 197, 94, 0.2), 0 1px 2px 0 rgba(34, 197, 94, 0.1);
}
.printer-card-online:hover {
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.2), 0 2px 4px -1px rgba(34, 197, 94, 0.1);
}
.dark .printer-card-online:hover {
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.3), 0 2px 4px -1px rgba(34, 197, 94, 0.2);
}
/* Online-Indikator-Animation */
.online-indicator {
animation: pulse-green 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse-green {
0%, 100% {
opacity: 1;
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
}
50% {
opacity: .8;
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0);
}
}
/* Status-Übersicht-Animationen */
.status-count-change {
animation: count-change 0.5s ease-in-out;
}
@keyframes count-change {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* Auto-Refresh-Button-Animationen */
.auto-refresh-active {
background: linear-gradient(45deg, #10b981, #059669);
animation: gradient-shift 3s ease-in-out infinite;
}
@keyframes gradient-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* Drucker-Karten-Übergangseffekte */
.printer-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.printer-card:hover {
transform: translateY(-2px);
}
/* Loading-Spinner für Live-Updates */
.live-update-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Responsive Verbesserungen */
@media (max-width: 640px) {
.filter-btn {
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
}
.status-overview {
flex-direction: column;
gap: 0.5rem;
}
.printer-card {
padding: 1rem;
}
}
/* Dark Mode Verbesserungen */
.dark .printer-card {
background-color: #1e293b;
border-color: #334155;
}
.dark .printer-card:hover {
background-color: #334155;
}
/* Accessibility Verbesserungen */
.filter-btn:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
.printer-card:focus-within {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Print-Styles */
@media print {
.filter-btn,
.auto-refresh-btn,
.printer-detail-btn,
.delete-printer-btn {
display: none;
}
.printer-card {
break-inside: avoid;
box-shadow: none;
border: 1px solid #000;
}
}
/* High Contrast Mode */
@media (prefers-contrast: high) {
.printer-card-online {
border: 2px solid #059669;
}
.online-indicator {
border: 1px solid #000;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
.online-indicator,
.auto-refresh-active,
.live-update-spinner {
animation: none;
}
.printer-card {
transition: none;
}
.printer-card:hover {
transform: none;
}
}

View File

@ -170,6 +170,10 @@
{% block extra_js %}
<script>
// Globale Variable für Admin-Status
window.isAdmin = {% if current_user.is_admin %}true{% else %}false{% endif %};
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// File upload preview
const fileInput = document.getElementById('stl_file');
@ -251,95 +255,209 @@ function refreshJobs() {
});
}
// Laden der Drucker für das Dropdown
// Laden der Drucker für das Dropdown mit verbesserter Online-Erkennung
function loadPrinters() {
// Lade Drucker mit Status-Check für bessere Verfügbarkeitsprüfung
fetch('/api/printers/status')
const printerSelect = document.getElementById('printer_id');
// Loading-State anzeigen
printerSelect.innerHTML = '<option value="">Lade Drucker...</option>';
printerSelect.disabled = true;
// Zuerst versuchen, Online-Drucker zu laden (schnell)
fetch('/api/printers/online')
.then(response => response.json())
.then(data => {
const printerSelect = document.getElementById('printer_id');
printerSelect.innerHTML = '<option value="">Drucker auswählen...</option>';
const onlinePrinters = data.printers || [];
console.log('Online-Drucker geladen:', onlinePrinters);
// Prüfe ob data ein Array ist (direkte Antwort) oder ein Objekt mit printers-Property
const printers = Array.isArray(data) ? data : (data.printers || []);
console.log('Geladene Drucker:', printers);
// Filtere verfügbare Drucker (status: 'available' oder active: true)
const availablePrinters = printers.filter(printer => {
return printer.status === 'available' || printer.active === true;
});
console.log('Verfügbare Drucker:', availablePrinters);
if (availablePrinters.length === 0) {
// Fallback: Lade alle Drucker ohne Status-Check
return fetch('/api/printers')
.then(response => response.json())
.then(fallbackData => {
const fallbackPrinters = fallbackData.printers || [];
console.log('Fallback Drucker:', fallbackPrinters);
if (fallbackPrinters.length === 0) {
printerSelect.innerHTML = '<option value="">Keine Drucker verfügbar</option>';
showNotification('Keine Drucker in der Datenbank gefunden', 'warning');
return;
}
// Zeige alle Drucker an, auch wenn Status unbekannt
fallbackPrinters.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
option.textContent = `${printer.name} (${printer.model || 'Unbekanntes Modell'}) - Status unbekannt`;
printerSelect.appendChild(option);
});
showNotification(`${fallbackPrinters.length} Drucker geladen (Status unbekannt)`, 'info');
});
}
// Füge verfügbare Drucker hinzu
availablePrinters.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
if (onlinePrinters.length > 0) {
// Online-Drucker verfügbar - diese bevorzugt anzeigen
populatePrinterSelect(onlinePrinters, true);
showNotification(`${onlinePrinters.length} online Drucker verfügbar`, 'success');
// Status-Indikator hinzufügen
const statusText = printer.status === 'available' ? '✅ Verfügbar' : '⚠️ Status unbekannt';
option.textContent = `${printer.name} (${printer.model || 'Unbekanntes Modell'}) - ${statusText}`;
printerSelect.appendChild(option);
});
showNotification(`${availablePrinters.length} verfügbare Drucker geladen`, 'success');
// Zusätzlich alle Drucker laden für Vollständigkeit
loadAllPrintersAsSecondary(onlinePrinters);
} else {
// Keine Online-Drucker - lade alle mit Live-Status-Check
loadPrintersWithLiveStatus();
}
})
.catch(error => {
console.error('Fehler beim Laden der Drucker:', error);
console.error('Fehler beim Laden der Online-Drucker:', error);
// Fallback: Lade alle Drucker mit Live-Status
loadPrintersWithLiveStatus();
});
}
// Hilfsfunktion: Drucker-Select befüllen
function populatePrinterSelect(printers, onlineOnly = false) {
const printerSelect = document.getElementById('printer_id');
printerSelect.innerHTML = '<option value="">Drucker auswählen...</option>';
printerSelect.disabled = false;
if (printers.length === 0) {
printerSelect.innerHTML = '<option value="">Keine Drucker verfügbar</option>';
return;
}
// Sortiere Drucker: Online zuerst, dann nach Name
const sortedPrinters = printers.sort((a, b) => {
// Online-Status prüfen
const aOnline = a.status === 'available' || a.is_online || a.active;
const bOnline = b.status === 'available' || b.is_online || b.active;
if (aOnline && !bOnline) return -1;
if (!aOnline && bOnline) return 1;
// Bei gleichem Online-Status nach Name sortieren
return a.name.localeCompare(b.name);
});
sortedPrinters.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
// Status-Indikator bestimmen
const isOnline = printer.status === 'available' || printer.is_online || printer.active;
let statusIcon, statusText;
if (isOnline) {
statusIcon = '🟢';
statusText = 'Online';
option.style.color = '#059669'; // Grün für online
} else {
statusIcon = '🔴';
statusText = 'Offline';
option.style.color = '#dc2626'; // Rot für offline
option.disabled = true; // Offline-Drucker deaktivieren
}
// Letzter Check-Zeitstempel
let lastChecked = '';
if (printer.last_checked) {
const checkTime = new Date(printer.last_checked);
const now = new Date();
const diffMinutes = Math.floor((now - checkTime) / 60000);
// Fallback: Versuche normale Drucker-API
fetch('/api/printers')
.then(response => response.json())
.then(data => {
const printerSelect = document.getElementById('printer_id');
const printers = data.printers || [];
if (printers.length === 0) {
printerSelect.innerHTML = '<option value="">Keine Drucker verfügbar</option>';
showNotification('Keine Drucker gefunden', 'error');
return;
}
printers.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
option.textContent = `${printer.name} (${printer.model || 'Unbekanntes Modell'}) - Status unbekannt`;
printerSelect.appendChild(option);
});
showNotification(`${printers.length} Drucker geladen (ohne Status-Check)`, 'warning');
})
.catch(fallbackError => {
console.error('Auch Fallback-API fehlgeschlagen:', fallbackError);
showNotification('Fehler beim Laden der Drucker', 'error');
});
if (diffMinutes < 1) {
lastChecked = ' (gerade geprüft)';
} else if (diffMinutes < 60) {
lastChecked = ` (vor ${diffMinutes} Min)`;
} else {
lastChecked = ` (vor ${Math.floor(diffMinutes / 60)} Std)`;
}
}
option.textContent = `${statusIcon} ${printer.name} (${printer.model || 'Unbekanntes Modell'}) - ${statusText}${lastChecked}`;
// Tooltip für zusätzliche Informationen
option.title = `Standort: ${printer.location || 'Unbekannt'}\nIP: ${printer.plug_ip || printer.ip_address || 'Unbekannt'}\nStatus: ${statusText}${lastChecked}`;
printerSelect.appendChild(option);
});
// Hinweis für Offline-Drucker
const offlineCount = printers.filter(p => !(p.status === 'available' || p.is_online || p.active)).length;
if (offlineCount > 0) {
const infoOption = document.createElement('option');
infoOption.disabled = true;
infoOption.textContent = `--- ${offlineCount} Drucker offline (nicht verfügbar) ---`;
infoOption.style.fontStyle = 'italic';
infoOption.style.color = '#6b7280';
printerSelect.appendChild(infoOption);
}
}
// Alle Drucker als sekundäre Option laden
function loadAllPrintersAsSecondary(onlinePrinters) {
fetch('/api/printers/status/live')
.then(response => response.json())
.then(data => {
const allPrinters = data.printers || [];
// Prüfe, ob es zusätzliche Drucker gibt, die nicht in der Online-Liste sind
const onlineIds = new Set(onlinePrinters.map(p => p.id));
const additionalPrinters = allPrinters.filter(p => !onlineIds.has(p.id));
if (additionalPrinters.length > 0) {
// Kombiniere Online- und zusätzliche Drucker
const combinedPrinters = [...onlinePrinters, ...additionalPrinters];
populatePrinterSelect(combinedPrinters, false);
const totalOnline = combinedPrinters.filter(p => p.status === 'available' || p.is_online || p.active).length;
showNotification(`${totalOnline} von ${combinedPrinters.length} Drucker online`, totalOnline > 0 ? 'success' : 'warning');
}
})
.catch(error => {
console.error('Fehler beim Laden aller Drucker:', error);
// Nicht kritisch, Online-Drucker sind bereits geladen
});
}
// Drucker mit Live-Status-Check laden (Fallback)
function loadPrintersWithLiveStatus() {
showNotification('Überprüfe Drucker-Status...', 'info');
fetch('/api/printers/status/live')
.then(response => response.json())
.then(data => {
const printers = data.printers || [];
console.log('Live-Status-Drucker geladen:', printers);
if (printers.length === 0) {
// Letzter Fallback: Normale Drucker-API
return loadPrintersBasic();
}
populatePrinterSelect(printers, false);
const onlineCount = printers.filter(p => p.status === 'available' || p.is_online || p.active).length;
if (onlineCount > 0) {
showNotification(`${onlineCount} von ${printers.length} Drucker online (Live-Check)`, 'success');
} else {
showNotification(`${printers.length} Drucker gefunden, aber alle offline`, 'warning');
}
})
.catch(error => {
console.error('Fehler beim Live-Status-Check:', error);
showNotification('Live-Status-Check fehlgeschlagen, lade Basis-Daten...', 'warning');
loadPrintersBasic();
});
}
// Basis-Drucker-Laden (letzter Fallback)
function loadPrintersBasic() {
fetch('/api/printers')
.then(response => response.json())
.then(data => {
const printers = data.printers || [];
console.log('Basis-Drucker geladen:', printers);
if (printers.length === 0) {
const printerSelect = document.getElementById('printer_id');
printerSelect.innerHTML = '<option value="">Keine Drucker in der Datenbank</option>';
printerSelect.disabled = true;
showNotification('Keine Drucker in der Datenbank gefunden', 'error');
return;
}
// Alle Drucker als "Status unbekannt" anzeigen
const printersWithUnknownStatus = printers.map(printer => ({
...printer,
status: 'unknown',
is_online: false,
active: true // Erlaube Auswahl trotz unbekanntem Status
}));
populatePrinterSelect(printersWithUnknownStatus, false);
showNotification(`${printers.length} Drucker geladen (Status unbekannt)`, 'warning');
})
.catch(error => {
console.error('Auch Basis-API fehlgeschlagen:', error);
const printerSelect = document.getElementById('printer_id');
printerSelect.innerHTML = '<option value="">Fehler beim Laden der Drucker</option>';
printerSelect.disabled = true;
showNotification('Fehler beim Laden der Drucker', 'error');
});
}
@ -723,7 +841,7 @@ function renderJobCard(job) {
}
// Admin: Beenden-Button für laufende Jobs
if (job.status === 'running' && isAdmin) {
if (job.status === 'running' && window.isAdmin) {
actionButtons += `
<button type="button" onclick="finishJob(${job.id})"
class="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm font-medium transition-colors duration-200">
@ -978,7 +1096,6 @@ function formatDateTime(isoString) {
});
}
// Globale Variable für Admin-Status
const isAdmin = {% if current_user.is_admin %}true{% else %}false{% endif %};
// Globale Variable für Admin-Status wird über window.isAdmin gesetzt
</script>
{% endblock %}

View File

@ -2,22 +2,63 @@
{% block title %}Drucker - MYP Platform{% endblock %}
{% block extra_css %}
<link href="{{ url_for('static', filename='css/printers.css') }}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto px-3 sm:px-6 lg:px-8 py-4 sm:py-8">
<!-- Header -->
<!-- Header mit Status-Übersicht -->
<div class="mb-4 sm:mb-8">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
<div class="mb-4 sm:mb-0">
<h1 class="text-2xl sm:text-3xl font-bold text-slate-900 dark:text-white">Drucker</h1>
<p class="mt-1 sm:mt-2 text-sm sm:text-base text-slate-600 dark:text-slate-400">Verwalten Sie Ihre 3D-Drucker</p>
<!-- Live-Status-Übersicht -->
<div id="status-overview" class="mt-3 flex flex-wrap gap-2 text-xs sm:text-sm">
<div class="flex items-center space-x-1">
<div class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-slate-600 dark:text-slate-400">Online: <span id="online-count" class="font-semibold text-green-600 dark:text-green-400">-</span></span>
</div>
<div class="flex items-center space-x-1">
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
<span class="text-slate-600 dark:text-slate-400">Offline: <span id="offline-count" class="font-semibold text-red-600 dark:text-red-400">-</span></span>
</div>
<div class="flex items-center space-x-1">
<div class="w-2 h-2 bg-blue-500 rounded-full"></div>
<span class="text-slate-600 dark:text-slate-400">Gesamt: <span id="total-count" class="font-semibold text-blue-600 dark:text-blue-400">-</span></span>
</div>
<div class="flex items-center space-x-1 ml-2">
<svg id="auto-refresh-icon" class="w-3 h-3 text-slate-400" 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>
<span class="text-xs text-slate-500 dark:text-slate-400">Auto-Update: <span id="next-update-time">-</span>s</span>
</div>
</div>
</div>
<div class="flex flex-wrap gap-2 sm:space-x-4">
<!-- Filter-Buttons -->
<div class="flex bg-slate-100 dark:bg-slate-700 rounded-lg p-1">
<button id="filter-all" class="filter-btn active px-3 py-1 text-xs rounded-md transition-all duration-200">Alle</button>
<button id="filter-online" class="filter-btn px-3 py-1 text-xs rounded-md transition-all duration-200">Online</button>
<button id="filter-offline" class="filter-btn px-3 py-1 text-xs rounded-md transition-all duration-200">Offline</button>
</div>
<button onclick="toggleAutoRefresh()" id="auto-refresh-btn" class="flex-1 sm:flex-none bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white px-3 sm:px-4 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center justify-center">
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Auto-Update
</button>
<button onclick="refreshPrinters()" class="flex-1 sm:flex-none bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-700 text-white px-3 sm:px-4 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center justify-center">
<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
Jetzt aktualisieren
</button>
{% if current_user.is_admin %}
<button id="addPrinterBtn" class="flex-1 sm:flex-none bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700 text-white px-3 sm:px-4 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center justify-center">
<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">
@ -143,6 +184,11 @@
<script>
// Globale Variablen für die Drucker-Verwaltung
let printers = [];
let currentFilter = 'all';
let autoRefreshEnabled = false;
let autoRefreshInterval = null;
let nextUpdateCountdown = null;
let nextUpdateTime = 30; // Sekunden bis zum nächsten Auto-Update
// Refresh printers mit Status-Check - Make it globally available
function refreshPrinters() {
@ -171,8 +217,8 @@
</div>
`;
// Drucker laden mit Status-Check
loadPrintersWithStatusCheck().finally(() => {
// Drucker laden mit Live-Status-Check
loadPrintersWithLiveStatus().finally(() => {
// Button wieder aktivieren
if (refreshBtn) {
refreshBtn.disabled = false;
@ -180,7 +226,7 @@
<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
Jetzt aktualisieren
`;
}
});
@ -338,22 +384,46 @@
}
}
// Render printers grid
// Render printers grid mit Filter-Unterstützung
function renderPrinters() {
const grid = document.getElementById('printers-grid');
if (printers.length === 0) {
// Filter anwenden
let filteredPrinters = printers;
if (currentFilter === 'online') {
filteredPrinters = printers.filter(p => p.status === 'available' || p.is_online);
} else if (currentFilter === 'offline') {
filteredPrinters = printers.filter(p => p.status === 'offline' || !p.is_online);
}
// Status-Übersicht aktualisieren falls nicht bereits gesetzt
if (!document.getElementById('online-count').textContent || document.getElementById('online-count').textContent === '-') {
const onlineCount = printers.filter(p => p.status === 'available' || p.is_online).length;
const offlineCount = printers.length - onlineCount;
updateStatusOverview(onlineCount, offlineCount, printers.length);
}
if (filteredPrinters.length === 0) {
let emptyMessage = 'Keine Drucker vorhanden';
if (currentFilter === 'online') {
emptyMessage = 'Keine Online-Drucker gefunden';
} else if (currentFilter === 'offline') {
emptyMessage = 'Keine Offline-Drucker gefunden';
}
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<svg class="h-12 w-12 sm:h-16 sm:w-16 text-slate-400 dark:text-slate-500 mx-auto mb-3 sm:mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
<p class="text-slate-700 dark:text-slate-300 text-base sm:text-lg">Keine Drucker vorhanden</p>
{% if current_user.is_admin %}
<button id="addFirstPrinterBtn" class="mt-3 sm:mt-4 bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-500 text-white px-4 sm:px-6 py-1.5 sm:py-2 rounded-lg transition-all duration-200 text-sm sm:text-base">
Ersten Drucker hinzufügen
</button>
{% endif %}
<p class="text-slate-700 dark:text-slate-300 text-base sm:text-lg">${emptyMessage}</p>
${currentFilter === 'all' && printers.length === 0 ? `
{% if current_user.is_admin %}
<button id="addFirstPrinterBtn" class="mt-3 sm:mt-4 bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-500 text-white px-4 sm:px-6 py-1.5 sm:py-2 rounded-lg transition-all duration-200 text-sm sm:text-base">
Ersten Drucker hinzufügen
</button>
{% endif %}
` : ''}
</div>
`;
@ -366,43 +436,55 @@
return;
}
grid.innerHTML = printers.map(printer => {
grid.innerHTML = filteredPrinters.map(printer => {
const statusColor = getPrinterStatusColor(printer.status);
const statusText = getPrinterStatusText(printer.status);
const isOnline = printer.status === 'available' || printer.is_online;
// Spezielle Styling für Online-Drucker
const cardClasses = isOnline
? 'bg-gradient-to-br from-green-50 to-white dark:from-green-900/20 dark:to-slate-800 border-green-200 dark:border-green-700 shadow-green-100 dark:shadow-green-900/20'
: 'bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700';
const onlineIndicator = isOnline
? '<div class="absolute top-2 right-2 w-3 h-3 bg-green-500 rounded-full animate-pulse shadow-lg"></div>'
: '';
return `
<div class="bg-white dark:bg-slate-800 rounded-xl p-4 sm:p-6 shadow-sm hover:shadow-lg transition-shadow duration-200 border border-slate-200 dark:border-slate-700">
<div class="relative ${cardClasses} rounded-xl p-4 sm:p-6 shadow-sm hover:shadow-lg transition-all duration-200 border ${isOnline ? 'hover:shadow-green-200 dark:hover:shadow-green-900/30' : ''}">
${onlineIndicator}
<div class="flex items-start justify-between mb-3 sm:mb-4">
<div class="flex-1">
<h3 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white">${printer.name}</h3>
<p class="text-xs sm:text-sm text-slate-600 dark:text-slate-400">${printer.model}</p>
<h3 class="text-base sm:text-lg font-bold ${isOnline ? 'text-green-900 dark:text-green-100' : 'text-slate-900 dark:text-white'}">${printer.name}</h3>
<p class="text-xs sm:text-sm ${isOnline ? 'text-green-700 dark:text-green-300' : 'text-slate-600 dark:text-slate-400'}">${printer.model}</p>
</div>
<div class="flex flex-col items-end">
<span class="inline-flex items-center px-2 py-0.5 sm:px-2.5 sm:py-0.5 rounded-full text-xs font-medium ${statusColor}">
${statusText}
${isOnline ? '🟢 ' : '🔴 '}${statusText}
</span>
${printer.last_checked ? `<span class="text-xs text-slate-500 dark:text-slate-400 mt-1">Geprüft: ${formatTime(printer.last_checked)}</span>` : ''}
${printer.last_checked ? `<span class="text-xs ${isOnline ? 'text-green-600 dark:text-green-400' : 'text-slate-500 dark:text-slate-400'} mt-1">Geprüft: ${formatTime(printer.last_checked)}</span>` : ''}
</div>
</div>
<div class="space-y-1.5 sm:space-y-2 mb-3 sm:mb-4">
<div class="flex items-center text-xs sm:text-sm text-slate-600 dark:text-slate-400">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 text-slate-500 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="flex items-center text-xs sm:text-sm ${isOnline ? 'text-green-700 dark:text-green-300' : 'text-slate-600 dark:text-slate-400'}">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 ${isOnline ? 'text-green-600 dark:text-green-400' : 'text-slate-500 dark:text-slate-400'}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
${printer.location}
</div>
<div class="flex items-center text-xs sm:text-sm text-slate-600 dark:text-slate-400">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 text-slate-500 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="flex items-center text-xs sm:text-sm ${isOnline ? 'text-green-700 dark:text-green-300' : 'text-slate-600 dark:text-slate-400'}">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 ${isOnline ? 'text-green-600 dark:text-green-400' : 'text-slate-500 dark:text-slate-400'}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
${printer.mac_address}
</div>
<div class="flex items-center text-xs sm:text-sm text-slate-600 dark:text-slate-400">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 text-slate-500 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="flex items-center text-xs sm:text-sm ${isOnline ? 'text-green-700 dark:text-green-300' : 'text-slate-600 dark:text-slate-400'}">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 ${isOnline ? 'text-green-600 dark:text-green-400' : 'text-slate-500 dark:text-slate-400'}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9v-9m0-9v9" />
</svg>
${printer.plug_ip}
@ -410,7 +492,7 @@
</div>
<div class="flex space-x-2">
<button class="printer-detail-btn flex-1 bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white py-1.5 sm:py-2 px-2 sm:px-3 rounded-lg text-xs sm:text-sm transition-all duration-200" data-printer-id="${printer.id}">
<button class="printer-detail-btn flex-1 ${isOnline ? 'bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-500' : 'bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500'} text-white py-1.5 sm:py-2 px-2 sm:px-3 rounded-lg text-xs sm:text-sm transition-all duration-200" data-printer-id="${printer.id}">
Details
</button>
@ -720,14 +802,13 @@
}
}
// Erweiterte Funktion zum Laden der Drucker mit Status-Check
async function loadPrintersWithStatusCheck() {
// Erweiterte Funktion zum Laden der Drucker mit Live-Status
async function loadPrintersWithLiveStatus() {
try {
// Erstelle einen AbortController für Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 Sekunden Timeout für Status-Check
const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 Sekunden Timeout für Live-Status
const response = await fetch('/api/printers/status', {
const response = await fetch('/api/printers/status/live', {
signal: controller.signal,
headers: {
'Accept': 'application/json',
@ -739,54 +820,205 @@
if (!response.ok) {
if (response.status === 408) {
throw new Error('Timeout beim Status-Check der Drucker. Versuchen Sie es später erneut.');
throw new Error('Timeout beim Live-Status-Check der Drucker.');
}
throw new Error(`Fehler beim Laden der Drucker-Status: ${response.status} ${response.statusText}`);
throw new Error(`Fehler beim Laden des Live-Status: ${response.status} ${response.statusText}`);
}
const statusData = await response.json();
const data = await response.json();
// Prüfe ob statusData ein Array ist
if (!Array.isArray(statusData)) {
console.error('Invalid response from /api/printers/status:', statusData);
throw new Error('Ungültige Antwort vom Server (erwartet Array, erhalten: ' + typeof statusData + ')');
if (data.error) {
throw new Error(data.error);
}
// Drucker-Daten mit Status-Informationen anreichern
printers = statusData.map(printer => ({
...printer,
// Status ist bereits korrekt gemappt vom Backend
status: printer.status || 'offline',
last_checked: printer.last_checked || new Date().toISOString()
}));
// Drucker-Daten aktualisieren
printers = data.printers || [];
// Status-Übersicht aktualisieren
updateStatusOverview(data.online_count, data.offline_count, data.count);
// Drucker rendern
renderPrinters();
// Erfolgs-Nachricht anzeigen
const onlineCount = printers.filter(p => p.status === 'available').length;
const totalCount = printers.length;
// Auto-Update-Timer aktualisieren
if (data.next_update) {
nextUpdateTime = data.next_update;
updateNextUpdateDisplay();
}
if (totalCount > 0) {
// Erfolgs-Nachricht nur bei manueller Aktualisierung
if (!autoRefreshEnabled) {
showStatusMessage(
`Status-Check abgeschlossen: ${onlineCount} von ${totalCount} Drucker verfügbar`,
onlineCount > 0 ? 'success' : 'warning'
`Live-Status aktualisiert: ${data.online_count} von ${data.count} Drucker online`,
data.online_count > 0 ? 'success' : 'warning'
);
} else {
showStatusMessage('Keine Drucker gefunden', 'info');
}
} catch (error) {
console.error('Error loading printer status:', error);
showStatusMessage('Fehler beim Überprüfen der Drucker-Status: ' + error.message, 'error');
console.error('Error loading live printer status:', error);
if (!autoRefreshEnabled) {
showStatusMessage('Fehler beim Live-Status-Check: ' + error.message, 'error');
}
// Fallback: Lade normale Drucker-Liste
loadPrinters();
}
}
// Nur Online-Drucker laden (schnell)
async function loadOnlinePrinters() {
try {
const response = await fetch('/api/printers/online');
if (!response.ok) {
throw new Error(`Fehler beim Laden der Online-Drucker: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
return data.printers || [];
} catch (error) {
console.error('Error loading online printers:', error);
return [];
}
}
// Status-Übersicht aktualisieren
function updateStatusOverview(onlineCount, offlineCount, totalCount) {
document.getElementById('online-count').textContent = onlineCount || 0;
document.getElementById('offline-count').textContent = offlineCount || 0;
document.getElementById('total-count').textContent = totalCount || 0;
// Animiere die Online-Anzeige bei Änderungen
const onlineElement = document.getElementById('online-count');
if (onlineElement.dataset.lastValue !== String(onlineCount)) {
onlineElement.classList.add('animate-pulse');
setTimeout(() => onlineElement.classList.remove('animate-pulse'), 1000);
onlineElement.dataset.lastValue = String(onlineCount);
}
}
// Auto-Refresh-Funktionalität
function toggleAutoRefresh() {
autoRefreshEnabled = !autoRefreshEnabled;
const btn = document.getElementById('auto-refresh-btn');
const icon = document.getElementById('auto-refresh-icon');
if (autoRefreshEnabled) {
btn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
btn.classList.add('bg-green-600', 'hover:bg-green-700');
btn.innerHTML = `
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2 animate-spin" 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>
Auto-Update AN
`;
// Starte Auto-Refresh
startAutoRefresh();
showStatusMessage('Auto-Update aktiviert - Drucker werden alle 30 Sekunden aktualisiert', 'info');
} else {
btn.classList.remove('bg-green-600', 'hover:bg-green-700');
btn.classList.add('bg-blue-600', 'hover:bg-blue-700');
btn.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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Auto-Update
`;
// Stoppe Auto-Refresh
stopAutoRefresh();
showStatusMessage('Auto-Update deaktiviert', 'info');
}
}
function startAutoRefresh() {
stopAutoRefresh(); // Stoppe vorherige Intervalle
nextUpdateTime = 30;
updateNextUpdateDisplay();
// Countdown-Timer
nextUpdateCountdown = setInterval(() => {
nextUpdateTime--;
updateNextUpdateDisplay();
if (nextUpdateTime <= 0) {
loadPrintersWithLiveStatus();
nextUpdateTime = 30;
}
}, 1000);
// Auto-Refresh-Interval
autoRefreshInterval = setInterval(() => {
loadPrintersWithLiveStatus();
}, 30000); // Alle 30 Sekunden
}
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
if (nextUpdateCountdown) {
clearInterval(nextUpdateCountdown);
nextUpdateCountdown = null;
}
document.getElementById('next-update-time').textContent = '-';
}
function updateNextUpdateDisplay() {
const element = document.getElementById('next-update-time');
if (autoRefreshEnabled && nextUpdateTime > 0) {
element.textContent = nextUpdateTime;
element.parentElement.style.opacity = '1';
} else {
element.textContent = '-';
element.parentElement.style.opacity = '0.5';
}
}
// Filter-Funktionalität
function setupFilters() {
const filterButtons = document.querySelectorAll('.filter-btn');
filterButtons.forEach(btn => {
btn.addEventListener('click', function() {
// Entferne active-Klasse von allen Buttons
filterButtons.forEach(b => {
b.classList.remove('active', 'bg-white', 'dark:bg-slate-600', 'shadow-sm');
b.classList.add('text-slate-600', 'dark:text-slate-400');
});
// Füge active-Klasse zum geklickten Button hinzu
this.classList.add('active', 'bg-white', 'dark:bg-slate-600', 'shadow-sm');
this.classList.remove('text-slate-600', 'dark:text-slate-400');
// Setze aktuellen Filter
currentFilter = this.id.replace('filter-', '');
// Rendere Drucker mit Filter
renderPrinters();
});
});
}
// Erweiterte Funktion zum Laden der Drucker mit Status-Check (Legacy-Kompatibilität)
async function loadPrintersWithStatusCheck() {
return loadPrintersWithLiveStatus();
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
// Lade Drucker beim Start
loadPrinters();
// Setup Filter-Buttons
setupFilters();
// Lade Drucker beim Start mit Live-Status
loadPrintersWithLiveStatus();
// Event-Listener für den "Drucker hinzufügen" Button
const addPrinterBtn = document.getElementById('addPrinterBtn');
@ -837,6 +1069,19 @@
hidePrinterDetailModal();
}
});
// Auto-Refresh bei Sichtbarkeitsänderung der Seite
document.addEventListener('visibilitychange', function() {
if (!document.hidden && autoRefreshEnabled) {
// Seite ist wieder sichtbar und Auto-Refresh ist aktiv
loadPrintersWithLiveStatus();
}
});
// Cleanup bei Seitenverlassen
window.addEventListener('beforeunload', function() {
stopAutoRefresh();
});
});
// Make all functions globally available for onclick handlers
@ -845,6 +1090,9 @@
window.deletePrinter = deletePrinter;
window.loadPrinters = loadPrinters;
window.handleAddPrinter = handleAddPrinter;
window.toggleAutoRefresh = toggleAutoRefresh;
window.loadPrintersWithLiveStatus = loadPrintersWithLiveStatus;
window.loadOnlinePrinters = loadOnlinePrinters;
// Debug: Log that functions are available
console.log('All printer functions loaded and available globally:', {