860 lines
44 KiB
HTML
860 lines
44 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Drucker - MYP Platform{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-7xl mx-auto px-3 sm:px-6 lg:px-8 py-4 sm:py-8">
|
|
<!-- Header -->
|
|
<div class="mb-4 sm:mb-8">
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
|
<div class="mb-4 sm:mb-0">
|
|
<h1 class="text-2xl sm:text-3xl font-bold text-slate-900 dark:text-white">Drucker</h1>
|
|
<p class="mt-1 sm:mt-2 text-sm sm:text-base text-slate-600 dark:text-slate-400">Verwalten Sie Ihre 3D-Drucker</p>
|
|
</div>
|
|
<div class="flex flex-wrap gap-2 sm:space-x-4">
|
|
<button onclick="refreshPrinters()" class="flex-1 sm:flex-none bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-700 text-white px-3 sm:px-4 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center justify-center">
|
|
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
Aktualisieren
|
|
</button>
|
|
{% if current_user.is_admin %}
|
|
<button id="addPrinterBtn" class="flex-1 sm:flex-none bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700 text-white px-3 sm:px-4 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center justify-center">
|
|
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
</svg>
|
|
Drucker hinzufügen
|
|
</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-3 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>
|
|
|
|
<!-- Add Printer Modal -->
|
|
{% if current_user.is_admin %}
|
|
<div id="addPrinterModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl p-4 sm:p-6 w-full max-w-md shadow-lg">
|
|
<div class="flex items-center justify-between mb-4 sm:mb-6">
|
|
<h2 class="text-lg sm:text-xl 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">
|
|
<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-3 sm:space-y-4">
|
|
<div>
|
|
<label for="printer-name" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">Name</label>
|
|
<input type="text" id="printer-name" name="name" required
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printer-model" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">Modell</label>
|
|
<input type="text" id="printer-model" name="model" required
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printer-location" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">Standort</label>
|
|
<input type="text" id="printer-location" name="location" required
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printer-mac" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">MAC-Adresse</label>
|
|
<input type="text" id="printer-mac" name="mac_address" required
|
|
placeholder="AA:BB:CC:DD:EE:FF"
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printer-ip" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">Plug IP-Adresse</label>
|
|
<input type="text" id="printer-ip" name="plug_ip" required
|
|
placeholder="192.168.1.100"
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printer-plug-username" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">Plug Benutzername</label>
|
|
<input type="text" id="printer-plug-username" name="plug_username" required
|
|
placeholder="admin"
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printer-plug-password" class="block text-sm font-medium text-slate-900 dark:text-white mb-1 sm:mb-2">Plug Passwort</label>
|
|
<input type="password" id="printer-plug-password" name="plug_password" required
|
|
placeholder="••••••••"
|
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:text-white">
|
|
</div>
|
|
|
|
<div class="flex space-x-3 pt-3 sm:pt-4">
|
|
<button type="button" id="cancelAddPrinterBtn"
|
|
class="flex-1 bg-slate-200 hover:bg-slate-300 text-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white py-2 px-4 rounded-lg transition-all duration-200 text-sm">
|
|
Abbrechen
|
|
</button>
|
|
<button type="submit"
|
|
class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg transition-all duration-200 text-sm">
|
|
Hinzufügen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Printer Detail Modal -->
|
|
<div id="printerDetailModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl p-4 sm:p-6 w-full max-w-lg shadow-lg">
|
|
<div class="flex items-center justify-between mb-4 sm:mb-6">
|
|
<h2 class="text-lg sm:text-xl 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">
|
|
<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>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
// Globale Variablen für die Drucker-Verwaltung
|
|
let printers = [];
|
|
|
|
// 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 Status-Check
|
|
loadPrintersWithStatusCheck().finally(() => {
|
|
// Button wieder aktivieren
|
|
if (refreshBtn) {
|
|
refreshBtn.disabled = false;
|
|
refreshBtn.innerHTML = `
|
|
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
Aktualisieren
|
|
`;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Make function globally available
|
|
window.refreshPrinters = refreshPrinters;
|
|
|
|
// Modal-Funktionen
|
|
function showAddPrinterModal() {
|
|
document.getElementById('addPrinterModal').classList.remove('hidden');
|
|
}
|
|
|
|
function hideAddPrinterModal() {
|
|
document.getElementById('addPrinterModal').classList.add('hidden');
|
|
document.getElementById('addPrinterForm').reset();
|
|
}
|
|
|
|
// Make modal functions globally available
|
|
window.showAddPrinterModal = showAddPrinterModal;
|
|
window.hideAddPrinterModal = hideAddPrinterModal;
|
|
|
|
function showPrinterDetail(printerId) {
|
|
const printer = printers.find(p => p.id === printerId);
|
|
if (!printer) return;
|
|
|
|
const content = document.getElementById('printer-detail-content');
|
|
content.innerHTML = `
|
|
<div class="space-y-3 sm:space-y-4">
|
|
<div>
|
|
<h3 class="font-medium text-slate-900 dark:text-white text-base sm:text-lg">${printer.name}</h3>
|
|
<p class="text-xs sm:text-sm text-slate-600 dark:text-slate-400">${printer.model}</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4 text-xs sm:text-sm">
|
|
<div>
|
|
<span class="font-medium text-slate-700 dark:text-slate-300">Status:</span>
|
|
<span class="ml-1 sm:ml-2 ${getPrinterStatusColor(printer.status)} px-1.5 sm:px-2 py-0.5 sm:py-1 rounded text-xs">
|
|
${getPrinterStatusText(printer.status)}
|
|
</span>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="font-medium text-slate-700 dark:text-slate-300">Standort:</span>
|
|
<span class="ml-1 sm:ml-2 text-slate-900 dark:text-white">${printer.location}</span>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="font-medium text-slate-700 dark:text-slate-300">MAC:</span>
|
|
<span class="ml-1 sm:ml-2 text-slate-900 dark:text-white font-mono text-xs">${printer.mac_address}</span>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="font-medium text-slate-700 dark:text-slate-300">Plug IP:</span>
|
|
<span class="ml-1 sm:ml-2 text-slate-900 dark:text-white font-mono text-xs">${printer.plug_ip}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-3 sm:pt-4 border-t border-slate-200 dark:border-slate-700">
|
|
<p class="text-xs text-slate-600 dark:text-slate-400">
|
|
Erstellt: ${formatDate(printer.created_at)}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex space-x-3 pt-3 sm:pt-4">
|
|
<button onclick="hidePrinterDetailModal()"
|
|
class="flex-1 bg-slate-200 hover:bg-slate-300 text-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white py-2 px-4 rounded-lg transition-all duration-200 text-sm">
|
|
Schließen
|
|
</button>
|
|
|
|
{% if current_user.is_admin %}
|
|
<button onclick="deletePrinter(${printer.id}); hidePrinterDetailModal();"
|
|
class="flex-1 bg-red-600 hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-500 text-white py-2 px-4 rounded-lg transition-all duration-200 text-sm flex items-center justify-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="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>
|
|
`;
|
|
|
|
document.getElementById('printerDetailModal').classList.remove('hidden');
|
|
}
|
|
|
|
function hidePrinterDetailModal() {
|
|
document.getElementById('printerDetailModal').classList.add('hidden');
|
|
}
|
|
|
|
// Load printers (schnelles Laden ohne Status-Check)
|
|
async function loadPrinters() {
|
|
const grid = document.getElementById('printers-grid');
|
|
|
|
// 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');
|
|
}
|
|
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
// Render printers grid
|
|
function renderPrinters() {
|
|
const grid = document.getElementById('printers-grid');
|
|
|
|
if (printers.length === 0) {
|
|
grid.innerHTML = `
|
|
<div class="col-span-full text-center py-6 sm:py-12">
|
|
<svg class="h-12 w-12 sm:h-16 sm:w-16 text-slate-400 dark:text-slate-500 mx-auto mb-3 sm:mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
<p class="text-slate-700 dark:text-slate-300 text-base sm:text-lg">Keine Drucker vorhanden</p>
|
|
{% if current_user.is_admin %}
|
|
<button id="addFirstPrinterBtn" class="mt-3 sm:mt-4 bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-500 text-white px-4 sm:px-6 py-1.5 sm:py-2 rounded-lg transition-all duration-200 text-sm sm:text-base">
|
|
Ersten Drucker hinzufügen
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
`;
|
|
|
|
// Event-Listener für den "Ersten Drucker hinzufügen" Button
|
|
const addFirstPrinterBtn = document.getElementById('addFirstPrinterBtn');
|
|
if (addFirstPrinterBtn) {
|
|
addFirstPrinterBtn.addEventListener('click', showAddPrinterModal);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = printers.map(printer => {
|
|
const statusColor = getPrinterStatusColor(printer.status);
|
|
const statusText = getPrinterStatusText(printer.status);
|
|
|
|
return `
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl p-4 sm:p-6 shadow-sm hover:shadow-lg transition-shadow duration-200 border border-slate-200 dark:border-slate-700">
|
|
<div class="flex items-start justify-between mb-3 sm:mb-4">
|
|
<div class="flex-1">
|
|
<h3 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white">${printer.name}</h3>
|
|
<p class="text-xs sm:text-sm text-slate-600 dark:text-slate-400">${printer.model}</p>
|
|
</div>
|
|
<div class="flex flex-col items-end">
|
|
<span class="inline-flex items-center px-2 py-0.5 sm:px-2.5 sm:py-0.5 rounded-full text-xs font-medium ${statusColor}">
|
|
${statusText}
|
|
</span>
|
|
${printer.last_checked ? `<span class="text-xs text-slate-500 dark:text-slate-400 mt-1">Geprüft: ${formatTime(printer.last_checked)}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-1.5 sm:space-y-2 mb-3 sm:mb-4">
|
|
<div class="flex items-center text-xs sm:text-sm text-slate-600 dark:text-slate-400">
|
|
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 text-slate-500 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
${printer.location}
|
|
</div>
|
|
|
|
<div class="flex items-center text-xs sm:text-sm text-slate-600 dark:text-slate-400">
|
|
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 text-slate-500 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
${printer.mac_address}
|
|
</div>
|
|
|
|
<div class="flex items-center text-xs sm:text-sm text-slate-600 dark:text-slate-400">
|
|
<svg class="h-3.5 w-3.5 sm:h-4 sm:w-4 mr-1.5 sm:mr-2 text-slate-500 dark:text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9v-9m0-9v9" />
|
|
</svg>
|
|
${printer.plug_ip}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<button class="printer-detail-btn flex-1 bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white py-1.5 sm:py-2 px-2 sm:px-3 rounded-lg text-xs sm:text-sm transition-all duration-200" data-printer-id="${printer.id}">
|
|
Details
|
|
</button>
|
|
|
|
{% if current_user.is_admin %}
|
|
<button class="delete-printer-btn flex-1 bg-red-600 hover:bg-red-700 dark:bg-red-600 dark:hover:bg-red-500 text-white py-1.5 sm:py-2 px-2 sm:px-3 rounded-lg text-xs sm:text-sm transition-all duration-200" data-printer-id="${printer.id}">
|
|
<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('');
|
|
|
|
// Event-Listener für Details und Löschen-Buttons hinzufügen
|
|
document.querySelectorAll('.printer-detail-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const printerId = parseInt(this.getAttribute('data-printer-id'));
|
|
showPrinterDetail(printerId);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.delete-printer-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const printerId = parseInt(this.getAttribute('data-printer-id'));
|
|
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 'Verfügbar';
|
|
case 'busy': return 'Beschäftigt';
|
|
case 'offline': return 'Offline';
|
|
case 'maintenance': return 'Wartung';
|
|
default: return 'Unbekannt';
|
|
}
|
|
}
|
|
|
|
// Format date helper
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString('de-DE');
|
|
}
|
|
|
|
// Format time helper für Status-Zeitstempel
|
|
function formatTime(dateString) {
|
|
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' });
|
|
}
|
|
}
|
|
|
|
// Show error message
|
|
function showError(message) {
|
|
const grid = document.getElementById('printers-grid');
|
|
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') {
|
|
// 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);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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');
|
|
}
|
|
}
|
|
|
|
// Erweiterte Funktion zum Laden der Drucker mit Status-Check
|
|
async function loadPrintersWithStatusCheck() {
|
|
try {
|
|
// Erstelle einen AbortController für Timeout
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 Sekunden Timeout für Status-Check
|
|
|
|
const response = await fetch('/api/printers/status', {
|
|
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 Status-Check der Drucker. Versuchen Sie es später erneut.');
|
|
}
|
|
throw new Error(`Fehler beim Laden der Drucker-Status: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const statusData = await response.json();
|
|
|
|
// Prüfe ob statusData ein Array ist
|
|
if (!Array.isArray(statusData)) {
|
|
console.error('Invalid response from /api/printers/status:', statusData);
|
|
throw new Error('Ungültige Antwort vom Server (erwartet Array, erhalten: ' + typeof statusData + ')');
|
|
}
|
|
|
|
// Drucker-Daten mit Status-Informationen anreichern
|
|
printers = statusData.map(printer => ({
|
|
...printer,
|
|
// Status ist bereits korrekt gemappt vom Backend
|
|
status: printer.status || 'offline',
|
|
last_checked: printer.last_checked || new Date().toISOString()
|
|
}));
|
|
|
|
renderPrinters();
|
|
|
|
// Erfolgs-Nachricht anzeigen
|
|
const onlineCount = printers.filter(p => p.status === 'available').length;
|
|
const totalCount = printers.length;
|
|
|
|
if (totalCount > 0) {
|
|
showStatusMessage(
|
|
`Status-Check abgeschlossen: ${onlineCount} von ${totalCount} Drucker verfügbar`,
|
|
onlineCount > 0 ? 'success' : 'warning'
|
|
);
|
|
} else {
|
|
showStatusMessage('Keine Drucker gefunden', 'info');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error loading printer status:', error);
|
|
showStatusMessage('Fehler beim Überprüfen der Drucker-Status: ' + error.message, 'error');
|
|
// Fallback: Lade normale Drucker-Liste
|
|
loadPrinters();
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Lade Drucker beim Start
|
|
loadPrinters();
|
|
|
|
// Event-Listener für den "Drucker hinzufügen" Button
|
|
const addPrinterBtn = document.getElementById('addPrinterBtn');
|
|
if (addPrinterBtn) {
|
|
addPrinterBtn.addEventListener('click', showAddPrinterModal);
|
|
}
|
|
|
|
// Event-Listener für das Schließen des Modals
|
|
const closeAddPrinterBtn = document.getElementById('closeAddPrinterBtn');
|
|
if (closeAddPrinterBtn) {
|
|
closeAddPrinterBtn.addEventListener('click', hideAddPrinterModal);
|
|
}
|
|
|
|
const cancelAddPrinterBtn = document.getElementById('cancelAddPrinterBtn');
|
|
if (cancelAddPrinterBtn) {
|
|
cancelAddPrinterBtn.addEventListener('click', hideAddPrinterModal);
|
|
}
|
|
|
|
// Event-Listener für das Schließen des Detail-Modals
|
|
const closePrinterDetailBtn = document.getElementById('closePrinterDetailBtn');
|
|
if (closePrinterDetailBtn) {
|
|
closePrinterDetailBtn.addEventListener('click', hidePrinterDetailModal);
|
|
}
|
|
|
|
// Event-Listener für das Formular
|
|
const addPrinterForm = document.getElementById('addPrinterForm');
|
|
if (addPrinterForm) {
|
|
addPrinterForm.addEventListener('submit', handleAddPrinter);
|
|
}
|
|
|
|
// Modals schließen, wenn außerhalb geklickt wird
|
|
document.getElementById('addPrinterModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
hideAddPrinterModal();
|
|
}
|
|
});
|
|
|
|
document.getElementById('printerDetailModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
hidePrinterDetailModal();
|
|
}
|
|
});
|
|
|
|
// Modals schließen mit Escape-Taste
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
hideAddPrinterModal();
|
|
hidePrinterDetailModal();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Make all functions globally available for onclick handlers
|
|
window.showPrinterDetail = showPrinterDetail;
|
|
window.hidePrinterDetailModal = hidePrinterDetailModal;
|
|
window.deletePrinter = deletePrinter;
|
|
window.loadPrinters = loadPrinters;
|
|
window.handleAddPrinter = handleAddPrinter;
|
|
|
|
// Debug: Log that functions are available
|
|
console.log('All printer functions loaded and available globally:', {
|
|
refreshPrinters: typeof window.refreshPrinters,
|
|
showAddPrinterModal: typeof window.showAddPrinterModal,
|
|
hideAddPrinterModal: typeof window.hideAddPrinterModal,
|
|
showPrinterDetail: typeof window.showPrinterDetail,
|
|
hidePrinterDetailModal: typeof window.hidePrinterDetailModal,
|
|
deletePrinter: typeof window.deletePrinter,
|
|
loadPrinters: typeof window.loadPrinters
|
|
});
|
|
</script>
|
|
{% endblock %} |