1253 lines
58 KiB
HTML

{% extends "base.html" %}
{% 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 mit Status-Übersicht - Neu gestaltet -->
<div class="mb-6 sm:mb-10">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="mb-4 sm:mb-0">
<h1 class="text-2xl sm:text-3xl font-bold text-slate-900 dark:text-white mb-2">Drucker</h1>
<p class="text-sm sm:text-base text-slate-600 dark:text-slate-400">Verwalten Sie Ihre 3D-Drucker</p>
<!-- Live-Status-Übersicht - Neu gestaltet -->
<div id="status-overview" class="status-overview-new mt-3">
<div class="flex items-center space-x-2">
<div class="status-dot online"></div>
<span class="text-slate-700 dark:text-slate-300">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-2">
<div class="status-dot offline"></div>
<span class="text-slate-700 dark:text-slate-300">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-2">
<svg class="w-2.5 h-2.5 text-blue-500" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path>
</svg>
<span class="text-slate-700 dark:text-slate-300">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-2 ml-2">
<svg id="auto-refresh-icon" class="w-3 h-3 text-slate-500" 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" class="font-mono">-</span>s</span>
</div>
</div>
</div>
<div class="flex flex-wrap gap-3">
<!-- Filter-Buttons - Neu gestaltet -->
<div class="filter-bar-new">
<button id="filter-all" class="filter-btn-new active">Alle</button>
<button id="filter-online" class="filter-btn-new">Online</button>
<button id="filter-offline" class="filter-btn-new">Offline</button>
</div>
<button onclick="toggleAutoRefresh()" id="auto-refresh-btn" class="action-btn-new primary">
<svg class="h-4 w-4 sm:h-5 sm:w-5" 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>
<span>Auto-Update</span>
</button>
<button onclick="refreshPrinters()" class="action-btn-new primary">
<svg class="h-4 w-4 sm:h-5 sm:w-5" 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>Jetzt aktualisieren</span>
</button>
{% if current_user.is_admin %}
<button id="addPrinterBtn" class="action-btn-new success">
<svg class="h-4 w-4 sm:h-5 sm:w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>Drucker hinzufügen</span>
</button>
{% endif %}
</div>
</div>
</div>
<!-- Printers Grid -->
<div id="printers-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
<!-- Loading state -->
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Lade Drucker...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies sollte nur wenige Sekunden dauern</p>
</div>
</div>
</div>
<!-- Drucker Detail Modal - Komplett überarbeitet mit expliziter hidden-Klasse -->
<div id="printerDetailModal" class="modal-new hidden" style="display: none !important; visibility: hidden !important;">
<div class="modal-content-new max-w-lg">
<div class="flex items-center justify-between mb-5">
<h2 class="text-xl sm:text-2xl font-bold text-slate-900 dark:text-white">Drucker Details</h2>
<button id="closePrinterDetailBtn" class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white transition-colors">
<svg class="h-5 w-5 sm:h-6 sm:w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div id="printer-detail-content">
<!-- Content will be loaded here -->
</div>
</div>
</div>
<!-- Add Printer Modal - Komplett überarbeitet mit expliziter hidden-Klasse -->
{% if current_user.is_admin %}
<div id="addPrinterModal" class="modal-new hidden" style="display: none !important; visibility: hidden !important;">
<div class="modal-content-new">
<div class="flex items-center justify-between mb-5">
<h2 class="text-xl sm:text-2xl font-bold text-slate-900 dark:text-white">Neuen Drucker hinzufügen</h2>
<button id="closeAddPrinterBtn" class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white transition-colors">
<svg class="h-5 w-5 sm:h-6 sm:w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<form id="addPrinterForm" class="space-y-4">
<div>
<label for="printer-name" class="form-label-new">Name</label>
<input type="text" id="printer-name" name="name" required
class="input-new" placeholder="z.B. Printer 1">
</div>
<div>
<label for="printer-model" class="form-label-new">Modell</label>
<input type="text" id="printer-model" name="model" required
class="input-new" placeholder="z.B. P115">
</div>
<div>
<label for="printer-location" class="form-label-new">Standort</label>
<input type="text" id="printer-location" name="location" required
class="input-new" placeholder="z.B. Werk 040 - Berlin - TBA">
</div>
<div>
<label for="printer-mac" class="form-label-new">MAC-Adresse</label>
<input type="text" id="printer-mac" name="mac_address" required
placeholder="AA:BB:CC:DD:EE:FF"
class="input-new">
</div>
<div>
<label for="printer-ip" class="form-label-new">Plug IP-Adresse</label>
<input type="text" id="printer-ip" name="plug_ip" required
placeholder="192.168.1.100"
class="input-new">
</div>
<div>
<label for="printer-plug-username" class="form-label-new">Plug Benutzername</label>
<input type="text" id="printer-plug-username" name="plug_username" required
placeholder="admin"
class="input-new">
</div>
<div>
<label for="printer-plug-password" class="form-label-new">Plug Passwort</label>
<input type="password" id="printer-plug-password" name="plug_password" required
placeholder="••••••••"
class="input-new">
</div>
<div class="flex gap-3 pt-3">
<button type="button" id="cancelAddPrinterBtn"
class="flex-1 action-btn-new bg-slate-200 hover:bg-slate-300 text-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white">
Abbrechen
</button>
<button type="submit"
class="flex-1 action-btn-new success">
Hinzufügen
</button>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<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
// Sicherstellen, dass alle Modals initial versteckt sind
document.addEventListener('DOMContentLoaded', function() {
console.log("DOM vollständig geladen, initialisiere Seite...");
// WICHTIG: Sofort alle Modals verstecken (mit mehreren Methoden)
const modalElements = document.querySelectorAll('.modal-new');
modalElements.forEach(modal => {
// Mehrere Ansätze kombinieren für maximale Sicherheit
modal.classList.add('hidden');
modal.style.display = 'none';
modal.style.visibility = 'hidden';
modal.setAttribute('aria-hidden', 'true');
});
// Verzögerte Initialisierung
setTimeout(initializePrintersPage, 500);
});
// Hauptinitialisierungsfunktion
function initializePrintersPage() {
try {
console.log("Initialisiere Drucker-Seite...");
// Filter-Buttons einrichten
setupFilters();
// Event-Listener für Buttons und Modals initialisieren
setupEventListeners();
// Drucker laden (einfache Liste ohne Status-Check)
loadPrinters().catch(err => {
console.error("Fehler beim Laden der Drucker:", err);
showError("Drucker konnten nicht geladen werden. Bitte laden Sie die Seite neu.");
});
// Nach kurzer Verzögerung Status-Check durchführen
setTimeout(() => {
try {
loadPrintersWithLiveStatus().catch(err => {
console.error("Fehler beim Status-Check:", err);
});
} catch (error) {
console.error("Fehler beim verzögerten Status-Check:", error);
}
}, 2000);
console.log("Drucker-Seite erfolgreich initialisiert");
} catch (error) {
console.error("Kritischer Fehler bei der Initialisierung:", error);
showError("Ein Fehler ist bei der Initialisierung aufgetreten. Bitte laden Sie die Seite neu.");
}
}
// Event-Listener für Buttons und Modals
function setupEventListeners() {
// "Drucker hinzufügen" Button
const addPrinterBtn = document.getElementById('addPrinterBtn');
if (addPrinterBtn) {
addPrinterBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
showAddPrinterModal();
});
}
// Modal schließen Buttons
setupCloseButtons('closeAddPrinterBtn', hideAddPrinterModal);
setupCloseButtons('closePrinterDetailBtn', hidePrinterDetailModal);
setupCloseButtons('cancelAddPrinterBtn', hideAddPrinterModal);
// Formular-Handler
const addPrinterForm = document.getElementById('addPrinterForm');
if (addPrinterForm) {
addPrinterForm.addEventListener('submit', function(e) {
e.preventDefault();
e.stopPropagation();
handleAddPrinter(e);
});
}
// Schließen bei Klick außerhalb
setupOutsideClickHandler('addPrinterModal', hideAddPrinterModal);
setupOutsideClickHandler('printerDetailModal', hidePrinterDetailModal);
// Escape-Taste für alle Modals
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
hideAllModals();
}
});
// Refresh-Button
const refreshBtn = document.querySelector('button[onclick="refreshPrinters()"]');
if (refreshBtn) {
refreshBtn.addEventListener('click', function(e) {
e.preventDefault();
refreshPrinters();
});
}
// Auto-Refresh-Button
const autoRefreshBtn = document.getElementById('auto-refresh-btn');
if (autoRefreshBtn) {
autoRefreshBtn.addEventListener('click', function(e) {
e.preventDefault();
toggleAutoRefresh();
});
}
}
// Hilfsfunktion für Schließen-Buttons
function setupCloseButtons(buttonId, closeFunction) {
const button = document.getElementById(buttonId);
if (button) {
button.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
closeFunction();
});
}
}
// Hilfsfunktion für Klick außerhalb
function setupOutsideClickHandler(modalId, closeFunction) {
const modal = document.getElementById(modalId);
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === this) {
e.preventDefault();
e.stopPropagation();
closeFunction();
}
});
}
}
// Alle Modals ausblenden
function hideAllModals() {
hideAddPrinterModal();
hidePrinterDetailModal();
}
// Modal-Steuerungsfunktionen
function showAddPrinterModal() {
const modal = document.getElementById('addPrinterModal');
if (!modal) return;
console.log("Öffne 'Drucker hinzufügen' Modal");
modal.classList.remove('hidden');
modal.style.display = '';
modal.style.visibility = '';
modal.setAttribute('aria-hidden', 'false');
}
function hideAddPrinterModal() {
const modal = document.getElementById('addPrinterModal');
if (!modal) return;
console.log("Schließe 'Drucker hinzufügen' Modal");
modal.classList.add('hidden');
modal.style.display = 'none';
modal.style.visibility = 'hidden';
modal.setAttribute('aria-hidden', 'true');
// Formular zurücksetzen
const form = document.getElementById('addPrinterForm');
if (form) form.reset();
}
function showPrinterDetail(printerId) {
// Drucker-Details finden
const printer = printers.find(p => p.id === printerId);
if (!printer) {
console.error("Drucker nicht gefunden:", printerId);
return;
}
// Detail-Inhalt aktualisieren
const content = document.getElementById('printer-detail-content');
if (!content) {
console.error("printer-detail-content Element nicht gefunden");
return;
}
// Prüfen, ob das Modal existiert
const modal = document.getElementById('printerDetailModal');
if (!modal) {
console.error("printerDetailModal Element nicht gefunden");
return;
}
// Modal-Inhalt mit Drucker-Daten füllen
const isOnline = printer.status === 'available' || printer.is_online;
content.innerHTML = `
<div class="space-y-4">
<div class="${isOnline ? 'bg-green-50/80 dark:bg-green-900/30' : 'bg-slate-50/80 dark:bg-slate-700/30'} rounded-lg p-3 sm:p-4 backdrop-blur-sm border ${isOnline ? 'border-green-200 dark:border-green-800/30' : 'border-slate-200 dark:border-slate-600/30'}">
<h3 class="font-semibold ${isOnline ? 'text-green-800 dark:text-green-300' : 'text-slate-900 dark:text-white'} text-lg mb-1">${printer.name}</h3>
<p class="text-sm ${isOnline ? 'text-green-700 dark:text-green-400' : 'text-slate-600 dark:text-slate-400'}">${printer.model}</p>
<div class="mt-3 flex items-center gap-2">
<span class="status-badge-new ${isOnline ? 'online' : 'offline'}">
${isOnline ? '🟢' : '🔴'} ${getPrinterStatusText(printer.status)}
</span>
${printer.last_checked ?
`<span class="text-xs ${isOnline ? 'text-green-600 dark:text-green-400' : 'text-slate-500 dark:text-slate-400'}">
Zuletzt geprüft: ${formatDate(printer.last_checked)}
</span>` : ''}
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div class="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 backdrop-blur-sm border border-slate-200/60 dark:border-slate-700/30">
<div class="text-xs uppercase font-semibold text-slate-500 dark:text-slate-400 mb-1.5">Standort</div>
<div class="text-sm text-slate-900 dark:text-white">${printer.location}</div>
</div>
<div class="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 backdrop-blur-sm border border-slate-200/60 dark:border-slate-700/30">
<div class="text-xs uppercase font-semibold text-slate-500 dark:text-slate-400 mb-1.5">MAC-Adresse</div>
<div class="text-sm text-slate-900 dark:text-white font-mono">${printer.mac_address}</div>
</div>
<div class="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 backdrop-blur-sm border border-slate-200/60 dark:border-slate-700/30">
<div class="text-xs uppercase font-semibold text-slate-500 dark:text-slate-400 mb-1.5">Plug IP</div>
<div class="text-sm text-slate-900 dark:text-white font-mono">${printer.plug_ip}</div>
</div>
<div class="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 backdrop-blur-sm border border-slate-200/60 dark:border-slate-700/30">
<div class="text-xs uppercase font-semibold text-slate-500 dark:text-slate-400 mb-1.5">Erstellt am</div>
<div class="text-sm text-slate-900 dark:text-white">${formatDate(printer.created_at)}</div>
</div>
</div>
<div class="border-t border-slate-200 dark:border-slate-700 pt-4 mt-4">
<div class="flex gap-3">
<button onclick="hidePrinterDetailModal()"
class="flex-1 action-btn-new bg-slate-200 hover:bg-slate-300 text-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white">
Schließen
</button>
{% if current_user.is_admin %}
<button onclick="deletePrinter(${printer.id}); hidePrinterDetailModal();"
class="flex-1 action-btn-new danger">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Löschen
</button>
{% endif %}
</div>
</div>
</div>
`;
// Modal anzeigen
console.log("Öffne Drucker-Details Modal für Drucker:", printerId);
modal.classList.remove('hidden');
modal.style.display = '';
modal.style.visibility = '';
modal.setAttribute('aria-hidden', 'false');
}
function hidePrinterDetailModal() {
const modal = document.getElementById('printerDetailModal');
if (!modal) return;
console.log("Schließe Drucker-Details Modal");
modal.classList.add('hidden');
modal.style.display = 'none';
modal.style.visibility = 'hidden';
modal.setAttribute('aria-hidden', 'true');
}
// Render printers grid mit Filter-Unterstützung
function renderPrinters() {
const grid = document.getElementById('printers-grid');
if (!grid) {
console.error('printers-grid Element nicht gefunden');
return;
}
// 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-8 sm:py-12">
<svg class="h-14 w-14 sm:h-16 sm:w-16 text-slate-300 dark:text-slate-600 mx-auto mb-4 sm:mb-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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 font-medium">${emptyMessage}</p>
${currentFilter === 'all' && printers.length === 0 ? `
{% if current_user.is_admin %}
<button id="addFirstPrinterBtn" class="mt-4 sm:mt-5 action-btn-new success">
<svg class="h-4 w-4 sm:h-5 sm:w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>Ersten Drucker hinzufügen</span>
</button>
{% endif %}
` : ''}
</div>
`;
// Event-Listener für den "Ersten Drucker hinzufügen" Button
const addFirstPrinterBtn = document.getElementById('addFirstPrinterBtn');
if (addFirstPrinterBtn) {
// Entferne vorherige Event-Listener, um doppelte Registrierung zu vermeiden
const clonedBtn = addFirstPrinterBtn.cloneNode(true);
addFirstPrinterBtn.parentNode.replaceChild(clonedBtn, addFirstPrinterBtn);
clonedBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
showAddPrinterModal();
});
}
return;
}
grid.innerHTML = filteredPrinters.map(printer => {
const isOnline = printer.status === 'available' || printer.is_online;
const statusText = getPrinterStatusText(printer.status);
return `
<div class="printer-card-new ${isOnline ? 'online' : ''}">
${isOnline ? '<div class="online-indicator"></div>' : ''}
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="text-base sm:text-lg font-bold ${isOnline ? 'text-green-800 dark:text-green-300' : 'text-slate-900 dark:text-white'}">${printer.name}</h3>
<p class="text-xs sm:text-sm ${isOnline ? 'text-green-700 dark:text-green-400' : 'text-slate-600 dark:text-slate-400'}">${printer.model}</p>
</div>
<div class="flex flex-col items-end">
<span class="status-badge-new ${isOnline ? 'online' : 'offline'}">
${isOnline ? '🟢' : '🔴'} ${statusText}
</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.5">
Geprüft: ${formatTime(printer.last_checked)}
</span>` : ''}
</div>
</div>
<div class="space-y-2 mb-4">
<div class="printer-info-row">
<svg class="printer-info-icon" 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>
<span>${printer.location}</span>
</div>
<div class="printer-info-row">
<svg class="printer-info-icon" 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>
<span class="font-mono text-xs">${printer.mac_address}</span>
</div>
<div class="printer-info-row">
<svg class="printer-info-icon" 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>
<span class="font-mono text-xs">${printer.plug_ip}</span>
</div>
</div>
<div class="flex gap-2">
<button class="printer-detail-btn flex-1 action-btn-new primary text-xs" data-printer-id="${printer.id}">
Details
</button>
{% if current_user.is_admin %}
<button class="delete-printer-btn flex-1 action-btn-new danger text-xs" data-printer-id="${printer.id}">
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Löschen
</button>
{% endif %}
</div>
</div>
`;
}).join('');
// Sichere Ereignisbehandlung: Alte Event-Listener entfernen, indem wir neue Listener hinzufügen
document.querySelectorAll('.printer-detail-btn').forEach(btn => {
// Entferne alte Listener durch Klon-Ersetzung
const clonedBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(clonedBtn, btn);
// Füge neuen Listener hinzu
clonedBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const printerId = parseInt(this.getAttribute('data-printer-id'));
if (printerId) {
showPrinterDetail(printerId);
}
});
});
document.querySelectorAll('.delete-printer-btn').forEach(btn => {
// Entferne alte Listener durch Klon-Ersetzung
const clonedBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(clonedBtn, btn);
// Füge neuen Listener hinzu
clonedBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const printerId = parseInt(this.getAttribute('data-printer-id'));
if (printerId) {
deletePrinter(printerId);
}
});
});
}
// Helper functions
function getPrinterStatusColor(status) {
switch (status) {
case 'available': return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300';
case 'busy': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300';
case 'offline': return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300';
case 'maintenance': return 'bg-slate-200 text-slate-800 dark:bg-slate-700 dark:text-slate-300';
default: return 'bg-slate-200 text-slate-800 dark:bg-slate-700 dark:text-slate-300';
}
}
function getPrinterStatusText(status) {
switch (status) {
case 'available': return 'Steckdose AN';
case 'busy': return 'Beschäftigt';
case 'offline': return 'Steckdose AUS';
case 'maintenance': return 'Wartung';
default: return 'Unbekannt';
}
}
// Format date helper
function formatDate(dateString) {
if (!dateString) return 'Unbekannt';
try {
const date = new Date(dateString);
return date.toLocaleString('de-DE');
} catch (e) {
console.error('Fehler beim Formatieren des Datums:', e);
return 'Ungültiges Datum';
}
}
// Format time helper für Status-Zeitstempel
function formatTime(dateString) {
if (!dateString) return 'Unbekannt';
try {
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
if (diffSecs < 60) {
return 'gerade eben';
} else if (diffMins < 60) {
return `vor ${diffMins} Min`;
} else {
return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
}
} catch (e) {
console.error('Fehler beim Formatieren der Zeit:', e);
return 'Ungültige Zeit';
}
}
// Refresh printers mit Status-Check - Make it globally available
function refreshPrinters() {
console.log('refreshPrinters function called');
const grid = document.getElementById('printers-grid');
const refreshBtn = document.querySelector('button[onclick="refreshPrinters()"]');
// Button deaktivieren und Loading-State anzeigen
if (refreshBtn) {
refreshBtn.disabled = true;
refreshBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
Überprüfe Status...
`;
}
// Loading-State im Grid anzeigen
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Überprüfe Drucker-Status...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies kann bis zu 7 Sekunden dauern</p>
</div>
`;
// Drucker laden mit Live-Status-Check
loadPrintersWithLiveStatus().finally(() => {
// Button wieder aktivieren
if (refreshBtn) {
refreshBtn.disabled = false;
refreshBtn.innerHTML = `
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Jetzt aktualisieren
`;
}
});
}
// Show error message
function showError(message) {
const grid = document.getElementById('printers-grid');
if (!grid) return;
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-red-500 dark:text-red-400 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="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Drucker konnten nicht geladen werden</h3>
<p class="text-slate-700 dark:text-slate-300 text-sm sm:text-base mb-4 max-w-md mx-auto">${message}</p>
<div class="flex flex-col sm:flex-row gap-3 justify-center items-center">
<button onclick="loadPrinters()" class="bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white px-4 sm:px-6 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center">
<svg class="h-4 w-4 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>
Erneut versuchen
</button>
<button onclick="refreshPrinters()" class="bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-500 text-white px-4 sm:px-6 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Mit Status-Check
</button>
</div>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-3">
Tipp: "Mit Status-Check" dauert länger, überprüft aber die Verfügbarkeit aller Drucker
</p>
</div>
`;
}
// Show status message (success, info, warning, error)
function showStatusMessage(message, type = 'info') {
// Vorzeitig beenden, wenn der Ladevorgang noch nicht abgeschlossen ist
if (document.readyState !== 'complete') {
console.log('Status-Nachricht unterdrückt während des Ladens:', message);
return;
}
// Entferne vorherige Status-Nachrichten
const existingMessage = document.getElementById('status-message');
if (existingMessage) {
existingMessage.remove();
}
// Bestimme Farben basierend auf Typ
let bgColor, textColor, iconPath;
switch (type) {
case 'success':
bgColor = 'bg-green-100 dark:bg-green-900/30';
textColor = 'text-green-800 dark:text-green-200';
iconPath = 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z';
break;
case 'warning':
bgColor = 'bg-yellow-100 dark:bg-yellow-900/30';
textColor = 'text-yellow-800 dark:text-yellow-200';
iconPath = 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z';
break;
case 'error':
bgColor = 'bg-red-100 dark:bg-red-900/30';
textColor = 'text-red-800 dark:text-red-200';
iconPath = 'M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
break;
default: // info
bgColor = 'bg-blue-100 dark:bg-blue-900/30';
textColor = 'text-blue-800 dark:text-blue-200';
iconPath = 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
}
// Erstelle Status-Nachricht
const statusMessage = document.createElement('div');
statusMessage.id = 'status-message';
statusMessage.className = `fixed top-4 right-4 z-50 ${bgColor} ${textColor} px-4 py-3 rounded-lg shadow-lg flex items-center space-x-3 max-w-md`;
statusMessage.innerHTML = `
<svg class="h-5 w-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${iconPath}" />
</svg>
<span class="text-sm font-medium">${message}</span>
<button onclick="this.parentElement.remove()" class="ml-2 text-current hover:opacity-75">
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
`;
// Füge zur Seite hinzu
document.body.appendChild(statusMessage);
// Automatisch nach 5 Sekunden entfernen
setTimeout(() => {
if (statusMessage && statusMessage.parentElement) {
statusMessage.remove();
}
}, 5000);
}
// Load printers (schnelles Laden ohne Status-Check)
async function loadPrinters() {
const grid = document.getElementById('printers-grid');
if (!grid) return;
// Loading-State anzeigen
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Lade Drucker...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies sollte nur wenige Sekunden dauern</p>
</div>
`;
try {
// Erstelle einen AbortController für Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 Sekunden Timeout
const response = await fetch('/api/printers', {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
if (response.status === 408) {
throw new Error('Timeout beim Laden der Drucker. Bitte versuchen Sie es erneut.');
}
throw new Error(`Server-Fehler: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Prüfe auf Server-seitige Fehler
if (data.error) {
throw new Error(data.error);
}
// Verwende die korrekten Daten aus der neuen API-Antwort
printers = data.printers || [];
console.log(`Successfully loaded ${printers.length} printers (ohne Status-Check)`);
renderPrinters();
// Zeige Erfolgsmeldung nur wenn Drucker vorhanden sind
if (printers.length > 0) {
showStatusMessage(`${printers.length} Drucker erfolgreich geladen (ohne aktuellen Status)`, 'success');
}
return printers;
} catch (error) {
console.error('Error loading printers:', error);
// Spezielle Behandlung für verschiedene Fehlertypen
let errorMessage = 'Fehler beim Laden der Drucker';
if (error.name === 'AbortError') {
errorMessage = 'Timeout beim Laden der Drucker. Die Anfrage dauerte zu lange.';
} else if (error.message) {
errorMessage = error.message;
}
showError(errorMessage);
return [];
}
}
// Status-Übersicht aktualisieren
function updateStatusOverview(onlineCount, offlineCount, totalCount) {
const onlineElement = document.getElementById('online-count');
const offlineElement = document.getElementById('offline-count');
const totalElement = document.getElementById('total-count');
if (onlineElement) onlineElement.textContent = onlineCount || 0;
if (offlineElement) offlineElement.textContent = offlineCount || 0;
if (totalElement) totalElement.textContent = totalCount || 0;
// Animiere die Online-Anzeige bei Änderungen
if (onlineElement && onlineElement.dataset.lastValue !== String(onlineCount)) {
onlineElement.classList.add('animate-pulse');
setTimeout(() => onlineElement.classList.remove('animate-pulse'), 1000);
onlineElement.dataset.lastValue = String(onlineCount);
}
}
// Erweiterte Funktion zum Laden der Drucker mit Live-Status
async function loadPrintersWithLiveStatus() {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 Sekunden Timeout für Live-Status
const response = await fetch('/api/printers/status/live', {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
if (response.status === 408) {
throw new Error('Timeout beim Live-Status-Check der Drucker.');
}
throw new Error(`Fehler beim Laden des Live-Status: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
// Drucker-Daten aktualisieren
printers = data.printers || [];
// Status-Übersicht aktualisieren
updateStatusOverview(data.online_count, data.offline_count, data.count);
// Drucker rendern
renderPrinters();
// Auto-Update-Timer aktualisieren
if (data.next_update) {
nextUpdateTime = data.next_update;
updateNextUpdateDisplay();
}
// Erfolgs-Nachricht nur bei manueller Aktualisierung
if (!autoRefreshEnabled) {
showStatusMessage(
`Live-Status aktualisiert: ${data.online_count} von ${data.count} Drucker online`,
data.online_count > 0 ? 'success' : 'warning'
);
}
return true; // Erfolg signalisieren
} catch (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 ohne Status-Check
try {
await loadPrinters();
return true; // Fallback war erfolgreich
} catch (fallbackError) {
console.error('Fallback error:', fallbackError);
showError('Drucker konnten nicht geladen werden. Bitte versuchen Sie es später erneut.');
return false; // Auch Fallback fehlgeschlagen
}
}
}
// Auto-Refresh-Funktionalität
function toggleAutoRefresh() {
autoRefreshEnabled = !autoRefreshEnabled;
const btn = document.getElementById('auto-refresh-btn');
const icon = document.getElementById('auto-refresh-icon');
if (autoRefreshEnabled) {
if (btn) {
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 {
if (btn) {
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;
}
const element = document.getElementById('next-update-time');
if (element) element.textContent = '-';
}
function updateNextUpdateDisplay() {
const element = document.getElementById('next-update-time');
if (!element) return;
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-new');
filterButtons.forEach(btn => {
btn.addEventListener('click', function() {
// Entferne active-Klasse von allen Buttons
filterButtons.forEach(b => {
b.classList.remove('active');
});
// Füge active-Klasse zum geklickten Button hinzu
this.classList.add('active');
// Setze aktuellen Filter
currentFilter = this.id.replace('filter-', '');
// Rendere Drucker mit Filter
renderPrinters();
});
});
}
// Delete printer
async function deletePrinter(printerId) {
if (!confirm('Sind Sie sicher, dass Sie diesen Drucker löschen möchten?')) {
return;
}
try {
const response = await fetch(`/api/printers/${printerId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Fehler beim Löschen des Druckers');
}
const result = await response.json();
hidePrinterDetailModal();
showStatusMessage(result.message || 'Drucker erfolgreich gelöscht', 'success');
loadPrinters();
} catch (error) {
console.error('Error deleting printer:', error);
showStatusMessage('Fehler beim Löschen des Druckers: ' + error.message, 'error');
}
}
// Add printer
async function handleAddPrinter(event) {
event.preventDefault();
const form = document.getElementById('addPrinterForm');
const submitBtn = form.querySelector('button[type="submit"]');
const formData = new FormData(form);
// Deaktiviere Submit Button während der Verarbeitung
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
Wird hinzugefügt...
`;
// Sammle und validiere Formulardaten
const printerData = {
name: formData.get('name')?.trim(),
model: formData.get('model')?.trim(),
location: formData.get('location')?.trim(),
mac_address: formData.get('mac_address')?.trim().toUpperCase(),
plug_ip: formData.get('plug_ip')?.trim(),
plug_username: formData.get('plug_username')?.trim(),
plug_password: formData.get('plug_password') // Passwort nicht trimmen
};
// Client-seitige Validierung
const errors = [];
if (!printerData.name) errors.push('Name ist erforderlich');
if (!printerData.model) errors.push('Modell ist erforderlich');
if (!printerData.location) errors.push('Standort ist erforderlich');
if (!printerData.mac_address) errors.push('MAC-Adresse ist erforderlich');
if (!printerData.plug_ip) errors.push('Plug IP ist erforderlich');
if (!printerData.plug_username) errors.push('Plug Benutzername ist erforderlich');
if (!printerData.plug_password) errors.push('Plug Passwort ist erforderlich');
// MAC-Adresse Format validieren
if (printerData.mac_address && !/^([0-9A-F]{2}:){5}[0-9A-F]{2}$/.test(printerData.mac_address)) {
// Versuche automatische Formatierung
const cleanMac = printerData.mac_address.replace(/[^0-9A-F]/g, '');
if (cleanMac.length === 12) {
printerData.mac_address = cleanMac.match(/.{2}/g).join(':');
} else {
errors.push('MAC-Adresse muss im Format AA:BB:CC:DD:EE:FF sein');
}
}
// IP-Adresse Format validieren
if (printerData.plug_ip && !/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(printerData.plug_ip)) {
errors.push('IP-Adresse muss im Format 192.168.1.100 sein');
}
if (errors.length > 0) {
showStatusMessage(`Bitte korrigieren Sie folgende Fehler:\n${errors.join('\n')}`, 'error');
// Button wieder aktivieren
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
return;
}
try {
// Erstelle AbortController für Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 Sekunden Timeout
const response = await fetch('/api/printers/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(printerData),
signal: controller.signal
});
clearTimeout(timeoutId);
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || `Server-Fehler: ${response.status}`);
}
// Erfolg!
hideAddPrinterModal();
showStatusMessage(result.message || 'Drucker erfolgreich hinzugefügt', 'success');
// Drucker-Liste neu laden
await loadPrinters();
console.log('Printer added successfully:', result.printer);
} catch (error) {
console.error('Error adding printer:', error);
let errorMessage = 'Fehler beim Hinzufügen des Druckers';
if (error.name === 'AbortError') {
errorMessage = 'Timeout beim Hinzufügen des Druckers. Bitte versuchen Sie es erneut.';
} else if (error.message) {
errorMessage = error.message;
}
showStatusMessage(errorMessage, 'error');
} finally {
// Button wieder aktivieren
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
}
}
// Make all functions globally available for onclick handlers
window.showPrinterDetail = showPrinterDetail;
window.hidePrinterDetailModal = hidePrinterDetailModal;
window.deletePrinter = deletePrinter;
window.loadPrinters = loadPrinters;
window.handleAddPrinter = handleAddPrinter;
window.toggleAutoRefresh = toggleAutoRefresh;
window.loadPrintersWithLiveStatus = loadPrintersWithLiveStatus;
window.refreshPrinters = refreshPrinters;
window.hideAddPrinterModal = hideAddPrinterModal;
window.showAddPrinterModal = showAddPrinterModal;
</script>
{% endblock %}