Files
Projektarbeit-MYP/backend/app/templates/printers.html

562 lines
26 KiB
HTML

{% extends "base.html" %}
{% block title %}3D-Drucker - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
{{ super() }}
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endblock %}
{% block content %}
<div class="space-y-8">
<!-- Page Header -->
<div class="dashboard-card p-6">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div class="flex items-center gap-4">
<div class="w-12 h-12 flex-shrink-0">
<svg class="w-full h-full text-slate-900 dark:text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white tracking-tight">3D-Drucker</h1>
<p class="text-slate-500 dark:text-slate-400 mt-1">Verwaltung und Überwachung Ihrer Produktionseinheiten</p>
</div>
</div>
<div class="flex flex-wrap gap-3">
<button onclick="openAddPrinterModal()"
class="btn-primary flex items-center gap-2">
<svg class="w-5 h-5" 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>
<button onclick="refreshPrinters()"
class="btn-secondary flex items-center gap-2">
<svg class="w-5 h-5" 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>Aktualisieren</span>
</button>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div class="dashboard-card p-6">
<div class="flex justify-between">
<div>
<h3 class="stat-label">Online</h3>
<div class="stat-value text-green-600 dark:text-green-400" id="online-count">0</div>
</div>
<div class="mb-stat-icon text-green-600 dark:text-green-400">
<div class="w-3 h-3 bg-green-500 rounded-full"></div>
</div>
</div>
</div>
<div class="dashboard-card p-6">
<div class="flex justify-between">
<div>
<h3 class="stat-label">Offline</h3>
<div class="stat-value text-red-600 dark:text-red-400" id="offline-count">0</div>
</div>
<div class="mb-stat-icon text-red-600 dark:text-red-400">
<div class="w-3 h-3 bg-red-500 rounded-full"></div>
</div>
</div>
</div>
<div class="dashboard-card p-6">
<div class="flex justify-between">
<div>
<h3 class="stat-label">Aktiv</h3>
<div class="stat-value text-blue-600 dark:text-blue-400" id="printing-count">0</div>
</div>
<div class="mb-stat-icon text-blue-600 dark:text-blue-400">
<div class="w-3 h-3 bg-blue-500 rounded-full"></div>
</div>
</div>
</div>
<div class="dashboard-card p-6">
<div class="flex justify-between">
<div>
<h3 class="stat-label">Gesamt</h3>
<div class="stat-value" id="total-count">0</div>
</div>
<div class="mb-stat-icon text-slate-600 dark:text-slate-400">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
</svg>
</div>
</div>
</div>
</div>
<!-- Filter Section -->
<div class="dashboard-card p-6">
<div class="flex flex-col sm:flex-row sm:items-end gap-4">
<div class="flex-1">
<label for="filterStatus" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Status-Filter
</label>
<select id="filterStatus" class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">Alle Status</option>
<option value="online">Online</option>
<option value="offline">Offline</option>
<option value="printing">Druckt</option>
<option value="error">Fehler</option>
</select>
</div>
<div class="flex-1">
<label for="filterLocation" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Standort-Filter
</label>
<select id="filterLocation" class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">Alle Standorte</option>
</select>
</div>
</div>
</div>
<!-- Printers Grid -->
<div id="printers-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Printers will be loaded here -->
</div>
<!-- Loading State -->
<div id="loading-state" class="dashboard-card p-8 text-center">
<div class="text-slate-400 dark:text-slate-500 mb-4">
<svg class="w-12 h-12 mx-auto mb-3 animate-spin" 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>
</div>
<p class="text-slate-600 dark:text-slate-400">Lade Drucker...</p>
</div>
<!-- Empty State -->
<div id="empty-state" class="dashboard-card p-8 text-center hidden">
<div class="text-slate-400 dark:text-slate-500 mb-4">
<svg class="w-12 h-12 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
</svg>
</div>
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-2">Keine Drucker gefunden</h3>
<p class="text-slate-500 dark:text-slate-400 mb-4">
Fügen Sie Ihren ersten 3D-Drucker hinzu.
</p>
<button onclick="openAddPrinterModal()" class="btn-primary">
Ersten Drucker hinzufügen
</button>
</div>
</div>
<!-- Add/Edit Printer Modal -->
<div id="printerModal" 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="dashboard-card max-w-lg w-full p-8 transform transition-all duration-300 scale-95 opacity-0" id="printerModalContent">
<div class="flex justify-between items-center mb-6">
<div>
<h3 id="printerModalTitle" class="text-xl font-bold text-slate-900 dark:text-white mb-2">
Drucker hinzufügen
</h3>
<p class="text-slate-500 dark:text-slate-400">Fügen Sie einen neuen 3D-Drucker hinzu</p>
</div>
<button onclick="closePrinterModal()" class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors">
<svg class="w-6 h-6 text-slate-500 dark:text-slate-400" 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="printerForm" class="space-y-6">
<input type="hidden" id="printerId" name="printerId">
<div>
<label for="printerName" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Drucker-Name <span class="text-red-500">*</span>
</label>
<input type="text" id="printerName" name="name" required
class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="z.B. Ultimaker S3">
</div>
<div>
<label for="printerModel" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Modell
</label>
<input type="text" id="printerModel" name="model"
class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="z.B. Ultimaker S3">
</div>
<div>
<label for="printerLocation" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Standort
</label>
<input type="text" id="printerLocation" name="location"
class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="z.B. Produktionshalle A">
</div>
<div>
<label for="printerIP" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
IP-Adresse
</label>
<input type="text" id="printerIP" name="ip_address"
class="block w-full px-3 py-2 border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="z.B. 192.168.1.100">
</div>
<div class="flex items-center">
<input type="checkbox" id="printerActive" name="active" checked
class="w-4 h-4 text-blue-600 bg-white dark:bg-slate-800 border-gray-300 dark:border-slate-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="printerActive" class="ml-2 text-sm text-slate-700 dark:text-slate-300">
Drucker aktiv
</label>
</div>
<div class="flex items-center justify-end gap-3 pt-6 border-t border-gray-200 dark:border-slate-600">
<button type="button" onclick="closePrinterModal()" class="btn-secondary">
Abbrechen
</button>
<button type="submit" class="btn-primary">
Speichern
</button>
<button type="button" id="deletePrinterBtn" onclick="deletePrinter()" style="display: none;"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors">
Löschen
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Printer Details Modal -->
<div id="printerDetailsModal" 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="dashboard-card max-w-2xl w-full p-8 transform transition-all duration-300 scale-95 opacity-0" id="printerDetailsModalContent">
<div class="flex justify-between items-center mb-6">
<div>
<h3 id="printerDetailsTitle" class="text-xl font-bold text-slate-900 dark:text-white mb-2">
Drucker Details
</h3>
<p class="text-slate-500 dark:text-slate-400">Detaillierte Informationen über den Drucker</p>
</div>
<button onclick="closePrinterDetailsModal()" class="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg transition-colors">
<svg class="w-6 h-6 text-slate-500 dark:text-slate-400" 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="printerDetailsContent">
<!-- Details will be loaded here -->
</div>
</div>
</div>
</div>
<script>
let allPrinters = [];
let filteredPrinters = [];
document.addEventListener('DOMContentLoaded', function() {
loadPrinters();
setupFilters();
});
// Load printers from API
async function loadPrinters() {
try {
const response = await fetch('/api/printers');
const data = await response.json();
if (data.success) {
allPrinters = data.printers;
filteredPrinters = [...allPrinters];
displayPrinters();
updateStatistics();
populateLocationFilter();
} else {
showError('Fehler beim Laden der Drucker: ' + data.error);
}
} catch (error) {
console.error('Error loading printers:', error);
showError('Fehler beim Laden der Drucker');
} finally {
document.getElementById('loading-state').style.display = 'none';
}
}
// Display printers in grid
function displayPrinters() {
const grid = document.getElementById('printers-grid');
const emptyState = document.getElementById('empty-state');
if (filteredPrinters.length === 0) {
grid.style.display = 'none';
emptyState.classList.remove('hidden');
return;
}
grid.style.display = 'grid';
emptyState.classList.add('hidden');
grid.innerHTML = filteredPrinters.map(printer => createPrinterCard(printer)).join('');
}
// Create printer card HTML
function createPrinterCard(printer) {
const statusClass = getStatusClass(printer.status);
const statusIcon = getStatusIcon(printer.status);
return `
<div class="dashboard-card p-6 border-l-4 ${statusClass.border}">
<div class="flex justify-between items-start mb-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 ${statusClass.bg} rounded-lg flex items-center justify-center">
${statusIcon}
</div>
<div>
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">${printer.name}</h3>
<p class="text-sm text-slate-500 dark:text-slate-400">${printer.model || 'Unbekanntes Modell'}</p>
</div>
</div>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium ${statusClass.badge}">
${printer.status_text || printer.status}
</span>
</div>
<div class="space-y-3 mb-4">
<div class="flex justify-between text-sm">
<span class="text-slate-500 dark:text-slate-400">Standort:</span>
<span class="text-slate-900 dark:text-white">${printer.location || 'Nicht angegeben'}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-500 dark:text-slate-400">IP-Adresse:</span>
<span class="text-slate-900 dark:text-white">${printer.ip_address || 'Nicht konfiguriert'}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-500 dark:text-slate-400">Letzter Job:</span>
<span class="text-slate-900 dark:text-white">${printer.last_job || 'Kein Job'}</span>
</div>
</div>
<div class="flex gap-2">
<button onclick="showPrinterDetails(${printer.id})"
class="flex-1 px-3 py-2 text-sm bg-gray-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-gray-200 dark:hover:bg-slate-600 transition-colors">
Details
</button>
<button onclick="editPrinter(${printer.id})"
class="flex-1 px-3 py-2 text-sm bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 rounded-lg hover:bg-blue-200 dark:hover:bg-blue-900/50 transition-colors">
Bearbeiten
</button>
</div>
</div>
`;
}
// Get status styling classes
function getStatusClass(status) {
const classes = {
'online': {
border: 'border-green-400',
bg: 'bg-green-100 dark:bg-green-900/30',
badge: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'
},
'offline': {
border: 'border-red-400',
bg: 'bg-red-100 dark:bg-red-900/30',
badge: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'
},
'printing': {
border: 'border-blue-400',
bg: 'bg-blue-100 dark:bg-blue-900/30',
badge: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400'
},
'error': {
border: 'border-orange-400',
bg: 'bg-orange-100 dark:bg-orange-900/30',
badge: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400'
}
};
return classes[status] || classes['offline'];
}
// Get status icon
function getStatusIcon(status) {
const icons = {
'online': '<div class="w-3 h-3 bg-green-500 rounded-full"></div>',
'offline': '<div class="w-3 h-3 bg-red-500 rounded-full"></div>',
'printing': '<div class="w-3 h-3 bg-blue-500 rounded-full animate-pulse"></div>',
'error': '<div class="w-3 h-3 bg-orange-500 rounded-full"></div>'
};
return icons[status] || icons['offline'];
}
// Update statistics
function updateStatistics() {
const stats = {
total: filteredPrinters.length,
online: filteredPrinters.filter(p => p.status === 'online').length,
offline: filteredPrinters.filter(p => p.status === 'offline').length,
printing: filteredPrinters.filter(p => p.status === 'printing').length
};
document.getElementById('total-count').textContent = stats.total;
document.getElementById('online-count').textContent = stats.online;
document.getElementById('offline-count').textContent = stats.offline;
document.getElementById('printing-count').textContent = stats.printing;
}
// Setup filter functionality
function setupFilters() {
const statusFilter = document.getElementById('filterStatus');
const locationFilter = document.getElementById('filterLocation');
statusFilter.addEventListener('change', applyFilters);
locationFilter.addEventListener('change', applyFilters);
}
// Apply filters
function applyFilters() {
const statusFilter = document.getElementById('filterStatus').value;
const locationFilter = document.getElementById('filterLocation').value;
filteredPrinters = allPrinters.filter(printer => {
const statusMatch = !statusFilter || printer.status === statusFilter;
const locationMatch = !locationFilter || printer.location === locationFilter;
return statusMatch && locationMatch;
});
displayPrinters();
updateStatistics();
}
// Populate location filter
function populateLocationFilter() {
const locationFilter = document.getElementById('filterLocation');
const locations = [...new Set(allPrinters.map(p => p.location).filter(Boolean))];
locationFilter.innerHTML = '<option value="">Alle Standorte</option>' +
locations.map(location => `<option value="${location}">${location}</option>`).join('');
}
// Modal functions
function openAddPrinterModal() {
const modal = document.getElementById('printerModal');
const modalContent = document.getElementById('printerModalContent');
document.getElementById('printerModalTitle').textContent = 'Drucker hinzufügen';
document.getElementById('printerForm').reset();
document.getElementById('printerId').value = '';
document.getElementById('deletePrinterBtn').style.display = 'none';
modal.classList.remove('hidden');
setTimeout(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
}, 10);
}
function closePrinterModal() {
const modal = document.getElementById('printerModal');
const modalContent = document.getElementById('printerModalContent');
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
}, 200);
}
function editPrinter(printerId) {
const printer = allPrinters.find(p => p.id === printerId);
if (!printer) return;
document.getElementById('printerModalTitle').textContent = 'Drucker bearbeiten';
document.getElementById('printerId').value = printer.id;
document.getElementById('printerName').value = printer.name;
document.getElementById('printerModel').value = printer.model || '';
document.getElementById('printerLocation').value = printer.location || '';
document.getElementById('printerIP').value = printer.ip_address || '';
document.getElementById('printerActive').checked = printer.active;
document.getElementById('deletePrinterBtn').style.display = 'block';
openAddPrinterModal();
}
function showPrinterDetails(printerId) {
// Implementation for showing printer details
console.log('Show details for printer:', printerId);
}
function closePrinterDetailsModal() {
const modal = document.getElementById('printerDetailsModal');
const modalContent = document.getElementById('printerDetailsModalContent');
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
}, 200);
}
function deletePrinter() {
const printerId = document.getElementById('printerId').value;
if (printerId && confirm('Möchten Sie diesen Drucker wirklich löschen?')) {
// Implementation for deleting printer
console.log('Delete printer:', printerId);
closePrinterModal();
}
}
function refreshPrinters() {
document.getElementById('loading-state').style.display = 'block';
document.getElementById('printers-grid').style.display = 'none';
document.getElementById('empty-state').classList.add('hidden');
loadPrinters();
}
function showError(message) {
console.error(message);
// You could implement a toast notification here
}
// Form submission
document.getElementById('printerForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
data.active = document.getElementById('printerActive').checked;
try {
const url = data.printerId ? `/api/printers/${data.printerId}` : '/api/printers';
const method = data.printerId ? 'PUT' : 'POST';
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
closePrinterModal();
refreshPrinters();
} else {
showError('Fehler beim Speichern: ' + result.error);
}
} catch (error) {
console.error('Error saving printer:', error);
showError('Fehler beim Speichern des Druckers');
}
});
</script>
{% endblock %}