📝 feat(database): Remove unneeded database files and update templates & tests 📆

This commit is contained in:
Till Tomczak 2025-05-29 17:10:52 +02:00
parent 2177975513
commit d00fc592cc
5 changed files with 629 additions and 269 deletions

Binary file not shown.

Binary file not shown.

View File

@ -372,7 +372,7 @@
</div>
<div class="text-sm">
<a href="{{ url_for('reset_password_request') if url_for and url_for('reset_password_request') else '#' }}"
<a href="#"
class="text-mercedes-blue hover:text-blue-700 transition-colors">
Passwort vergessen?
</a>

View File

@ -1221,303 +1221,663 @@
<script>
let allPrinters = [];
let filteredPrinters = [];
let isAutoRefreshEnabled = true;
let autoRefreshInterval;
let currentGridView = 'grid';
let isMaintenanceMode = false;
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';
// Enhanced Printer Management System
class PrinterManager {
constructor() {
this.init();
this.setupEventListeners();
this.startAutoRefresh();
}
}
// Display printers in grid
function displayPrinters() {
const grid = document.getElementById('printers-grid');
const emptyState = document.getElementById('empty-state');
async init() {
await this.loadPrinters();
this.setupFilters();
this.initializePerformanceMonitoring();
}
setupEventListeners() {
// Filter Event Listeners
document.getElementById('search-input').addEventListener('input', this.debounce(this.applyFilters.bind(this), 300));
document.getElementById('filterStatus').addEventListener('change', this.applyFilters.bind(this));
document.getElementById('filterLocation').addEventListener('change', this.applyFilters.bind(this));
document.getElementById('filterModel').addEventListener('change', this.applyFilters.bind(this));
document.getElementById('sort-by').addEventListener('change', this.sortAndDisplayPrinters.bind(this));
// Advanced Filter Checkboxes
document.getElementById('show-offline').addEventListener('change', this.applyFilters.bind(this));
document.getElementById('show-maintenance').addEventListener('change', this.applyFilters.bind(this));
document.getElementById('only-available').addEventListener('change', this.applyFilters.bind(this));
// Modal Event Listeners
document.getElementById('printerModel').addEventListener('change', this.handleModelChange.bind(this));
document.getElementById('printerForm').addEventListener('submit', this.handleFormSubmit.bind(this));
// Keyboard Shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeAllModals();
}
if (e.ctrlKey && e.key === 'r') {
e.preventDefault();
this.refreshPrinters();
}
});
}
async loadPrinters() {
try {
this.showLoadingState();
const response = await fetch('/api/printers');
const data = await response.json();
if (data.success) {
allPrinters = data.printers || [];
this.applyFilters();
this.updateStatistics();
this.populateFilterDropdowns();
this.updateLastUpdateTime();
} else {
this.showError('Fehler beim Laden der Drucker: ' + data.message);
}
} catch (error) {
console.error('Error loading printers:', error);
this.showError('Fehler beim Laden der Drucker');
} finally {
this.hideLoadingState();
}
}
showLoadingState() {
document.getElementById('loading-state').classList.remove('hidden');
document.getElementById('printers-container').classList.add('hidden');
}
hideLoadingState() {
document.getElementById('loading-state').classList.add('hidden');
document.getElementById('printers-container').classList.remove('hidden');
}
applyFilters() {
const searchTerm = document.getElementById('search-input').value.toLowerCase();
const statusFilter = document.getElementById('filterStatus').value;
const locationFilter = document.getElementById('filterLocation').value;
const modelFilter = document.getElementById('filterModel').value;
const showOffline = document.getElementById('show-offline').checked;
const showMaintenance = document.getElementById('show-maintenance').checked;
const onlyAvailable = document.getElementById('only-available').checked;
filteredPrinters = allPrinters.filter(printer => {
// Text search
const matchesSearch = !searchTerm ||
printer.name.toLowerCase().includes(searchTerm) ||
(printer.model && printer.model.toLowerCase().includes(searchTerm)) ||
(printer.ip_address && printer.ip_address.includes(searchTerm)) ||
(printer.location && printer.location.toLowerCase().includes(searchTerm));
// Status filter
const matchesStatus = !statusFilter || printer.status === statusFilter;
const matchesLocation = !locationFilter || printer.location === locationFilter;
const matchesModel = !modelFilter || printer.model === modelFilter;
// Advanced filters
if (!showOffline && printer.status === 'offline') return false;
if (!showMaintenance && printer.status === 'maintenance') return false;
if (onlyAvailable && !this.isPrinterAvailable(printer)) return false;
return matchesSearch && matchesStatus && matchesLocation && matchesModel;
});
this.updateFilterCounts();
this.sortAndDisplayPrinters();
}
isPrinterAvailable(printer) {
return ['online', 'idle'].includes(printer.status);
}
sortAndDisplayPrinters() {
const sortBy = document.getElementById('sort-by').value;
filteredPrinters.sort((a, b) => {
switch (sortBy) {
case 'name':
return a.name.localeCompare(b.name);
case 'status':
return a.status.localeCompare(b.status);
case 'location':
return (a.location || '').localeCompare(b.location || '');
case 'model':
return (a.model || '').localeCompare(b.model || '');
case 'last_activity':
return new Date(b.last_activity || 0) - new Date(a.last_activity || 0);
default:
return 0;
}
});
this.displayPrinters();
}
displayPrinters() {
const grid = document.getElementById('printers-grid');
const emptyState = document.getElementById('empty-state');
const noResultsState = document.getElementById('no-results-state');
if (allPrinters.length === 0) {
grid.innerHTML = '';
emptyState.classList.remove('hidden');
noResultsState.classList.add('hidden');
return;
}
if (filteredPrinters.length === 0) {
grid.style.display = 'none';
emptyState.classList.remove('hidden');
return;
grid.innerHTML = '';
emptyState.classList.add('hidden');
noResultsState.classList.remove('hidden');
return;
}
emptyState.classList.add('hidden');
noResultsState.classList.add('hidden');
if (currentGridView === 'list') {
grid.className = 'space-y-4';
grid.innerHTML = filteredPrinters.map(printer => this.createPrinterListItem(printer)).join('');
} else {
grid.className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6';
grid.innerHTML = filteredPrinters.map(printer => this.createPrinterCard(printer)).join('');
}
}
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>
createPrinterCard(printer) {
const statusClasses = this.getStatusClasses(printer.status);
const temperature = printer.temperature || {};
const currentJob = printer.current_job || {};
return `
<div class="printer-card ${statusClasses.container} p-6" data-printer-id="${printer.id}">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-12 h-12 ${statusClasses.iconBg} rounded-xl flex items-center justify-center">
${this.getStatusIcon(printer.status)}
</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>
<h3 class="text-lg font-semibold text-mercedes-black dark:text-white">${printer.name}</h3>
<p class="text-sm text-mercedes-gray dark:text-slate-400">${printer.model || 'Unbekanntes Modell'}</p>
</div>
</div>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium ${statusClasses.badge}">
${this.getStatusText(printer.status)}
</span>
</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>
${printer.status === 'printing' && currentJob.progress ? `
<div class="mb-4">
<div class="flex justify-between text-sm text-mercedes-gray dark:text-slate-400 mb-2">
<span>Fortschritt: ${currentJob.name || 'Unbekannter Job'}</span>
<span>${currentJob.progress}%</span>
</div>
<div class="print-progress">
<div class="print-progress-bar" style="width: ${currentJob.progress}%"></div>
</div>
${currentJob.time_remaining ? `
<div class="text-xs text-mercedes-gray dark:text-slate-400 mt-1">
Verbleibend: ${this.formatDuration(currentJob.time_remaining)}
</div>
` : ''}
</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 class="grid grid-cols-2 gap-4 mb-4 text-sm">
<div>
<span class="text-mercedes-gray dark:text-slate-400">Standort:</span>
<p class="text-mercedes-black dark:text-white truncate">${printer.location || 'Nicht angegeben'}</p>
</div>
<div>
<span class="text-mercedes-gray dark:text-slate-400">IP:</span>
<p class="text-mercedes-black dark:text-white">${printer.ip_address || 'N/A'}</p>
</div>
${Object.keys(temperature).length > 0 ? `
<div>
<span class="text-mercedes-gray dark:text-slate-400">Düse:</span>
<p class="text-mercedes-black dark:text-white">${temperature.nozzle || 0}°C</p>
</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>
<span class="text-mercedes-gray dark:text-slate-400">Bett:</span>
<p class="text-mercedes-black dark:text-white">${temperature.bed || 0}°C</p>
</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>
<span class="text-mercedes-gray dark:text-slate-400">Letzter Job:</span>
<p class="text-mercedes-black dark:text-white truncate">${printer.last_job || 'Kein Job'}</p>
</div>
<div>
<span class="text-mercedes-gray dark:text-slate-400">Uptime:</span>
<p class="text-mercedes-black dark:text-white">${this.formatUptime(printer.uptime)}</p>
</div>
`}
</div>
<div class="flex gap-2">
<button onclick="printerManager.openPrinterDetails('${printer.id}')"
class="printer-action-btn btn-details flex-1">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
Details
</button>
<button onclick="printerManager.editPrinter('${printer.id}')"
class="printer-action-btn btn-edit flex-1">
<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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
Bearbeiten
</button>
${this.getPrinterActionButton(printer)}
</div>
</div>
`;
}
createPrinterListItem(printer) {
const statusClasses = this.getStatusClasses(printer.status);
return `
<div class="printer-card ${statusClasses.container} p-4" data-printer-id="${printer.id}">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="w-10 h-10 ${statusClasses.iconBg} rounded-lg flex items-center justify-center">
${this.getStatusIcon(printer.status)}
</div>
<div class="flex-1">
<h3 class="text-lg font-semibold text-mercedes-black dark:text-white">${printer.name}</h3>
<div class="flex items-center gap-4 text-sm text-mercedes-gray dark:text-slate-400">
<span>${printer.model || 'Unbekanntes Modell'}</span>
<span></span>
<span>${printer.location || 'Kein Standort'}</span>
<span></span>
<span>${printer.ip_address || 'Keine IP'}</span>
</div>
</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 class="flex items-center gap-3">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium ${statusClasses.badge}">
${this.getStatusText(printer.status)}
</span>
<div class="flex gap-2">
<button onclick="printerManager.openPrinterDetails('${printer.id}')"
class="printer-action-btn btn-details">
Details
</button>
<button onclick="printerManager.editPrinter('${printer.id}')"
class="printer-action-btn btn-edit">
Bearbeiten
</button>
</div>
</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'
</div>
`;
}
getPrinterActionButton(printer) {
switch (printer.status) {
case 'online':
case 'idle':
return `
<button onclick="printerManager.testPrint('${printer.id}')"
class="printer-action-btn btn-start">
<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="M14.828 14.828a4 4 0 01-5.656 0M9 10h1.586a1 1 0 01.707.293l2.414 2.414a1 1 0 00.707.293H15M9 10V9a2 2 0 012-2h2a2 2 0 012 2v1M9 10v4a2 2 0 002 2h2a2 2 0 002-2v-4"/>
</svg>
Test
</button>
`;
case 'printing':
return `
<button onclick="printerManager.pausePrint('${printer.id}')"
class="printer-action-btn btn-pause">
<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="M10 9v6m4-6v6"/>
</svg>
Pause
</button>
`;
case 'error':
return `
<button onclick="printerManager.resetPrinter('${printer.id}')"
class="printer-action-btn btn-maintenance">
<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="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>
Reset
</button>
`;
default:
return `
<button onclick="printerManager.connectPrinter('${printer.id}')"
class="printer-action-btn btn-start">
<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="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"/>
</svg>
Verbinden
</button>
`;
}
};
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');
getStatusClasses(status) {
const classes = {
'online': {
container: 'status-online',
iconBg: 'bg-green-100 dark:bg-green-900/30 text-green-600',
badge: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'
},
'offline': {
container: 'status-offline',
iconBg: 'bg-red-100 dark:bg-red-900/30 text-red-600',
badge: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'
},
'printing': {
container: 'status-printing',
iconBg: 'bg-blue-100 dark:bg-blue-900/30 text-blue-600',
badge: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400'
},
'error': {
container: 'status-error',
iconBg: 'bg-orange-100 dark:bg-orange-900/30 text-orange-600',
badge: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400'
},
'maintenance': {
container: 'status-maintenance',
iconBg: 'bg-purple-100 dark:bg-purple-900/30 text-purple-600',
badge: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400'
}
};
return classes[status] || classes['offline'];
}
statusFilter.addEventListener('change', applyFilters);
locationFilter.addEventListener('change', applyFilters);
}
// Apply filters
function applyFilters() {
const statusFilter = document.getElementById('filterStatus').value;
const locationFilter = document.getElementById('filterLocation').value;
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 status-pulse"></div>',
'error': '<div class="w-3 h-3 bg-orange-500 rounded-full"></div>',
'maintenance': '<div class="w-3 h-3 bg-purple-500 rounded-full"></div>'
};
return icons[status] || icons['offline'];
}
filteredPrinters = allPrinters.filter(printer => {
const statusMatch = !statusFilter || printer.status === statusFilter;
const locationMatch = !locationFilter || printer.location === locationFilter;
return statusMatch && locationMatch;
});
getStatusText(status) {
const texts = {
'online': 'Online',
'offline': 'Offline',
'printing': 'Druckt',
'error': 'Fehler',
'maintenance': 'Wartung',
'idle': 'Bereit'
};
return texts[status] || status;
}
displayPrinters();
updateStatistics();
}
// Populate location filter
function populateLocationFilter() {
const locationFilter = document.getElementById('filterLocation');
const locations = [...new Set(allPrinters.map(p => p.location).filter(Boolean))];
updateStatistics() {
const stats = {
total: allPrinters.length,
online: allPrinters.filter(p => ['online', 'idle'].includes(p.status)).length,
offline: allPrinters.filter(p => p.status === 'offline').length,
printing: allPrinters.filter(p => p.status === 'printing').length
};
// Animate counter updates
this.animateCounter('total-count', stats.total);
this.animateCounter('online-count', stats.online);
this.animateCounter('offline-count', stats.offline);
this.animateCounter('printing-count', stats.printing);
// Update percentages
const total = stats.total || 1;
document.getElementById('online-percentage').textContent = Math.round((stats.online / total) * 100);
document.getElementById('offline-percentage').textContent = Math.round((stats.offline / total) * 100);
document.getElementById('active-percentage').textContent = Math.round((stats.printing / total) * 100);
this.updatePerformanceMetrics(stats);
}
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');
updatePerformanceMetrics(stats) {
// Update utilization
const utilization = stats.total > 0 ? Math.round((stats.printing / stats.total) * 100) : 0;
document.getElementById('avg-utilization').textContent = utilization + '%';
document.getElementById('utilization-bar').style.width = utilization + '%';
// Update active jobs count
document.getElementById('active-jobs-count').textContent = stats.printing;
// Update queue count (placeholder)
document.getElementById('queue-count').textContent = '0';
// Update time estimates (placeholder)
document.getElementById('estimated-completion').textContent = stats.printing > 0 ? '2h 30min' : '--:--';
document.getElementById('next-job-start').textContent = '--:--';
}
document.getElementById('printerModalTitle').textContent = 'Drucker hinzufügen';
document.getElementById('printerForm').reset();
document.getElementById('printerId').value = '';
document.getElementById('deletePrinterBtn').style.display = 'none';
animateCounter(elementId, targetValue) {
const element = document.getElementById(elementId);
const currentValue = parseInt(element.textContent) || 0;
const duration = 1000;
const steps = 30;
const stepValue = (targetValue - currentValue) / steps;
let currentStep = 0;
const interval = setInterval(() => {
currentStep++;
const newValue = Math.round(currentValue + (stepValue * currentStep));
element.textContent = newValue;
if (currentStep >= steps) {
element.textContent = targetValue;
clearInterval(interval);
}
}, duration / steps);
}
modal.classList.remove('hidden');
populateFilterDropdowns() {
// Populate location filter
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('');
// Populate model filter
const modelFilter = document.getElementById('filterModel');
const models = [...new Set(allPrinters.map(p => p.model).filter(Boolean))];
modelFilter.innerHTML = '<option value="">Alle Modelle</option>' +
models.map(model => `<option value="${model}">${model}</option>`).join('');
}
updateFilterCounts() {
document.getElementById('filtered-count').textContent = filteredPrinters.length;
document.getElementById('total-printers').textContent = allPrinters.length;
}
updateLastUpdateTime() {
const now = new Date();
document.getElementById('last-update-time').textContent = now.toLocaleTimeString('de-DE');
document.getElementById('last-update').textContent = `Letzte Aktualisierung: ${now.toLocaleTimeString('de-DE')}`;
}
// Utility functions
formatDuration(minutes) {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return hours > 0 ? `${hours}h ${mins}min` : `${mins}min`;
}
formatUptime(uptime) {
if (!uptime) return 'N/A';
const days = Math.floor(uptime / 1440);
const hours = Math.floor((uptime % 1440) / 60);
return days > 0 ? `${days}d ${hours}h` : `${hours}h`;
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Auto-refresh functionality
startAutoRefresh() {
if (isAutoRefreshEnabled) {
autoRefreshInterval = setInterval(() => {
this.loadPrinters();
}, 30000); // Refresh every 30 seconds
}
}
stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
showError(message) {
console.error(message);
// Placeholder for toast notification system
}
showSuccess(message) {
console.log(message);
// Placeholder for toast notification system
}
closeAllModals() {
const modals = ['printerModal', 'printerDetailsModal'];
modals.forEach(modalId => {
const modal = document.getElementById(modalId);
if (modal && !modal.classList.contains('hidden')) {
this.closeModal(modalId);
}
});
}
closeModal(modalId) {
const modal = document.getElementById(modalId);
const content = modal.querySelector('.mercedes-modal, .dashboard-card');
content.classList.remove('scale-100', 'opacity-100');
content.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
}, 10);
modal.classList.add('hidden');
}, 200);
}
}
// Initialize Printer Manager
let printerManager;
document.addEventListener('DOMContentLoaded', () => {
printerManager = new PrinterManager();
});
// Global functions for UI interactions
function refreshPrinters() {
const button = document.getElementById('refresh-button');
button.disabled = true;
button.querySelector('svg').classList.add('animate-spin');
printerManager.loadPrinters().finally(() => {
button.disabled = false;
button.querySelector('svg').classList.remove('animate-spin');
});
}
function toggleAutoRefresh() {
isAutoRefreshEnabled = !isAutoRefreshEnabled;
const button = document.getElementById('auto-refresh-toggle');
if (isAutoRefreshEnabled) {
button.textContent = 'Auto-Refresh: ON';
button.classList.remove('bg-gray-500');
button.classList.add('bg-mercedes-blue');
printerManager.startAutoRefresh();
} else {
button.textContent = 'Auto-Refresh: OFF';
button.classList.remove('bg-mercedes-blue');
button.classList.add('bg-gray-500');
printerManager.stopAutoRefresh();
}
}
function toggleMaintenanceMode() {
isMaintenanceMode = !isMaintenanceMode;
const button = document.getElementById('maintenance-toggle');
if (isMaintenanceMode) {
button.classList.add('bg-orange-500', 'text-white');
button.querySelector('span').textContent = 'Wartung aktiv';
} else {
button.classList.remove('bg-orange-500', 'text-white');
button.querySelector('span').textContent = 'Wartungsmodus';
}
}
function toggleGridView(view) {
currentGridView = view;
const gridBtn = document.getElementById('grid-view-btn');
const listBtn = document.getElementById('list-view-btn');
if (view === 'grid') {
gridBtn.classList.add('bg-mercedes-blue', 'text-white');
gridBtn.classList.remove('text-mercedes-gray', 'hover:bg-mercedes-silver');
listBtn.classList.remove('bg-mercedes-blue', 'text-white');
listBtn.classList.add('text-mercedes-gray', 'hover:bg-mercedes-silver');
} else {
listBtn.classList.add('bg-mercedes-blue', 'text-white');
listBtn.classList.remove('text-mercedes-gray', 'hover:bg-mercedes-silver');
gridBtn.classList.remove('bg-mercedes-blue', 'text-white');
gridBtn.classList.add('text-mercedes-gray', 'hover:bg-mercedes-silver');
}
printerManager.displayPrinters();
}
function clearAllFilters() {
document.getElementById('search-input').value = '';
document.getElementById('filterStatus').value = '';
document.getElementById('filterLocation').value = '';
document.getElementById('filterModel').value = '';
document.getElementById('show-offline').checked = false;
document.getElementById('show-maintenance').checked = false;
document.getElementById('only-available').checked = false;
document.getElementById('sort-by').value = 'name';
printerManager.applyFilters();
}
function openAddPrinterModal() {
printerManager.openAddPrinterModal();
}
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);
printerManager.closeModal('printerModal');
}
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);
printerManager.closeModal('printerDetailsModal');
}
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 %}

View File

@ -74,8 +74,8 @@ def test_database_schema():
print_status("=== TESTE DATENBANK-SCHEMA ===", "INFO")
try:
# Verbindung zur Datenbank
db_path = "database.db"
# Verbindung zur Datenbank - korrigierter Pfad
db_path = "database/myp.db"
if not os.path.exists(db_path):
print_status("✗ Datenbank nicht gefunden", "ERROR")
return False