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:
parent
c39595382c
commit
cbe1864678
@ -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)
|
||||
|
177
backend/app/static/css/printers.css
Normal file
177
backend/app/static/css/printers.css
Normal 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;
|
||||
}
|
||||
}
|
@ -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 %}
|
@ -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:', {
|
||||
|
Loading…
x
Reference in New Issue
Block a user