2399 lines
105 KiB
HTML
2399 lines
105 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Drucker-Steckdosen - Mercedes-Benz TBA Marienfelde{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* Mercedes-Benz Corporate Design */
|
|
.text-mercedes-black { color: #000000; }
|
|
.text-mercedes-gray { color: #6b7280; }
|
|
.text-mercedes-silver { color: #9ca3af; }
|
|
.text-mercedes-blue { color: #0073ce; }
|
|
.text-mercedes-green { color: #008c32; }
|
|
.text-mercedes-red { color: #dc2626; }
|
|
|
|
.bg-mercedes-black { background-color: #000000; }
|
|
.bg-mercedes-silver { background-color: #e5e7eb; }
|
|
.bg-mercedes-blue { background-color: #0073ce; }
|
|
.bg-mercedes-green { background-color: #008c32; }
|
|
|
|
.border-mercedes-silver { border-color: #d1d5db; }
|
|
.border-mercedes-blue { border-color: #0073ce; }
|
|
|
|
.hover\:border-mercedes-blue:hover { border-color: #0073ce; }
|
|
.focus\:ring-mercedes-blue:focus {
|
|
--tw-ring-color: #0073ce;
|
|
--tw-ring-opacity: 0.5;
|
|
}
|
|
.focus\:border-mercedes-blue:focus { border-color: #0073ce; }
|
|
|
|
/* Enhanced Printer Cards */
|
|
.printer-card {
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
border: 1px solid #e2e8f0;
|
|
box-shadow:
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
border-radius: 16px;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.dark .printer-card {
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
border-color: #334155;
|
|
box-shadow:
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.3),
|
|
0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.printer-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow:
|
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
|
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.dark .printer-card:hover {
|
|
box-shadow:
|
|
0 20px 25px -5px rgba(0, 0, 0, 0.3),
|
|
0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* Status Indicators */
|
|
.status-online {
|
|
border-left: 4px solid #10b981;
|
|
background: linear-gradient(90deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.05) 100%);
|
|
}
|
|
|
|
.status-offline {
|
|
border-left: 4px solid #ef4444;
|
|
background: linear-gradient(90deg, rgba(239, 68, 68, 0.1) 0%, rgba(220, 38, 38, 0.05) 100%);
|
|
}
|
|
|
|
.status-printing {
|
|
border-left: 4px solid #3b82f6;
|
|
background: linear-gradient(90deg, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.05) 100%);
|
|
}
|
|
|
|
.status-error {
|
|
border-left: 4px solid #f59e0b;
|
|
background: linear-gradient(90deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.05) 100%);
|
|
}
|
|
|
|
.status-maintenance {
|
|
border-left: 4px solid #8b5cf6;
|
|
background: linear-gradient(90deg, rgba(139, 92, 246, 0.1) 0%, rgba(124, 58, 237, 0.05) 100%);
|
|
}
|
|
|
|
.status-standby {
|
|
border-left: 4px solid #f59e0b;
|
|
background: linear-gradient(90deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.05) 100%);
|
|
}
|
|
|
|
.status-unreachable {
|
|
border-left: 4px solid #6b7280;
|
|
background: linear-gradient(90deg, rgba(107, 114, 128, 0.1) 0%, rgba(75, 85, 99, 0.05) 100%);
|
|
}
|
|
|
|
.status-unconfigured {
|
|
border-left: 4px solid #6366f1;
|
|
background: linear-gradient(90deg, rgba(99, 102, 241, 0.1) 0%, rgba(79, 70, 229, 0.05) 100%);
|
|
}
|
|
|
|
/* Real-time Status Indicators */
|
|
.status-pulse {
|
|
animation: pulse-status 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse-status {
|
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
50% { opacity: 0.8; transform: scale(1.1); }
|
|
}
|
|
|
|
/* Temperature Gauges */
|
|
.temperature-gauge {
|
|
width: 80px;
|
|
height: 80px;
|
|
position: relative;
|
|
background: conic-gradient(from 0deg, #ef4444 0%, #f59e0b 25%, #10b981 50%, #3b82f6 75%, #ef4444 100%);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.temperature-gauge::before {
|
|
content: '';
|
|
position: absolute;
|
|
width: 70px;
|
|
height: 70px;
|
|
background: #ffffff;
|
|
border-radius: 50%;
|
|
top: 5px;
|
|
left: 5px;
|
|
}
|
|
|
|
.dark .temperature-gauge::before {
|
|
background: #1e293b;
|
|
}
|
|
|
|
.temperature-value {
|
|
position: relative;
|
|
z-index: 1;
|
|
font-weight: bold;
|
|
font-size: 0.875rem;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Progress Indicators */
|
|
.print-progress {
|
|
background: #f3f4f6;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.dark .print-progress {
|
|
background: #374151;
|
|
}
|
|
|
|
.print-progress-bar {
|
|
background: linear-gradient(90deg, #0073ce 0%, #1e40af 100%);
|
|
height: 100%;
|
|
border-radius: 4px;
|
|
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.dark .print-progress-bar {
|
|
background: linear-gradient(90deg, #60a5fa 0%, #3b82f6 100%);
|
|
}
|
|
|
|
.print-progress-bar::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
|
animation: progress-shine 2s infinite;
|
|
}
|
|
|
|
@keyframes progress-shine {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
/* Enhanced Form Elements */
|
|
.mercedes-form-input {
|
|
transition: all 0.2s ease;
|
|
border: 1px solid #d1d5db;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.mercedes-form-input:focus {
|
|
border-color: #0073ce;
|
|
box-shadow:
|
|
0 0 0 3px rgba(0, 115, 206, 0.1),
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
transform: translateY(-1px);
|
|
background: rgba(255, 255, 255, 1);
|
|
}
|
|
|
|
.dark .mercedes-form-input {
|
|
background: rgba(30, 41, 59, 0.8);
|
|
border-color: #475569;
|
|
color: #f8fafc;
|
|
}
|
|
|
|
.dark .mercedes-form-input:focus {
|
|
border-color: #0ea5e9;
|
|
box-shadow:
|
|
0 0 0 3px rgba(14, 165, 233, 0.1),
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
|
background: rgba(30, 41, 59, 1);
|
|
}
|
|
|
|
/* Enhanced Modals */
|
|
.mercedes-modal {
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
border: 1px solid #e2e8f0;
|
|
box-shadow:
|
|
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
border-radius: 16px;
|
|
backdrop-filter: blur(10px);
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
|
}
|
|
|
|
.dark .mercedes-modal {
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
border-color: #334155;
|
|
box-shadow:
|
|
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
|
0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
|
scrollbar-color: rgba(59, 130, 246, 0.3) transparent;
|
|
}
|
|
|
|
/* Modal Scrollbar Styling */
|
|
.mercedes-modal::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
.mercedes-modal::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.mercedes-modal::-webkit-scrollbar-thumb {
|
|
background: rgba(0, 115, 206, 0.2);
|
|
border-radius: 4px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.mercedes-modal::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(0, 115, 206, 0.4);
|
|
}
|
|
|
|
.dark .mercedes-modal::-webkit-scrollbar-thumb {
|
|
background: rgba(59, 130, 246, 0.3);
|
|
}
|
|
|
|
.dark .mercedes-modal::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(59, 130, 246, 0.5);
|
|
}
|
|
|
|
/* Smooth scrolling for modals */
|
|
.mercedes-modal {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
/* Ensure modal containers allow scrolling */
|
|
.modal-container {
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Action Buttons */
|
|
.printer-action-btn {
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 8px;
|
|
font-weight: 500;
|
|
font-size: 0.875rem;
|
|
transition: all 0.2s ease;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.printer-action-btn:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.btn-start {
|
|
background: #16a34a;
|
|
color: white;
|
|
}
|
|
|
|
.btn-stop {
|
|
background: #ef4444;
|
|
color: white;
|
|
}
|
|
|
|
.btn-pause {
|
|
background: #f59e0b;
|
|
color: white;
|
|
}
|
|
|
|
.btn-maintenance {
|
|
background: #8b5cf6;
|
|
color: white;
|
|
}
|
|
|
|
.btn-details {
|
|
background: #0073ce;
|
|
color: white;
|
|
}
|
|
|
|
.btn-edit {
|
|
background: #6b7280;
|
|
color: white;
|
|
}
|
|
|
|
/* Live Monitoring Dashboard */
|
|
.monitoring-widget {
|
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
padding: 1rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .monitoring-widget {
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
border-color: #334155;
|
|
}
|
|
|
|
.monitoring-widget:hover {
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Statistics Cards */
|
|
.stat-card {
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.dark .stat-card {
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
border-color: #334155;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.stat-card.online {
|
|
border-left: 4px solid #10b981;
|
|
}
|
|
|
|
.stat-card.offline {
|
|
border-left: 4px solid #ef4444;
|
|
}
|
|
|
|
.stat-card.printing {
|
|
border-left: 4px solid #3b82f6;
|
|
}
|
|
|
|
.stat-card.total {
|
|
border-left: 4px solid #6b7280;
|
|
}
|
|
|
|
/* Loading States */
|
|
.loading-skeleton {
|
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
background-size: 200% 100%;
|
|
animation: loading 2s infinite;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.dark .loading-skeleton {
|
|
background: linear-gradient(90deg, #374151 25%, #4b5563 50%, #374151 75%);
|
|
background-size: 200% 100%;
|
|
}
|
|
|
|
@keyframes loading {
|
|
0% { background-position: 200% 0; }
|
|
100% { background-position: -200% 0; }
|
|
}
|
|
|
|
/* Filter Section */
|
|
.filter-section {
|
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .filter-section {
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
border-color: #334155;
|
|
}
|
|
|
|
.filter-section:hover {
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
/* Network Status Indicators */
|
|
.network-indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.network-connected {
|
|
background: rgba(16, 185, 129, 0.1);
|
|
color: #059669;
|
|
border: 1px solid rgba(16, 185, 129, 0.2);
|
|
}
|
|
|
|
.network-disconnected {
|
|
background: rgba(239, 68, 68, 0.1);
|
|
color: #dc2626;
|
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
}
|
|
|
|
/* Responsive Design */
|
|
@media (max-width: 768px) {
|
|
.printer-card {
|
|
margin: 0 0.5rem;
|
|
}
|
|
|
|
.filter-section {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.printer-action-btn {
|
|
font-size: 0.75rem;
|
|
padding: 0.375rem 0.75rem;
|
|
}
|
|
|
|
.temperature-gauge {
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
|
|
.temperature-gauge::before {
|
|
width: 50px;
|
|
height: 50px;
|
|
top: 5px;
|
|
left: 5px;
|
|
}
|
|
}
|
|
|
|
/* Accessibility Enhancements */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*, *::before, *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
}
|
|
}
|
|
|
|
/* Focus States */
|
|
.printer-card:focus-within {
|
|
outline: 2px solid #0073ce;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.printer-action-btn:focus {
|
|
outline: 2px solid #0073ce;
|
|
outline-offset: 2px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block head %}
|
|
{{ super() }}
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-8">
|
|
<!-- Enhanced 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-6">
|
|
<div class="w-16 h-16 flex-shrink-0 bg-mercedes-blue text-white rounded-xl flex items-center justify-center">
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-4xl font-bold text-mercedes-black dark:text-white tracking-tight">Drucker-Steckdosen</h1>
|
|
<p class="text-mercedes-gray dark:text-slate-400 mt-1 text-lg">Sichere Steckdosen-Steuerung für 3D-Drucker in der TBA</p>
|
|
<div class="flex items-center mt-2 text-sm text-mercedes-blue">
|
|
<div id="live-status-indicator" class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></div>
|
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
<span id="last-update">Echtzeit-Status aktiv</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap gap-3">
|
|
<button onclick="refreshPrinters()" id="refresh-button"
|
|
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>Status aktualisieren</span>
|
|
</button>
|
|
{% if current_user.is_authenticated and current_user.is_admin %}
|
|
<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>Steckdose hinzufügen</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Statistics Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<div class="stat-card online p-6">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Online</h3>
|
|
<div class="text-3xl font-bold text-green-600 dark:text-green-400 mb-1" id="online-count">-</div>
|
|
<div class="flex items-center text-xs text-green-600 dark:text-green-400">
|
|
<div class="w-2 h-2 bg-green-500 rounded-full mr-2 status-pulse"></div>
|
|
<span>Betriebsbereit</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-xl flex items-center justify-center">
|
|
<div class="w-6 h-6 bg-green-500 rounded-full"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 pt-4 border-t border-green-200 dark:border-green-800">
|
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
|
<span class="font-medium" id="online-percentage">0%</span> aller Drucker
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card offline p-6">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Offline</h3>
|
|
<div class="text-3xl font-bold text-red-600 dark:text-red-400 mb-1" id="offline-count">-</div>
|
|
<div class="flex items-center text-xs text-red-600 dark:text-red-400">
|
|
<div class="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
|
|
<span>Nicht verfügbar</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-12 h-12 bg-red-100 dark:bg-red-900/30 rounded-xl flex items-center justify-center">
|
|
<div class="w-6 h-6 bg-red-500 rounded-full"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 pt-4 border-t border-red-200 dark:border-red-800">
|
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
|
<span class="font-medium" id="offline-percentage">0%</span> aller Drucker
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card printing p-6">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Aktiv</h3>
|
|
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-1" id="printing-count">-</div>
|
|
<div class="flex items-center text-xs text-blue-600 dark:text-blue-400">
|
|
<div class="w-2 h-2 bg-blue-500 rounded-full mr-2 status-pulse"></div>
|
|
<span>Druckt gerade</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-xl flex items-center justify-center">
|
|
<div class="w-6 h-6 bg-blue-500 rounded-full animate-pulse"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 pt-4 border-t border-blue-200 dark:border-blue-800">
|
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
|
<span class="font-medium" id="active-percentage">0%</span> Auslastung
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card total p-6">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Gesamt</h3>
|
|
<div class="text-3xl font-bold text-mercedes-black dark:text-white mb-1" id="total-count">-</div>
|
|
<div class="flex items-center text-xs text-mercedes-gray dark:text-slate-400">
|
|
<svg class="w-3 h-3 mr-2" 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>
|
|
<span>Drucker registriert</span>
|
|
</div>
|
|
</div>
|
|
<div class="w-12 h-12 bg-mercedes-silver rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-mercedes-black 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>
|
|
<div class="mt-4 pt-4 border-t border-mercedes-silver">
|
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
|
Letzte Aktualisierung: <span id="last-update-time">--:--</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Live Performance Overview -->
|
|
<div class="dashboard-card p-6">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-mercedes-black dark:text-white mb-2">Live Performance</h2>
|
|
<p class="text-mercedes-gray dark:text-slate-400">Echzeit-Überwachung der Druckerleistung</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<div class="network-indicator network-connected">
|
|
<div class="w-2 h-2 bg-green-500 rounded-full status-pulse"></div>
|
|
<span>Verbunden</span>
|
|
</div>
|
|
<button onclick="toggleAutoRefresh()" id="auto-refresh-toggle"
|
|
class="text-sm px-3 py-1 bg-mercedes-blue text-white rounded-lg hover:bg-blue-700 transition-colors">
|
|
Auto-Refresh: ON
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div class="monitoring-widget text-center">
|
|
<div class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Durchschnittliche Auslastung</div>
|
|
<div class="text-2xl font-bold text-mercedes-blue mb-2" id="avg-utilization">--</div>
|
|
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
|
<div class="bg-mercedes-blue h-2 rounded-full transition-all duration-1000"
|
|
id="utilization-bar" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="monitoring-widget text-center">
|
|
<div class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Aktive Jobs</div>
|
|
<div class="text-2xl font-bold text-green-600 mb-2" id="active-jobs-count">--</div>
|
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
|
Geschätzte Fertigstellung: <span id="estimated-completion">--:--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="monitoring-widget text-center">
|
|
<div class="text-sm font-medium text-mercedes-gray dark:text-slate-400 mb-2">Warteschlange</div>
|
|
<div class="text-2xl font-bold text-yellow-600 mb-2" id="queue-count">--</div>
|
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
|
Nächster Start: <span id="next-job-start">--:--</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Filter Section -->
|
|
<div class="filter-section">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-mercedes-black dark:text-white mb-2">Filter & Suche</h3>
|
|
<p class="text-sm text-mercedes-gray dark:text-slate-400">Finden Sie schnell den gewünschten Drucker</p>
|
|
</div>
|
|
<button onclick="clearAllFilters()" class="text-sm text-mercedes-blue hover:text-blue-700 transition-colors">
|
|
<svg class="w-4 h-4 inline mr-1" 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>
|
|
Alle Filter zurücksetzen
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<!-- Search -->
|
|
<div>
|
|
<label for="search-input" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
</svg>
|
|
Suchen
|
|
</label>
|
|
<input type="text" id="search-input" placeholder="Drucker-Name, Modell oder IP..."
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg">
|
|
</div>
|
|
|
|
<!-- Status Filter -->
|
|
<div>
|
|
<label for="filterStatus" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z"/>
|
|
</svg>
|
|
Status
|
|
</label>
|
|
<select id="filterStatus" class="mercedes-form-input block w-full px-4 py-3 rounded-lg">
|
|
<option value="">Alle Status</option>
|
|
<option value="online">Online</option>
|
|
<option value="standby">Standby</option>
|
|
<option value="offline">Offline</option>
|
|
<option value="unreachable">Unerreichbar</option>
|
|
<option value="printing">Druckt</option>
|
|
<option value="error">Fehler</option>
|
|
<option value="maintenance">Wartung</option>
|
|
<option value="unconfigured">Nicht konfiguriert</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Location Filter -->
|
|
<div>
|
|
<label for="filterLocation" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
<svg class="w-4 h-4 inline mr-1" 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>
|
|
Standort
|
|
</label>
|
|
<select id="filterLocation" class="mercedes-form-input block w-full px-4 py-3 rounded-lg">
|
|
<option value="">Alle Standorte</option>
|
|
<!-- Wird dynamisch gefüllt -->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Model Filter -->
|
|
<div>
|
|
<label for="filterModel" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
|
</svg>
|
|
Modell
|
|
</label>
|
|
<select id="filterModel" class="mercedes-form-input block w-full px-4 py-3 rounded-lg">
|
|
<option value="">Alle Modelle</option>
|
|
<!-- Wird dynamisch gefüllt -->
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Filter Options -->
|
|
<div class="mt-6 flex flex-wrap items-center justify-between gap-4">
|
|
<div class="flex flex-wrap gap-4">
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="show-offline" class="w-4 h-4 text-mercedes-blue rounded">
|
|
<label for="show-offline" class="ml-2 text-sm text-mercedes-gray dark:text-slate-400">
|
|
Offline-Drucker anzeigen
|
|
</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="show-maintenance" class="w-4 h-4 text-mercedes-blue rounded">
|
|
<label for="show-maintenance" class="ml-2 text-sm text-mercedes-gray dark:text-slate-400">
|
|
Wartungsmodus anzeigen
|
|
</label>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="only-available" class="w-4 h-4 text-mercedes-blue rounded">
|
|
<label for="only-available" class="ml-2 text-sm text-mercedes-gray dark:text-slate-400">
|
|
Nur verfügbare Drucker
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<div class="text-sm text-mercedes-gray dark:text-slate-400">
|
|
<span id="filtered-count">0</span> von <span id="total-printers">0</span> Druckern
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<label for="sort-by" class="text-sm text-mercedes-gray dark:text-slate-400">Sortieren:</label>
|
|
<select id="sort-by" class="text-sm border border-mercedes-silver rounded px-2 py-1">
|
|
<option value="name">Name</option>
|
|
<option value="status">Status</option>
|
|
<option value="location">Standort</option>
|
|
<option value="model">Modell</option>
|
|
<option value="last_activity">Letzte Aktivität</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced Printers Grid -->
|
|
<div class="dashboard-card p-6">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-mercedes-black dark:text-white mb-2">Drucker-Übersicht</h2>
|
|
<p class="text-mercedes-gray dark:text-slate-400">Live-Status aller registrierten 3D-Drucker</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<div class="text-sm text-mercedes-gray dark:text-slate-400">
|
|
Ansicht:
|
|
</div>
|
|
<button onclick="toggleGridView('grid')" id="grid-view-btn"
|
|
class="p-2 rounded-lg transition-colors bg-mercedes-blue text-white">
|
|
<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 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/>
|
|
</svg>
|
|
</button>
|
|
<button onclick="toggleGridView('list')" id="list-view-btn"
|
|
class="p-2 rounded-lg transition-colors text-mercedes-gray hover:bg-mercedes-silver">
|
|
<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 6h16M4 10h16M4 14h16M4 18h16"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div id="loading-state" class="space-y-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div class="printer-card p-6">
|
|
<div class="flex items-center gap-4 mb-4">
|
|
<div class="loading-skeleton w-12 h-12 rounded-xl"></div>
|
|
<div class="flex-1">
|
|
<div class="loading-skeleton h-5 w-3/4 mb-2"></div>
|
|
<div class="loading-skeleton h-4 w-1/2"></div>
|
|
</div>
|
|
<div class="loading-skeleton w-16 h-6 rounded-full"></div>
|
|
</div>
|
|
<div class="space-y-3 mb-4">
|
|
<div class="loading-skeleton h-4 w-full"></div>
|
|
<div class="loading-skeleton h-4 w-2/3"></div>
|
|
<div class="loading-skeleton h-4 w-1/2"></div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<div class="loading-skeleton h-8 w-20 rounded-lg"></div>
|
|
<div class="loading-skeleton h-8 w-24 rounded-lg"></div>
|
|
</div>
|
|
</div>
|
|
<div class="printer-card p-6">
|
|
<div class="flex items-center gap-4 mb-4">
|
|
<div class="loading-skeleton w-12 h-12 rounded-xl"></div>
|
|
<div class="flex-1">
|
|
<div class="loading-skeleton h-5 w-3/4 mb-2"></div>
|
|
<div class="loading-skeleton h-4 w-1/2"></div>
|
|
</div>
|
|
<div class="loading-skeleton w-16 h-6 rounded-full"></div>
|
|
</div>
|
|
<div class="space-y-3 mb-4">
|
|
<div class="loading-skeleton h-4 w-full"></div>
|
|
<div class="loading-skeleton h-4 w-2/3"></div>
|
|
<div class="loading-skeleton h-4 w-1/2"></div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<div class="loading-skeleton h-8 w-20 rounded-lg"></div>
|
|
<div class="loading-skeleton h-8 w-24 rounded-lg"></div>
|
|
</div>
|
|
</div>
|
|
<div class="printer-card p-6">
|
|
<div class="flex items-center gap-4 mb-4">
|
|
<div class="loading-skeleton w-12 h-12 rounded-xl"></div>
|
|
<div class="flex-1">
|
|
<div class="loading-skeleton h-5 w-3/4 mb-2"></div>
|
|
<div class="loading-skeleton h-4 w-1/2"></div>
|
|
</div>
|
|
<div class="loading-skeleton w-16 h-6 rounded-full"></div>
|
|
</div>
|
|
<div class="space-y-3 mb-4">
|
|
<div class="loading-skeleton h-4 w-full"></div>
|
|
<div class="loading-skeleton h-4 w-2/3"></div>
|
|
<div class="loading-skeleton h-4 w-1/2"></div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<div class="loading-skeleton h-8 w-20 rounded-lg"></div>
|
|
<div class="loading-skeleton h-8 w-24 rounded-lg"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-mercedes-gray dark:text-slate-400 mb-4">
|
|
<svg class="w-8 h-8 mx-auto mb-3 animate-spin text-mercedes-blue" 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-mercedes-gray dark:text-slate-400">Lade Drucker-Informationen...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Printers Grid Container -->
|
|
<div id="printers-container" class="hidden">
|
|
<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>
|
|
|
|
<!-- Empty State -->
|
|
<div id="empty-state" class="hidden col-span-full py-16 text-center">
|
|
<div class="max-w-md mx-auto">
|
|
<div class="text-mercedes-silver mb-6">
|
|
<svg class="w-20 h-20 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" 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-2xl font-semibold text-mercedes-black dark:text-white mb-3">Keine Drucker gefunden</h3>
|
|
<p class="text-mercedes-gray dark:text-slate-400 mb-8 leading-relaxed">
|
|
Es wurden keine 3D-Drucker gefunden, die den aktuellen Filterkriterien entsprechen.
|
|
Versuchen Sie, die Filter zu ändern oder fügen Sie Ihren ersten Drucker hinzu.
|
|
</p>
|
|
<div class="flex flex-col sm:flex-row gap-3 justify-center">
|
|
<button onclick="clearAllFilters()" class="btn-secondary">
|
|
Filter zurücksetzen
|
|
</button>
|
|
<button onclick="openAddPrinterModal()" class="btn-primary">
|
|
Ersten Drucker hinzufügen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- No Results State -->
|
|
<div id="no-results-state" class="hidden col-span-full py-16 text-center">
|
|
<div class="max-w-md mx-auto">
|
|
<div class="text-mercedes-silver mb-6">
|
|
<svg class="w-16 h-16 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-xl font-semibold text-mercedes-black dark:text-white mb-3">Keine passenden Drucker</h3>
|
|
<p class="text-mercedes-gray dark:text-slate-400 mb-6">
|
|
Ihre Suche ergab keine Treffer. Versuchen Sie andere Suchbegriffe oder erweitern Sie Ihre Filter.
|
|
</p>
|
|
<button onclick="clearAllFilters()" class="btn-secondary">
|
|
Alle Filter zurücksetzen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced 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="mercedes-modal max-w-2xl 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-2xl font-bold text-mercedes-black dark:text-white mb-2">
|
|
Drucker hinzufügen
|
|
</h3>
|
|
<p class="text-mercedes-gray dark:text-slate-400">Konfigurieren Sie einen neuen 3D-Drucker für das System</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-mercedes-gray 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">
|
|
|
|
<!-- Basic Information -->
|
|
<div class="space-y-4">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white border-b border-mercedes-silver pb-2">
|
|
Grundinformationen
|
|
</h4>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="printerName" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Drucker-Name <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printerName" name="name" required
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="z.B. Ultimaker S3 #01">
|
|
<p class="text-xs text-mercedes-gray dark:text-slate-400 mt-1">Eindeutiger Name zur Identifikation</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printerModel" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Modell
|
|
</label>
|
|
<select id="printerModel" name="model" class="mercedes-form-input block w-full px-4 py-3 rounded-lg">
|
|
<option value="">Modell auswählen...</option>
|
|
<option value="Ultimaker S3">Ultimaker S3</option>
|
|
<option value="Ultimaker S5">Ultimaker S5</option>
|
|
<option value="Prusa MK3S+">Prusa MK3S+</option>
|
|
<option value="Bambu Lab X1 Carbon">Bambu Lab X1 Carbon</option>
|
|
<option value="Formlabs Form 3">Formlabs Form 3</option>
|
|
<option value="Custom">Benutzerdefiniert</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="customModelDiv" class="hidden md:col-span-2">
|
|
<label for="customModel" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Benutzerdefiniertes Modell
|
|
</label>
|
|
<input type="text" id="customModel" name="customModel"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="Geben Sie das Druckermodell ein">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printerLocation" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Standort <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" id="printerLocation" name="location" required
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="z.B. Produktionshalle A, Raum 101">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printerIP" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
IP-Adresse
|
|
</label>
|
|
<input type="text" id="printerIP" name="ip_address"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="192.168.1.100"
|
|
pattern="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$">
|
|
<p class="text-xs text-mercedes-gray dark:text-slate-400 mt-1">Für Netzwerk-Überwachung</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Technical Specifications -->
|
|
<div class="space-y-4">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white border-b border-mercedes-silver pb-2">
|
|
Technische Spezifikationen
|
|
</h4>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<label for="buildVolumeX" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Bauvolumen X (mm)
|
|
</label>
|
|
<input type="number" id="buildVolumeX" name="build_volume_x" min="0" max="1000"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="330">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="buildVolumeY" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Bauvolumen Y (mm)
|
|
</label>
|
|
<input type="number" id="buildVolumeY" name="build_volume_y" min="0" max="1000"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="240">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="buildVolumeZ" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Bauvolumen Z (mm)
|
|
</label>
|
|
<input type="number" id="buildVolumeZ" name="build_volume_z" min="0" max="1000"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="300">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="nozzleTemp" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Max. Düsentemperatur (°C)
|
|
</label>
|
|
<input type="number" id="nozzleTemp" name="max_nozzle_temp" min="0" max="500"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="280">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="bedTemp" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Max. Betttemperatur (°C)
|
|
</label>
|
|
<input type="number" id="bedTemp" name="max_bed_temp" min="0" max="200"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg"
|
|
placeholder="100">
|
|
</div>
|
|
|
|
<div>
|
|
<label for="filamentTypes" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Unterstützte Materialien
|
|
</label>
|
|
<select id="filamentTypes" name="supported_filaments" multiple
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg">
|
|
<option value="PLA">PLA</option>
|
|
<option value="ABS">ABS</option>
|
|
<option value="PETG">PETG</option>
|
|
<option value="TPU">TPU</option>
|
|
<option value="ASA">ASA</option>
|
|
<option value="PC">PC (Polycarbonat)</option>
|
|
<option value="Nylon">Nylon</option>
|
|
</select>
|
|
<p class="text-xs text-mercedes-gray dark:text-slate-400 mt-1">Mehrfachauswahl möglich</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings -->
|
|
<div class="space-y-4">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white border-b border-mercedes-silver pb-2">
|
|
Einstellungen
|
|
</h4>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="space-y-4">
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="printerActive" name="active" checked
|
|
class="w-4 h-4 text-mercedes-blue bg-white dark:bg-slate-800 border-mercedes-silver rounded focus:ring-mercedes-blue focus:ring-2">
|
|
<label for="printerActive" class="ml-3 text-sm text-mercedes-black dark:text-slate-300">
|
|
Drucker aktiv
|
|
</label>
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="autoQueue" name="auto_queue"
|
|
class="w-4 h-4 text-mercedes-blue bg-white dark:bg-slate-800 border-mercedes-silver rounded focus:ring-mercedes-blue focus:ring-2">
|
|
<label for="autoQueue" class="ml-3 text-sm text-mercedes-black dark:text-slate-300">
|
|
Automatische Warteschlange
|
|
</label>
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
<input type="checkbox" id="enableMonitoring" name="enable_monitoring"
|
|
class="w-4 h-4 text-mercedes-blue bg-white dark:bg-slate-800 border-mercedes-silver rounded focus:ring-mercedes-blue focus:ring-2">
|
|
<label for="enableMonitoring" class="ml-3 text-sm text-mercedes-black dark:text-slate-300">
|
|
Live-Überwachung aktivieren
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="printerNotes" class="block text-sm font-medium text-mercedes-black dark:text-slate-300 mb-2">
|
|
Notizen
|
|
</label>
|
|
<textarea id="printerNotes" name="notes" rows="4"
|
|
class="mercedes-form-input block w-full px-4 py-3 rounded-lg resize-none"
|
|
placeholder="Zusätzliche Informationen zum Drucker..."></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Connection Test -->
|
|
<div id="connectionTest" class="hidden space-y-4">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white border-b border-mercedes-silver pb-2">
|
|
Verbindungstest
|
|
</h4>
|
|
|
|
<div class="p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-blue-800 dark:text-blue-400">Verbindung testen</p>
|
|
<p class="text-xs text-blue-600 dark:text-blue-300">Prüfen Sie die Netzwerkverbindung zum Drucker</p>
|
|
</div>
|
|
<button type="button" onclick="testPrinterConnection()" id="testConnectionBtn"
|
|
class="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors">
|
|
Test starten
|
|
</button>
|
|
</div>
|
|
<div id="connectionResult" class="mt-3 hidden"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between pt-6 border-t border-mercedes-silver">
|
|
<button type="button" id="deletePrinterBtn" onclick="deletePrinter()" style="display: none;"
|
|
class="px-6 py-3 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors flex items-center gap-2">
|
|
<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>
|
|
Löschen
|
|
</button>
|
|
|
|
<div class="flex items-center gap-3">
|
|
<button type="button" onclick="closePrinterModal()" class="btn-secondary">
|
|
Abbrechen
|
|
</button>
|
|
<button type="submit" class="btn-primary flex items-center gap-2">
|
|
<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="M5 13l4 4L19 7"/>
|
|
</svg>
|
|
Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enhanced 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="mercedes-modal max-w-6xl 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-2xl font-bold text-mercedes-black dark:text-white mb-2">
|
|
Drucker Details
|
|
</h3>
|
|
<p class="text-mercedes-gray dark:text-slate-400">Live-Informationen und Steuerungsoptionen</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-mercedes-gray 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" class="space-y-6">
|
|
<!-- Content will be loaded dynamically -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Printer Status -->
|
|
<div class="lg:col-span-2 space-y-6">
|
|
<div class="monitoring-widget p-6">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white mb-4">Live Status</h4>
|
|
<div id="printer-status-info">
|
|
<!-- Dynamic content -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current Job -->
|
|
<div class="monitoring-widget p-6">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white mb-4">Aktueller Job</h4>
|
|
<div id="current-job-info">
|
|
<!-- Dynamic content -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="monitoring-widget p-6">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white mb-4">Letzte Aktivitäten</h4>
|
|
<div id="recent-activity-info">
|
|
<!-- Dynamic content -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls & Monitoring -->
|
|
<div class="space-y-6">
|
|
<!-- Temperature Monitoring -->
|
|
<div class="monitoring-widget p-6">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white mb-4">Temperaturen</h4>
|
|
<div id="temperature-info">
|
|
<!-- Dynamic content -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="monitoring-widget p-6">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white mb-4">Schnellaktionen</h4>
|
|
<div id="quick-actions-info">
|
|
<!-- Dynamic content -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics -->
|
|
<div class="monitoring-widget p-6">
|
|
<h4 class="text-lg font-semibold text-mercedes-black dark:text-white mb-4">Statistiken</h4>
|
|
<div id="printer-statistics">
|
|
<!-- Dynamic content -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global variables
|
|
let allPrinters = [];
|
|
let filteredPrinters = [];
|
|
let isAutoRefreshEnabled = true;
|
|
let autoRefreshInterval;
|
|
let currentGridView = 'grid';
|
|
let isMaintenanceMode = false;
|
|
let printerManager;
|
|
|
|
// Enhanced Printer Management System
|
|
class PrinterManager {
|
|
constructor() {
|
|
this.init();
|
|
this.setupEventListeners();
|
|
this.startAutoRefresh();
|
|
}
|
|
|
|
async init() {
|
|
await this.loadPrinters();
|
|
this.populateFilterDropdowns(); // setupFilters() existiert nicht - verwende populateFilterDropdowns()
|
|
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();
|
|
}
|
|
});
|
|
}
|
|
|
|
handleModelChange(event) {
|
|
const customDiv = document.getElementById('customModelDiv');
|
|
if (event.target.value === 'Custom') {
|
|
customDiv.classList.remove('hidden');
|
|
} else {
|
|
customDiv.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
handleFormSubmit(event) {
|
|
event.preventDefault();
|
|
const formData = new FormData(event.target);
|
|
this.submitPrinterForm(formData);
|
|
}
|
|
|
|
refreshPrinters() {
|
|
console.log('Drucker werden aktualisiert...');
|
|
this.loadPrinters();
|
|
}
|
|
|
|
async loadPrinters() {
|
|
try {
|
|
this.showLoadingState();
|
|
|
|
// Nutze das neue PrinterMonitor-System für Live-Status
|
|
if (window.printerMonitor) {
|
|
// Registriere Update-Callback für Live-Daten
|
|
window.printerMonitor.onUpdate((data) => {
|
|
if (data.type === 'update') {
|
|
// Konvertiere Map zu Array für kompatibilität
|
|
allPrinters = Array.from(data.printers.values());
|
|
console.log(`${allPrinters.length} Drucker mit Live-Status geladen:`, allPrinters);
|
|
this.applyFilters();
|
|
this.updateStatistics();
|
|
this.populateFilterDropdowns();
|
|
this.updateLastUpdateTime();
|
|
this.hideLoadingState();
|
|
} else if (data.type === 'error') {
|
|
console.error('PrinterMonitor Fehler:', data.message);
|
|
this.showError('Live-Status nicht verfügbar: ' + data.message);
|
|
// Fallback zu normaler API
|
|
this.loadPrintersFromAPI();
|
|
}
|
|
});
|
|
|
|
// Force update für sofortige Daten
|
|
await window.printerMonitor.forceUpdate();
|
|
} else {
|
|
// Fallback falls PrinterMonitor nicht verfügbar
|
|
this.loadPrintersFromAPI();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in loadPrinters:', error);
|
|
// Fallback zu normaler API
|
|
this.loadPrintersFromAPI();
|
|
}
|
|
}
|
|
|
|
async loadPrintersFromAPI() {
|
|
try {
|
|
const response = await fetch('/api/printers');
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.printers) {
|
|
allPrinters = data.printers || [];
|
|
console.log(`${allPrinters.length} Drucker über API geladen:`, allPrinters);
|
|
this.applyFilters();
|
|
this.updateStatistics();
|
|
this.populateFilterDropdowns();
|
|
this.updateLastUpdateTime();
|
|
} else {
|
|
console.error('Fehler beim Laden der Drucker:', data);
|
|
this.showError('Fehler beim Laden der Drucker: ' + (data.error || data.message || 'Unbekannter Fehler'));
|
|
allPrinters = [];
|
|
this.applyFilters();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading printers from API:', error);
|
|
this.showError('Netzwerkfehler beim Laden der Drucker: ' + error.message);
|
|
allPrinters = [];
|
|
this.applyFilters();
|
|
} 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.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('');
|
|
}
|
|
}
|
|
|
|
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-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>
|
|
|
|
${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="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>
|
|
<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>
|
|
<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 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>
|
|
</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>
|
|
`;
|
|
}
|
|
}
|
|
|
|
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'
|
|
},
|
|
'standby': {
|
|
container: 'status-standby',
|
|
iconBg: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600',
|
|
badge: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400'
|
|
},
|
|
'unreachable': {
|
|
container: 'status-unreachable',
|
|
iconBg: 'bg-gray-100 dark:bg-gray-900/30 text-gray-600',
|
|
badge: 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400'
|
|
},
|
|
'unconfigured': {
|
|
container: 'status-unconfigured',
|
|
iconBg: 'bg-indigo-100 dark:bg-indigo-900/30 text-indigo-600',
|
|
badge: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-400'
|
|
}
|
|
};
|
|
return classes[status] || classes['offline'];
|
|
}
|
|
|
|
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>',
|
|
'standby': '<div class="w-3 h-3 bg-yellow-500 rounded-full"></div>',
|
|
'unreachable': '<div class="w-3 h-3 bg-gray-500 rounded-full"></div>',
|
|
'unconfigured': '<div class="w-3 h-3 bg-indigo-500 rounded-full"></div>'
|
|
};
|
|
return icons[status] || icons['offline'];
|
|
}
|
|
|
|
getStatusText(status) {
|
|
const texts = {
|
|
'online': 'Online',
|
|
'offline': 'Offline',
|
|
'printing': 'Druckt',
|
|
'error': 'Fehler',
|
|
'maintenance': 'Wartung',
|
|
'idle': 'Bereit',
|
|
'standby': 'Standby',
|
|
'unreachable': 'Unerreichbar',
|
|
'unconfigured': 'Nicht konfiguriert'
|
|
};
|
|
return texts[status] || status;
|
|
}
|
|
|
|
updateStatistics() {
|
|
const stats = {
|
|
total: allPrinters.length,
|
|
online: allPrinters.filter(p => ['online', 'idle', 'standby'].includes(p.status)).length,
|
|
offline: allPrinters.filter(p => ['offline', 'unreachable'].includes(p.status)).length,
|
|
printing: allPrinters.filter(p => p.status === 'printing').length,
|
|
standby: allPrinters.filter(p => p.status === 'standby').length,
|
|
unreachable: allPrinters.filter(p => p.status === 'unreachable').length,
|
|
unconfigured: allPrinters.filter(p => p.status === 'unconfigured').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);
|
|
|
|
// Zeige detaillierte Status-Info in der Konsole
|
|
console.log('📊 Status-Verteilung:', {
|
|
'Online (inkl. Standby)': stats.online,
|
|
'Standby': stats.standby,
|
|
'Offline/Unerreichbar': stats.offline,
|
|
'Druckt aktiv': stats.printing,
|
|
'Unerreichbar': stats.unreachable,
|
|
'Unkonfiguriert': stats.unconfigured,
|
|
'Gesamt': stats.total
|
|
});
|
|
|
|
this.updatePerformanceMetrics(stats);
|
|
}
|
|
|
|
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 = '--:--';
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 => {
|
|
this.closeModal(modalId);
|
|
});
|
|
}
|
|
|
|
closeModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
// Modal-Funktionen
|
|
openAddPrinterModal() {
|
|
console.log('Drucker hinzufügen Modal wird geöffnet');
|
|
this.resetPrinterForm();
|
|
document.getElementById('printerModalTitle').textContent = 'Drucker hinzufügen';
|
|
document.getElementById('deletePrinterBtn').style.display = 'none';
|
|
this.showModal('printerModal');
|
|
}
|
|
|
|
editPrinter(printerId) {
|
|
console.log('Drucker bearbeiten:', printerId);
|
|
const printer = allPrinters.find(p => p.id == printerId);
|
|
if (printer) {
|
|
this.populatePrinterForm(printer);
|
|
document.getElementById('printerModalTitle').textContent = 'Drucker bearbeiten';
|
|
document.getElementById('deletePrinterBtn').style.display = 'block';
|
|
this.showModal('printerModal');
|
|
}
|
|
}
|
|
|
|
showModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
const content = modal.querySelector('.mercedes-modal');
|
|
|
|
modal.classList.remove('hidden');
|
|
|
|
// Animation
|
|
setTimeout(() => {
|
|
content.classList.remove('scale-95', 'opacity-0');
|
|
content.classList.add('scale-100', 'opacity-100');
|
|
}, 10);
|
|
}
|
|
|
|
resetPrinterForm() {
|
|
document.getElementById('printerForm').reset();
|
|
document.getElementById('printerId').value = '';
|
|
document.getElementById('customModelDiv').classList.add('hidden');
|
|
document.getElementById('connectionTest').classList.add('hidden');
|
|
}
|
|
|
|
populatePrinterForm(printer) {
|
|
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 || printer.plug_ip || '';
|
|
|
|
// Zeige Connection Test für bestehende Drucker
|
|
document.getElementById('connectionTest').classList.remove('hidden');
|
|
}
|
|
|
|
async submitPrinterForm(formData) {
|
|
try {
|
|
const printerId = document.getElementById('printerId').value;
|
|
const isEdit = printerId !== '';
|
|
|
|
const url = isEdit ? `/api/printers/${printerId}` : '/api/printers';
|
|
const method = isEdit ? 'PUT' : 'POST';
|
|
|
|
const response = await fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': this.getCSRFToken()
|
|
},
|
|
body: JSON.stringify({
|
|
name: formData.get('name'),
|
|
model: formData.get('model') === 'Custom' ? formData.get('customModel') : formData.get('model'),
|
|
location: formData.get('location'),
|
|
plug_ip: formData.get('ip_address'),
|
|
active: formData.get('active') === 'on'
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok) {
|
|
this.showSuccess(isEdit ? 'Drucker erfolgreich aktualisiert' : 'Drucker erfolgreich hinzugefügt');
|
|
this.closeModal('printerModal');
|
|
await this.loadPrinters(); // Drucker-Liste neu laden
|
|
} else {
|
|
this.showError('Fehler beim Speichern: ' + (result.error || result.message));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error submitting printer form:', error);
|
|
this.showError('Netzwerkfehler beim Speichern des Druckers');
|
|
}
|
|
}
|
|
|
|
getCSRFToken() {
|
|
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
|
}
|
|
|
|
// Weitere Modal-Funktionen
|
|
openPrinterDetails(printerId) {
|
|
console.log('Drucker Details öffnen für ID:', printerId);
|
|
const printer = allPrinters.find(p => p.id == printerId);
|
|
if (printer) {
|
|
document.getElementById('printerDetailsTitle').textContent = `${printer.name} - Details`;
|
|
this.loadPrinterDetails(printer);
|
|
this.showModal('printerDetailsModal');
|
|
}
|
|
}
|
|
|
|
loadPrinterDetails(printer) {
|
|
// Hier würden die Detail-Informationen geladen werden
|
|
console.log('Lade Details für Drucker:', printer);
|
|
// TODO: Implementiere Detail-Ansicht
|
|
}
|
|
|
|
// Performance Monitoring initialisieren
|
|
initializePerformanceMonitoring() {
|
|
// Performance-Metriken für Drucker-Dashboard
|
|
this.performanceMetrics = {
|
|
loadTime: 0,
|
|
renderTime: 0,
|
|
filterTime: 0,
|
|
lastUpdate: new Date()
|
|
};
|
|
|
|
// Start der Performance-Messung
|
|
this.performanceStart = performance.now();
|
|
console.log('📊 Performance-Monitoring für Drucker-Manager aktiviert');
|
|
|
|
// Performance-Dashboard initialisieren falls vorhanden
|
|
if (typeof window.performanceDashboard !== 'undefined') {
|
|
window.performanceDashboard.registerComponent('printers', this.performanceMetrics);
|
|
}
|
|
}
|
|
|
|
// Test-Print Funktion
|
|
async testPrint(printerId) {
|
|
console.log(`🖨️ Test-Druck wird gestartet für Drucker ${printerId}`);
|
|
|
|
try {
|
|
const printer = allPrinters.find(p => p.id == printerId);
|
|
if (!printer) {
|
|
this.showError('Drucker nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
// Bestätigungsdialog
|
|
const confirmed = confirm(`Test-Druck für "${printer.name}" starten?\n\nDies schaltet die Steckdose ein und führt einen Probe-Druckauftrag aus.`);
|
|
if (!confirmed) return;
|
|
|
|
// Test-Print über API starten
|
|
const response = await fetch(`/api/printers/${printerId}/test-print`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': this.getCSRFToken()
|
|
},
|
|
body: JSON.stringify({
|
|
test_type: 'probe_print',
|
|
reason: 'UI-Test durch Admin'
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
this.showSuccess(`Test-Druck für "${printer.name}" erfolgreich gestartet`);
|
|
// Status sofort aktualisieren
|
|
await this.loadPrinters();
|
|
} else {
|
|
this.showError(`Test-Druck fehlgeschlagen: ${result.error || 'Unbekannter Fehler'}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Test-Druck:', error);
|
|
this.showError(`Netzwerkfehler beim Test-Druck: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Drucker pausieren
|
|
async pausePrint(printerId) {
|
|
console.log(`⏸️ Druckauftrag wird pausiert für Drucker ${printerId}`);
|
|
|
|
try {
|
|
const response = await fetch(`/api/printers/${printerId}/pause`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': this.getCSRFToken()
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
this.showSuccess('Druckauftrag pausiert');
|
|
await this.loadPrinters();
|
|
} else {
|
|
this.showError(`Pausieren fehlgeschlagen: ${result.error || 'Unbekannter Fehler'}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Pausieren:', error);
|
|
this.showError(`Netzwerkfehler: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Drucker zurücksetzen
|
|
async resetPrinter(printerId) {
|
|
console.log(`🔄 Drucker wird zurückgesetzt: ${printerId}`);
|
|
|
|
try {
|
|
const printer = allPrinters.find(p => p.id == printerId);
|
|
if (!printer) {
|
|
this.showError('Drucker nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
const confirmed = confirm(`Drucker "${printer.name}" zurücksetzen?\n\nDies beendet alle laufenden Jobs und startet den Drucker neu.`);
|
|
if (!confirmed) return;
|
|
|
|
const response = await fetch(`/api/printers/${printerId}/reset`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': this.getCSRFToken()
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
this.showSuccess('Drucker erfolgreich zurückgesetzt');
|
|
await this.loadPrinters();
|
|
} else {
|
|
this.showError(`Reset fehlgeschlagen: ${result.error || 'Unbekannter Fehler'}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Reset:', error);
|
|
this.showError(`Netzwerkfehler: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Drucker verbinden
|
|
async connectPrinter(printerId) {
|
|
console.log(`🔗 Verbindung wird hergestellt zu Drucker ${printerId}`);
|
|
|
|
try {
|
|
const response = await fetch(`/api/printers/${printerId}/connect`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': this.getCSRFToken()
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
this.showSuccess('Verbindung hergestellt');
|
|
await this.loadPrinters();
|
|
} else {
|
|
this.showError(`Verbindung fehlgeschlagen: ${result.error || 'Unbekannter Fehler'}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Verbinden:', error);
|
|
this.showError(`Netzwerkfehler: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize Printer Manager
|
|
printerManager = new PrinterManager();
|
|
|
|
// Global functions for UI interactions
|
|
function refreshPrinters() {
|
|
const button = document.getElementById('refresh-button');
|
|
if (button) {
|
|
button.disabled = true;
|
|
const svg = button.querySelector('svg');
|
|
if (svg) svg.classList.add('animate-spin');
|
|
}
|
|
|
|
printerManager.loadPrinters().finally(() => {
|
|
if (button) {
|
|
button.disabled = false;
|
|
const svg = button.querySelector('svg');
|
|
if (svg) svg.classList.remove('animate-spin');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Globale testPrinterConnection Funktion für Kompatibilität
|
|
function testPrinterConnection() {
|
|
console.log('🔗 Test-Verbindung wird gestartet...');
|
|
const printerId = document.getElementById('printerId')?.value;
|
|
|
|
if (!printerId) {
|
|
alert('Fehler: Keine Drucker-ID gefunden');
|
|
return;
|
|
}
|
|
|
|
// Verwende printerManager falls verfügbar
|
|
if (window.printerManager) {
|
|
printerManager.testPrint(printerId);
|
|
} else {
|
|
alert('Drucker-Manager nicht verfügbar');
|
|
}
|
|
}
|
|
|
|
function toggleAutoRefresh() {
|
|
isAutoRefreshEnabled = !isAutoRefreshEnabled;
|
|
const button = document.getElementById('auto-refresh-toggle');
|
|
|
|
if (isAutoRefreshEnabled) {
|
|
if (button) {
|
|
button.textContent = 'Auto-Refresh: ON';
|
|
button.classList.remove('bg-gray-500');
|
|
button.classList.add('bg-mercedes-blue');
|
|
}
|
|
printerManager.startAutoRefresh();
|
|
} else {
|
|
if (button) {
|
|
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) {
|
|
button.classList.add('bg-orange-500', 'text-white');
|
|
const span = button.querySelector('span');
|
|
if (span) span.textContent = 'Wartung aktiv';
|
|
} else if (button) {
|
|
button.classList.remove('bg-orange-500', 'text-white');
|
|
const span = button.querySelector('span');
|
|
if (span) 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() {
|
|
printerManager.closeModal('printerModal');
|
|
}
|
|
|
|
function closePrinterDetailsModal() {
|
|
printerManager.closeModal('printerDetailsModal');
|
|
}
|
|
|
|
// Initialisierung beim Laden der Seite
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🚀 Mercedes-Benz MYP Printer Management System wird initialisiert...');
|
|
printerManager = new PrinterManager();
|
|
|
|
// Add Printer Button Event Listener
|
|
const addPrinterBtn = document.getElementById('add-printer-btn');
|
|
if (addPrinterBtn) {
|
|
addPrinterBtn.addEventListener('click', openAddPrinterModal);
|
|
}
|
|
|
|
// Weitere Buttons im Header
|
|
const buttons = document.querySelectorAll('[onclick*="openAddPrinterModal"]');
|
|
buttons.forEach(btn => {
|
|
btn.addEventListener('click', openAddPrinterModal);
|
|
});
|
|
|
|
// PrinterMonitor für Live-Status initialisieren
|
|
if (window.printerMonitor) {
|
|
console.log('🖨️ Starte PrinterMonitor für Live-Status-Updates...');
|
|
window.printerMonitor.start();
|
|
|
|
// Zusätzlicher Callback für Status-Updates
|
|
window.printerMonitor.onUpdate((data) => {
|
|
if (data.type === 'update' && data.summary) {
|
|
// Update header statistics
|
|
document.getElementById('online-count').textContent = data.summary.online || 0;
|
|
document.getElementById('total-count').textContent = data.summary.total || 0;
|
|
|
|
// Visual feedback für Live-Updates
|
|
const statusIndicator = document.getElementById('live-status-indicator');
|
|
if (statusIndicator) {
|
|
statusIndicator.classList.add('animate-pulse', 'text-green-500');
|
|
setTimeout(() => {
|
|
statusIndicator.classList.remove('animate-pulse');
|
|
}, 1000);
|
|
}
|
|
|
|
console.log('📊 Live-Status-Update:', data.summary);
|
|
}
|
|
});
|
|
} else {
|
|
console.warn('⚠️ PrinterMonitor nicht verfügbar - verwende Standard-Updates');
|
|
}
|
|
|
|
console.log('✅ Printer Management System erfolgreich initialisiert');
|
|
});
|
|
|
|
function getStatusSummary() {
|
|
const summary = {
|
|
total: printers.size,
|
|
online: 0,
|
|
offline: 0,
|
|
printing: 0, // Neuer Status: Drucker druckt gerade
|
|
standby: 0,
|
|
unreachable: 0,
|
|
unconfigured: 0,
|
|
error: 0 // Status für unbekannte Fehler
|
|
};
|
|
|
|
printers.forEach(printer => {
|
|
const status = printer.status;
|
|
if (summary.hasOwnProperty(status)) {
|
|
summary[status]++;
|
|
} else {
|
|
// Fallback für unbekannte Status
|
|
summary.offline++;
|
|
}
|
|
});
|
|
|
|
return summary;
|
|
}
|
|
</script>
|
|
{% endblock %} |