1249 lines
57 KiB
HTML
1249 lines
57 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Drucker - Mercedes-Benz MYP Platform{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="bg-professional">
|
|
<!-- Dark Mode Override -->
|
|
<style>
|
|
.dark .bg-professional {
|
|
background: #000000 !important;
|
|
}
|
|
.dark .professional-hero {
|
|
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%) !important;
|
|
border-color: #333333 !important;
|
|
}
|
|
.dark .professional-container {
|
|
background: #111111 !important;
|
|
border-color: #333333 !important;
|
|
}
|
|
.dark .mb-glass {
|
|
background: rgba(17, 17, 17, 0.95) !important;
|
|
border-color: rgba(255, 255, 255, 0.1) !important;
|
|
}
|
|
</style>
|
|
|
|
<!-- Professional Hero Header -->
|
|
<div class="professional-hero hero-pattern animate-fade-in" style="margin: 2rem; margin-bottom: 3rem;">
|
|
<div class="absolute inset-0 bg-gradient-to-r from-black/10 to-black/20 dark:from-black/40 dark:to-black/60"></div>
|
|
|
|
<!-- Status Indicator -->
|
|
<div class="absolute top-6 right-6 flex items-center space-x-4 z-10">
|
|
<div class="mb-glass rounded-full px-6 py-3 animate-scale-in">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="status-dot status-online"></div>
|
|
<span class="text-sm font-semibold text-professional-primary">Live Status</span>
|
|
</div>
|
|
</div>
|
|
<div class="mb-glass rounded-full px-6 py-3 animate-scale-in">
|
|
<span id="live-time" class="text-sm font-semibold text-professional-primary"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="relative max-w-7xl mx-auto px-6 lg:px-8 py-20 z-10">
|
|
<div class="text-center animate-slide-up">
|
|
<!-- Mercedes-Benz Logo -->
|
|
<div class="inline-flex items-center justify-center w-28 h-28 mb-glass rounded-full mb-10 professional-shadow">
|
|
<svg class="w-14 h-14 text-professional-primary" viewBox="0 0 80 80" fill="currentColor">
|
|
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
|
|
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
|
|
c0-13.1,7-24.5,17.5-30.9v0C26.6,6,32.5,4.2,39,4l-4.5,32.7L21.5,46.8v0L8.3,57.1C5.6,52,4,46.2,4,40z M58.6,70.8
|
|
C53.1,74.1,46.8,76,40,76c-6.8,0-13.2-1.9-18.6-5.2c-4.9-2.9-8.9-6.9-11.9-11.7l11.9-4.9v0L40,46.6l18.6,7.5v0l12,4.9
|
|
C67.6,63.9,63.4,67.9,58.6,70.8z M58.6,46.8L58.6,46.8l-12.9-10L41.1,4c6.3,0.2,12.3,2,17.4,5.1v0C69,15.4,76,26.9,76,40
|
|
c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<h1 class="title-professional text-6xl md:text-7xl font-bold mb-8 tracking-tight">
|
|
3D-Drucker Management
|
|
</h1>
|
|
<p class="subtitle-professional text-2xl md:text-3xl max-w-5xl mx-auto leading-relaxed mb-12">
|
|
Verwalten Sie Ihre 3D-Drucker mit höchster Präzision und Mercedes-Benz Qualitätsstandard
|
|
</p>
|
|
|
|
<!-- Status Overview -->
|
|
<div class="flex flex-wrap justify-center gap-8 mb-12">
|
|
<div class="mb-glass rounded-3xl px-8 py-6 animate-scale-in min-w-[200px]">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="status-dot status-online w-4 h-4"></div>
|
|
<div class="text-left">
|
|
<div class="text-sm text-professional-muted uppercase tracking-wide">Online</div>
|
|
<div class="text-3xl font-bold text-green-500" id="online-count">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-glass rounded-3xl px-8 py-6 animate-scale-in min-w-[200px]">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="status-dot status-offline w-4 h-4"></div>
|
|
<div class="text-left">
|
|
<div class="text-sm text-professional-muted uppercase tracking-wide">Offline</div>
|
|
<div class="text-3xl font-bold text-red-500" id="offline-count">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-glass rounded-3xl px-8 py-6 animate-scale-in min-w-[200px]">
|
|
<div class="flex items-center space-x-4">
|
|
<svg class="w-6 h-6 text-professional-accent" fill="currentColor" viewBox="0 0 20 20">
|
|
<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"/>
|
|
</svg>
|
|
<div class="text-left">
|
|
<div class="text-sm text-professional-muted uppercase tracking-wide">Gesamt</div>
|
|
<div class="text-3xl font-bold text-professional-accent" id="total-count">-</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="flex flex-wrap justify-center gap-6">
|
|
<!-- Filter Buttons -->
|
|
<div class="flex bg-white/10 dark:bg-black/30 backdrop-blur-sm rounded-3xl p-3 border border-white/20 dark:border-white/10">
|
|
<button id="filter-all" class="filter-btn-mercedes active px-6 py-3 rounded-2xl text-sm font-semibold transition-all duration-300">
|
|
Alle Drucker
|
|
</button>
|
|
<button id="filter-online" class="filter-btn-mercedes px-6 py-3 rounded-2xl text-sm font-semibold transition-all duration-300">
|
|
Online
|
|
</button>
|
|
<button id="filter-offline" class="filter-btn-mercedes px-6 py-3 rounded-2xl text-sm font-semibold transition-all duration-300">
|
|
Offline
|
|
</button>
|
|
</div>
|
|
|
|
<button onclick="toggleAutoRefresh()" id="auto-refresh-btn" class="btn-professional group px-8 py-4">
|
|
<svg class="w-6 h-6 mr-3 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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="btn-professional group px-8 py-4">
|
|
<svg class="w-6 h-6 mr-3 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>Status aktualisieren</span>
|
|
</button>
|
|
|
|
{% if current_user.is_admin %}
|
|
<button id="addPrinterBtn" class="btn-success-professional group px-8 py-4">
|
|
<svg class="w-6 h-6 mr-3 group-hover:scale-110 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
|
|
<!-- Auto-Update Info -->
|
|
<div class="mt-8 flex items-center justify-center space-x-3 text-professional-muted">
|
|
<svg id="auto-refresh-icon" class="w-5 h-5 animate-spin-slow" 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-base">Auto-Update: <span id="next-update-time" class="font-mono font-bold">-</span>s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-6 lg:px-8 -mt-12 relative z-10" style="margin-bottom: 4rem;">
|
|
|
|
<!-- Printers Grid -->
|
|
<div class="professional-container animate-slide-up" style="padding: 3rem; border-radius: 2rem; margin-bottom: 2rem;">
|
|
<div class="text-center mb-16">
|
|
<h2 class="title-professional text-4xl font-bold mb-6">
|
|
Verfügbare 3D-Drucker
|
|
</h2>
|
|
<p class="subtitle-professional text-xl max-w-3xl mx-auto">
|
|
Übersicht und Verwaltung Ihrer gesamten 3D-Drucker-Infrastruktur mit Echtzeit-Statusanzeige
|
|
</p>
|
|
</div>
|
|
|
|
<div id="printers-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
<!-- Loading state -->
|
|
<div class="col-span-full text-center py-16">
|
|
<div class="animate-spin rounded-full h-16 w-16 border-b-3 border-professional-accent mx-auto mb-8"></div>
|
|
<h3 class="text-2xl font-bold text-professional-primary mb-4">Lade Drucker...</h3>
|
|
<p class="text-lg text-professional-secondary">Dies sollte nur wenige Sekunden dauern</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Drucker Detail Modal -->
|
|
<div id="printerDetailModal" class="fixed inset-0 bg-black/60 backdrop-blur-sm hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="professional-container max-w-2xl w-full p-8 transform transition-all duration-500 scale-95 opacity-0" id="printerModalContent">
|
|
<div class="flex justify-between items-center mb-8">
|
|
<div>
|
|
<h3 class="text-2xl font-bold text-professional-primary mb-2">Drucker Details</h3>
|
|
<p class="text-professional-muted">Detaillierte Informationen zu Ihrem 3D-Drucker</p>
|
|
</div>
|
|
<button id="closePrinterDetailBtn" class="p-2 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-xl transition-colors duration-200">
|
|
<svg class="w-6 h-6 text-professional-muted hover:text-professional-primary" 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"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="printer-detail-content">
|
|
<!-- Inhalt wird dynamisch geladen -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Printer Modal -->
|
|
{% if current_user.is_admin %}
|
|
<div id="addPrinterModal" class="fixed inset-0 bg-black/60 backdrop-blur-sm hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="professional-container max-w-lg w-full p-8 transform transition-all duration-500 scale-95 opacity-0" id="addPrinterModalContent">
|
|
<div class="flex justify-between items-center mb-8">
|
|
<div>
|
|
<h3 class="text-2xl font-bold text-professional-primary mb-2">Neuen Drucker hinzufügen</h3>
|
|
<p class="text-professional-muted">Konfigurieren Sie einen neuen 3D-Drucker</p>
|
|
</div>
|
|
<button id="closeAddPrinterBtn" class="p-2 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-xl transition-colors duration-200">
|
|
<svg class="w-6 h-6 text-professional-muted hover:text-professional-primary" 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"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<form id="addPrinterForm" class="space-y-6">
|
|
<div class="grid grid-cols-1 gap-6">
|
|
<div class="group">
|
|
<label for="printer-name" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
Drucker Name <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printer-name" name="name" required
|
|
class="input-professional" placeholder="z.B. Printer 1">
|
|
</div>
|
|
|
|
<div class="group">
|
|
<label for="printer-model" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
Modell <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printer-model" name="model" required
|
|
class="input-professional" placeholder="z.B. P115">
|
|
</div>
|
|
|
|
<div class="group">
|
|
<label for="printer-location" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
Standort <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printer-location" name="location" required
|
|
class="input-professional" placeholder="z.B. Werk 040 - Berlin - TBA">
|
|
</div>
|
|
|
|
<div class="group">
|
|
<label for="printer-mac" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
MAC-Adresse <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printer-mac" name="mac_address" required
|
|
placeholder="AA:BB:CC:DD:EE:FF"
|
|
class="input-professional font-mono">
|
|
</div>
|
|
|
|
<div class="group">
|
|
<label for="printer-ip" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
Plug IP-Adresse <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printer-ip" name="plug_ip" required
|
|
placeholder="192.168.1.100"
|
|
class="input-professional font-mono">
|
|
</div>
|
|
|
|
<div class="group">
|
|
<label for="printer-plug-username" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
Plug Benutzername <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printer-plug-username" name="plug_username" required
|
|
placeholder="admin"
|
|
class="input-professional">
|
|
</div>
|
|
|
|
<div class="group">
|
|
<label for="printer-plug-password" class="block text-sm font-semibold text-professional-primary mb-3">
|
|
Plug Passwort <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="password" id="printer-plug-password" name="plug_password" required
|
|
placeholder="••••••••"
|
|
class="input-professional">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-end space-x-4 pt-8 border-t border-slate-200 dark:border-slate-600">
|
|
<button type="button" id="cancelAddPrinterBtn"
|
|
class="btn-secondary-professional">
|
|
Abbrechen
|
|
</button>
|
|
<button type="submit"
|
|
class="btn-success-professional group">
|
|
<svg class="w-5 h-5 mr-3 group-hover:scale-110 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
</svg>
|
|
<span>Drucker hinzufügen</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Live Time Update
|
|
function updateLiveTime() {
|
|
const now = new Date();
|
|
const timeElement = document.getElementById('live-time');
|
|
if (timeElement) {
|
|
timeElement.textContent = now.toLocaleTimeString('de-DE');
|
|
}
|
|
}
|
|
updateLiveTime();
|
|
setInterval(updateLiveTime, 1000);
|
|
|
|
// Filter Button Styling
|
|
function setupFilterButtons() {
|
|
const filterButtons = document.querySelectorAll('.filter-btn-mercedes');
|
|
|
|
filterButtons.forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
// Remove active class from all buttons
|
|
filterButtons.forEach(b => {
|
|
b.classList.remove('active');
|
|
b.classList.remove('bg-professional-accent', 'text-white');
|
|
b.classList.add('text-professional-muted', 'hover:text-professional-primary');
|
|
});
|
|
|
|
// Add active class to clicked button
|
|
this.classList.add('active');
|
|
this.classList.remove('text-professional-muted', 'hover:text-professional-primary');
|
|
this.classList.add('bg-professional-accent', 'text-white');
|
|
|
|
// Update filter
|
|
const filterId = this.id;
|
|
if (filterId === 'filter-all') {
|
|
currentFilter = 'all';
|
|
} else if (filterId === 'filter-online') {
|
|
currentFilter = 'online';
|
|
} else if (filterId === 'filter-offline') {
|
|
currentFilter = 'offline';
|
|
}
|
|
|
|
// Re-render printers with new filter
|
|
renderPrinters();
|
|
});
|
|
});
|
|
}
|
|
|
|
// Modal Animations - Professional Mercedes Style
|
|
function showModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
const content = modal.querySelector('.professional-container');
|
|
|
|
if (!modal || !content) {
|
|
console.error('Modal or content not found:', modalId);
|
|
return;
|
|
}
|
|
|
|
modal.classList.remove('hidden');
|
|
modal.style.display = '';
|
|
modal.style.visibility = '';
|
|
modal.setAttribute('aria-hidden', 'false');
|
|
|
|
setTimeout(() => {
|
|
content.classList.remove('scale-95', 'opacity-0');
|
|
content.classList.add('scale-100', 'opacity-100');
|
|
}, 10);
|
|
}
|
|
|
|
function hideModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
const content = modal.querySelector('.professional-container');
|
|
|
|
if (!modal || !content) {
|
|
console.error('Modal or content not found:', modalId);
|
|
return;
|
|
}
|
|
|
|
content.classList.remove('scale-100', 'opacity-100');
|
|
content.classList.add('scale-95', 'opacity-0');
|
|
|
|
setTimeout(() => {
|
|
modal.classList.add('hidden');
|
|
modal.style.display = 'none';
|
|
modal.style.visibility = 'hidden';
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
}, 200);
|
|
}
|
|
|
|
// 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('[id$="Modal"]');
|
|
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
|
|
setupFilterButtons();
|
|
|
|
// 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();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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() {
|
|
console.log("Öffne 'Drucker hinzufügen' Modal");
|
|
showModal('addPrinterModal');
|
|
}
|
|
|
|
function hideAddPrinterModal() {
|
|
console.log("Schließe 'Drucker hinzufügen' Modal");
|
|
hideModal('addPrinterModal');
|
|
|
|
// Formular zurücksetzen
|
|
const form = document.getElementById('addPrinterForm');
|
|
if (form) form.reset();
|
|
}
|
|
|
|
function hidePrinterDetailModal() {
|
|
console.log("Schließe Drucker-Details Modal");
|
|
hideModal('printerDetailModal');
|
|
}
|
|
|
|
// Render printers grid mit Filter-Unterstützung - Mercedes Professional Style
|
|
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-12">
|
|
<div class="mb-glass rounded-3xl p-12 max-w-md mx-auto">
|
|
<svg class="h-16 w-16 text-professional-muted mx-auto mb-6" 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>
|
|
<h3 class="text-xl font-bold text-professional-primary mb-3">${emptyMessage}</h3>
|
|
<p class="text-professional-muted mb-6">
|
|
${currentFilter === 'all' && printers.length === 0 ?
|
|
'Fügen Sie Ihren ersten 3D-Drucker hinzu, um loszulegen.' :
|
|
'Versuchen Sie einen anderen Filter oder aktualisieren Sie die Ansicht.'}
|
|
</p>
|
|
|
|
${currentFilter === 'all' && printers.length === 0 ? `
|
|
{% if current_user.is_admin %}
|
|
<button id="addFirstPrinterBtn" class="btn-success-professional group">
|
|
<svg class="w-6 h-6 mr-3 group-hover:scale-110 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
</div>
|
|
`;
|
|
|
|
// Event-Listener für den "Ersten Drucker hinzufügen" Button
|
|
const addFirstPrinterBtn = document.getElementById('addFirstPrinterBtn');
|
|
if (addFirstPrinterBtn) {
|
|
addFirstPrinterBtn.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="mb-glass p-6 rounded-3xl border border-white/20 dark:border-white/10 hover:border-professional-accent/50 dark:hover:border-professional-accent/30 transition-all duration-300 group cursor-pointer transform hover:scale-105 ${isOnline ? 'ring-2 ring-green-500/20 dark:ring-green-400/20' : ''}" onclick="showPrinterDetail(${printer.id})">
|
|
|
|
<!-- Status Indicator -->
|
|
${isOnline ? '<div class="absolute -top-2 -right-2 w-6 h-6 bg-green-500 rounded-full border-4 border-white dark:border-slate-800 animate-pulse"></div>' : ''}
|
|
|
|
<!-- Header -->
|
|
<div class="flex items-start justify-between mb-6">
|
|
<div class="flex-1">
|
|
<h3 class="text-xl font-bold text-professional-primary mb-2 group-hover:text-professional-accent transition-colors duration-300">${printer.name}</h3>
|
|
<p class="text-professional-secondary mb-3">${printer.model}</p>
|
|
|
|
<!-- Status Badge -->
|
|
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold ${isOnline ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300' : 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300'}">
|
|
${isOnline ? '🟢' : '🔴'} ${statusText}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Icon -->
|
|
<div class="w-10 h-10 bg-gradient-to-br from-professional-accent/20 to-professional-primary/20 rounded-xl flex items-center justify-center group-hover:from-professional-accent/30 group-hover:to-professional-primary/30 transition-all duration-300">
|
|
<svg class="w-5 h-5 text-professional-accent group-hover:scale-110 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details -->
|
|
<div class="space-y-3 mb-6">
|
|
<div class="flex items-center space-x-3 text-sm text-professional-secondary">
|
|
<svg class="w-4 h-4 text-professional-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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 class="truncate">${printer.location}</span>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-3 text-sm text-professional-muted">
|
|
<svg class="w-4 h-4 text-professional-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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="flex items-center space-x-3 text-sm text-professional-muted">
|
|
<svg class="w-4 h-4 text-professional-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
|
|
${printer.last_checked ?
|
|
`<div class="flex items-center space-x-3 text-xs text-professional-muted">
|
|
<svg class="w-4 h-4 text-professional-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>Geprüft: ${formatTime(printer.last_checked)}</span>
|
|
</div>` : ''}
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex gap-3">
|
|
<div class="flex-1 btn-professional-mini text-center opacity-80 group-hover:opacity-100 transition-opacity duration-300">
|
|
Details anzeigen
|
|
</div>
|
|
|
|
{% if current_user.is_admin %}
|
|
<button class="btn-danger-professional-mini" onclick="event.stopPropagation(); deletePrinter(${printer.id});">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// 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 w-6 h-6 mr-3" 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-12">
|
|
<div class="mb-glass rounded-3xl p-12 max-w-md mx-auto">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-professional-accent mx-auto mb-6"></div>
|
|
<h3 class="text-xl font-bold text-professional-primary mb-3">Überprüfe Drucker-Status...</h3>
|
|
<p class="text-professional-muted">Dies kann bis zu 7 Sekunden dauern</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Drucker laden mit Live-Status-Check
|
|
loadPrintersWithLiveStatus().finally(() => {
|
|
// Button wieder aktivieren
|
|
if (refreshBtn) {
|
|
refreshBtn.disabled = false;
|
|
refreshBtn.innerHTML = `
|
|
<svg class="w-6 h-6 mr-3 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
`;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Show error message with Professional Styling
|
|
function showError(message) {
|
|
const grid = document.getElementById('printers-grid');
|
|
if (!grid) return;
|
|
|
|
grid.innerHTML = `
|
|
<div class="col-span-full text-center py-12">
|
|
<div class="mb-glass rounded-3xl p-12 max-w-lg mx-auto">
|
|
<svg class="h-16 w-16 text-red-500 dark:text-red-400 mx-auto mb-6" 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-xl font-bold text-professional-primary mb-3">Drucker konnten nicht geladen werden</h3>
|
|
<p class="text-professional-muted mb-6">${message}</p>
|
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<button onclick="loadPrinters()" class="btn-professional group">
|
|
<svg class="w-5 h-5 mr-3 group-hover:rotate-180 transition-transform duration-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>Erneut versuchen</span>
|
|
</button>
|
|
<button onclick="refreshPrinters()" class="btn-success-professional group">
|
|
<svg class="w-5 h-5 mr-3 group-hover:scale-110 transition-transform duration-300" 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>
|
|
<span>Mit Status-Check</span>
|
|
</button>
|
|
</div>
|
|
<p class="text-xs text-professional-muted mt-4">
|
|
Tipp: "Mit Status-Check" dauert länger, überprüft aber die Verfügbarkeit aller Drucker
|
|
</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Show status message (success, info, warning, error) - Professional Style
|
|
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, borderColor;
|
|
switch (type) {
|
|
case 'success':
|
|
bgColor = 'bg-green-50 dark:bg-green-900/20';
|
|
textColor = 'text-green-800 dark:text-green-200';
|
|
borderColor = 'border-green-200 dark:border-green-800/30';
|
|
iconPath = 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
break;
|
|
case 'warning':
|
|
bgColor = 'bg-yellow-50 dark:bg-yellow-900/20';
|
|
textColor = 'text-yellow-800 dark:text-yellow-200';
|
|
borderColor = 'border-yellow-200 dark:border-yellow-800/30';
|
|
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-50 dark:bg-red-900/20';
|
|
textColor = 'text-red-800 dark:text-red-200';
|
|
borderColor = 'border-red-200 dark:border-red-800/30';
|
|
iconPath = 'M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
break;
|
|
default: // info
|
|
bgColor = 'bg-blue-50 dark:bg-blue-900/20';
|
|
textColor = 'text-blue-800 dark:text-blue-200';
|
|
borderColor = 'border-blue-200 dark:border-blue-800/30';
|
|
iconPath = 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
}
|
|
|
|
// Erstelle Status-Nachricht mit Professional Styling
|
|
const statusMessage = document.createElement('div');
|
|
statusMessage.id = 'status-message';
|
|
statusMessage.className = `fixed top-6 right-6 z-50 ${bgColor} ${textColor} ${borderColor} border backdrop-blur-sm rounded-2xl shadow-lg flex items-center space-x-4 px-6 py-4 max-w-md professional-shadow`;
|
|
statusMessage.innerHTML = `
|
|
<svg class="h-6 w-6 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 flex-1">${message}</span>
|
|
<button onclick="this.parentElement.remove()" class="ml-2 text-current hover:opacity-75 transition-opacity duration-200">
|
|
<svg class="h-5 w-5" 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"/>
|
|
</svg>
|
|
</button>
|
|
`;
|
|
|
|
// Füge zur Seite hinzu mit Animation
|
|
document.body.appendChild(statusMessage);
|
|
statusMessage.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
statusMessage.style.transform = 'translateX(0)';
|
|
statusMessage.style.transition = 'transform 0.3s ease-out';
|
|
}, 10);
|
|
|
|
// Automatisch nach 5 Sekunden entfernen
|
|
setTimeout(() => {
|
|
if (statusMessage && statusMessage.parentElement) {
|
|
statusMessage.style.transform = 'translateX(100%)';
|
|
setTimeout(() => statusMessage.remove(), 300);
|
|
}
|
|
}, 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-12">
|
|
<div class="mb-glass rounded-3xl p-12 max-w-md mx-auto">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-professional-accent mx-auto mb-6"></div>
|
|
<h3 class="text-xl font-bold text-professional-primary mb-3">Lade Drucker...</h3>
|
|
<p class="text-professional-muted">Dies sollte nur wenige Sekunden dauern</p>
|
|
</div>
|
|
</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');
|
|
|
|
if (autoRefreshEnabled) {
|
|
if (btn) {
|
|
btn.classList.remove('btn-professional');
|
|
btn.classList.add('btn-success-professional');
|
|
btn.innerHTML = `
|
|
<svg class="w-6 h-6 mr-3 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>
|
|
<span>Auto-Update AN</span>
|
|
`;
|
|
}
|
|
startAutoRefresh();
|
|
showStatusMessage('Auto-Update aktiviert - Drucker werden alle 30 Sekunden aktualisiert', 'success');
|
|
} else {
|
|
if (btn) {
|
|
btn.classList.remove('btn-success-professional');
|
|
btn.classList.add('btn-professional');
|
|
btn.innerHTML = `
|
|
<svg class="w-6 h-6 mr-3 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
`;
|
|
}
|
|
stopAutoRefresh();
|
|
showStatusMessage('Auto-Update deaktiviert', 'info');
|
|
}
|
|
}
|
|
|
|
function startAutoRefresh() {
|
|
stopAutoRefresh(); // Clear any existing interval
|
|
|
|
autoRefreshInterval = setInterval(() => {
|
|
loadPrintersWithLiveStatus();
|
|
}, 30000); // 30 seconds
|
|
|
|
// Start countdown timer
|
|
nextUpdateTime = 30;
|
|
nextUpdateCountdown = setInterval(() => {
|
|
nextUpdateTime--;
|
|
updateNextUpdateDisplay();
|
|
|
|
if (nextUpdateTime <= 0) {
|
|
nextUpdateTime = 30;
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function stopAutoRefresh() {
|
|
if (autoRefreshInterval) {
|
|
clearInterval(autoRefreshInterval);
|
|
autoRefreshInterval = null;
|
|
}
|
|
|
|
if (nextUpdateCountdown) {
|
|
clearInterval(nextUpdateCountdown);
|
|
nextUpdateCountdown = null;
|
|
}
|
|
|
|
// Reset display
|
|
const timeElement = document.getElementById('next-update-time');
|
|
if (timeElement) {
|
|
timeElement.textContent = '-';
|
|
}
|
|
}
|
|
|
|
function updateNextUpdateDisplay() {
|
|
const timeElement = document.getElementById('next-update-time');
|
|
if (timeElement && autoRefreshEnabled) {
|
|
timeElement.textContent = nextUpdateTime;
|
|
}
|
|
}
|
|
|
|
// 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: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
showStatusMessage('Drucker erfolgreich gelöscht', 'success');
|
|
// Drucker aus lokaler Liste entfernen
|
|
printers = printers.filter(p => p.id !== printerId);
|
|
renderPrinters();
|
|
// Status-Übersicht aktualisieren
|
|
const onlineCount = printers.filter(p => p.status === 'available' || p.is_online).length;
|
|
const offlineCount = printers.length - onlineCount;
|
|
updateStatusOverview(onlineCount, offlineCount, printers.length);
|
|
} else {
|
|
throw new Error(data.error || 'Fehler beim Löschen des Druckers');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting printer:', error);
|
|
showStatusMessage('Fehler beim Löschen: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Handle add printer form submission
|
|
async function handleAddPrinter(event) {
|
|
event.preventDefault();
|
|
|
|
const form = event.target;
|
|
const formData = new FormData(form);
|
|
|
|
// Validierung
|
|
const requiredFields = ['name', 'model', 'location', 'mac_address', 'plug_ip', 'plug_username', 'plug_password'];
|
|
for (let field of requiredFields) {
|
|
if (!formData.get(field)) {
|
|
showStatusMessage(`Bitte füllen Sie das Feld "${field}" aus`, 'warning');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// MAC-Adresse Format validieren
|
|
const macAddress = formData.get('mac_address');
|
|
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
|
if (!macRegex.test(macAddress)) {
|
|
showStatusMessage('Bitte geben Sie eine gültige MAC-Adresse ein (z.B. AA:BB:CC:DD:EE:FF)', 'warning');
|
|
return;
|
|
}
|
|
|
|
// IP-Adresse Format validieren
|
|
const ipAddress = formData.get('plug_ip');
|
|
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
if (!ipRegex.test(ipAddress)) {
|
|
showStatusMessage('Bitte geben Sie eine gültige IP-Adresse ein (z.B. 192.168.1.100)', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Submit button deaktivieren
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
const originalBtnContent = submitBtn.innerHTML;
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = `
|
|
<svg class="w-5 h-5 mr-3 animate-spin" 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>
|
|
<span>Wird hinzugefügt...</span>
|
|
`;
|
|
|
|
try {
|
|
const response = await fetch('/api/printers', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
showStatusMessage('Drucker erfolgreich hinzugefügt', 'success');
|
|
hideAddPrinterModal();
|
|
// Drucker neu laden
|
|
await loadPrinters();
|
|
} else {
|
|
throw new Error(data.error || 'Fehler beim Hinzufügen des Druckers');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error adding printer:', error);
|
|
showStatusMessage('Fehler beim Hinzufügen: ' + error.message, 'error');
|
|
} finally {
|
|
// Submit button wieder aktivieren
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = originalBtnContent;
|
|
}
|
|
}
|
|
|
|
// Make functions globally available
|
|
window.refreshPrinters = refreshPrinters;
|
|
window.showPrinterDetail = showPrinterDetail;
|
|
window.deletePrinter = deletePrinter;
|
|
window.toggleAutoRefresh = toggleAutoRefresh;
|
|
</script>
|
|
{% endblock %} |