- Dark-Mode JavaScript Optimierungen für bessere Performance - Base Template erweitert mit Enhanced UI Components - Dashboard Template modernisiert mit neuen Card-Layouts - Hardware Integration massiv konsolidiert (1771 Zeilen reduziert) - Drucker Steuerung Blueprint hinzugefügt - Legacy Hardware Integration Files bereinigt - System-Architektur vereinfacht und performanter Major Changes: - -2001 Zeilen Code durch Konsolidierung - +451 Zeilen neue optimierte Implementierung - Vollständige Template-System Überarbeitung 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
483 lines
19 KiB
HTML
483 lines
19 KiB
HTML
{% extends "base.html" %}
|
|
{% from 'macros/ui_components.html' import status_indicator, progress_bar, printer_card, clickable_card, dashboard_stat_card, printer_overview_table, filter_tabs %}
|
|
|
|
{% block title %}Dashboard - Mercedes-Benz MYP Platform{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* Enhanced Dashboard Styles using Unified CSS Variables */
|
|
.mb-dashboard-card {
|
|
background: var(--gradient-card);
|
|
border: 1px solid var(--border-primary);
|
|
border-radius: 16px;
|
|
box-shadow: var(--shadow-card);
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.mb-dashboard-card:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: var(--shadow-card-hover);
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.mb-stat-card {
|
|
background: var(--gradient-card);
|
|
color: var(--text-primary);
|
|
position: relative;
|
|
overflow: hidden;
|
|
border: 1px solid var(--border-primary);
|
|
box-shadow: var(--shadow-card);
|
|
}
|
|
|
|
.mb-stat-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
right: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: radial-gradient(circle, rgba(0, 114, 206, 0.1) 0%, transparent 70%);
|
|
animation: pulse-glow 4s ease-in-out infinite;
|
|
}
|
|
|
|
.dark .mb-stat-card::before {
|
|
background: radial-gradient(circle, rgba(255, 255, 255, 0.05) 0%, transparent 70%); /* Subtle glow for dark */
|
|
}
|
|
|
|
@keyframes pulse-glow {
|
|
0%, 100% { opacity: 0.5; transform: scale(1); }
|
|
50% { opacity: 0.8; transform: scale(1.05); }
|
|
}
|
|
|
|
.mb-stat-icon {
|
|
background: rgba(0, 0, 0, 0.1); /* Schwarz statt Blau */
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
z-index: 10;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .mb-stat-icon {
|
|
background: rgba(255, 255, 255, 0.1); /* Lighter icon background for contrast */
|
|
}
|
|
|
|
.mb-stat-card:hover .mb-stat-icon {
|
|
transform: scale(1.1);
|
|
background: rgba(0, 0, 0, 0.2); /* Schwarz statt Blau */
|
|
}
|
|
|
|
.dark .mb-stat-card:hover .mb-stat-icon {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
.mb-status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
position: relative;
|
|
display: inline-block;
|
|
}
|
|
|
|
.mb-status-indicator::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 50%;
|
|
animation: status-pulse 2s infinite;
|
|
}
|
|
|
|
.mb-status-online {
|
|
background: #10b981;
|
|
}
|
|
|
|
.mb-status-online::after {
|
|
background: #10b981;
|
|
}
|
|
|
|
.mb-status-busy {
|
|
background: #f59e0b;
|
|
}
|
|
|
|
.mb-status-busy::after {
|
|
background: #f59e0b;
|
|
}
|
|
|
|
.mb-status-offline {
|
|
background: #ef4444;
|
|
}
|
|
|
|
.mb-status-offline::after {
|
|
background: #ef4444;
|
|
}
|
|
|
|
.mb-status-idle {
|
|
background: #6b7280;
|
|
}
|
|
|
|
.mb-status-idle::after {
|
|
background: #6b7280;
|
|
}
|
|
|
|
@keyframes status-pulse {
|
|
0% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.5); opacity: 0.5; }
|
|
100% { transform: scale(2); opacity: 0; }
|
|
}
|
|
|
|
.mb-progress-container {
|
|
background: #f3f4f6;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.dark .mb-progress-container {
|
|
background: #374151;
|
|
}
|
|
|
|
.mb-progress-bar {
|
|
background: linear-gradient(90deg, #000000 0%, #333333 100%); /* Schwarz statt Blau */
|
|
height: 100%;
|
|
border-radius: 4px;
|
|
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.dark .mb-progress-bar {
|
|
background: linear-gradient(90deg, #f3f4f6 0%, #e5e7eb 100%);
|
|
}
|
|
|
|
.mb-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%); }
|
|
}
|
|
|
|
.mb-activity-item {
|
|
border-left: 4px solid #000000; /* Schwarz statt Blau */
|
|
background: linear-gradient(90deg, rgba(0, 0, 0, 0.05) 0%, transparent 100%); /* Schwarz statt Blau */
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
border-radius: 0 8px 8px 0;
|
|
}
|
|
|
|
.dark .mb-activity-item {
|
|
border-left-color: #f3f4f6;
|
|
background: linear-gradient(90deg, rgba(255, 255, 255, 0.02) 0%, transparent 100%);
|
|
}
|
|
|
|
/* Light/Dark Mode compatible cards */
|
|
.glass-card-light {
|
|
background: rgba(255, 255, 255, 0.7);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
border: 1px solid rgba(229, 231, 235, 0.8);
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .glass-card-light {
|
|
background: rgba(0, 0, 0, 0.8);
|
|
border-color: rgba(100, 116, 139, 0.3);
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.section-title {
|
|
color: #1e293b;
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.dark .section-title {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.stat-value {
|
|
color: #0f172a;
|
|
font-weight: 700;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.dark .stat-value {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #64748b;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.dark .stat-label {
|
|
color: #94a3b8;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-8">
|
|
<!-- Dashboard Header Card -->
|
|
<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">
|
|
<svg class="w-full h-full text-slate-900 dark:text-white" fill="currentColor" viewBox="0 0 80 80" aria-hidden="true">
|
|
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
|
|
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
|
|
c0-13.1,7-24.5,17.5-30.9v0C26.6,6,32.5,4.2,39,4l-4.5,32.7L21.5,46.8v0L8.3,57.1C5.6,52,4,46.2,4,40z M58.6,70.8
|
|
C53.1,74.1,46.8,76,40,76c-6.8,0-13.2-1.9-18.6-5.2c-4.9-2.9-8.9-6.9-11.9-11.7l11.9-4.9v0L40,46.6l18.6,7.5v0l12,4.9
|
|
C67.6,63.9,63.4,67.9,58.6,70.8z M58.6,46.8L58.6,46.8l-12.9-10L41.1,4c6.3,0.2,12.3,2,17.4,5.1v0C69,15.4,76,26.9,76,40
|
|
c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-slate-900 dark:text-white tracking-tight">Dashboard</h1>
|
|
<p class="text-slate-500 dark:text-slate-400 mt-1">Übersicht über Ihre 3D-Druck Aktivitäten</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap gap-3">
|
|
<a href="{{ url_for('dashboard') }}"
|
|
class="btn-secondary flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
<span>Aktualisieren</span>
|
|
</a>
|
|
<a href="{{ url_for('jobs_page') }}"
|
|
class="btn-primary flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
</svg>
|
|
<span>Neuer Auftrag</span>
|
|
</a>
|
|
<a href="{{ url_for('guest.guest_requests_overview') }}"
|
|
class="btn-secondary flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
</svg>
|
|
<span>Anträge Übersicht</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Overview Cards mit Jinja-Macros -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{{ dashboard_stat_card(
|
|
title="Aktive Aufträge",
|
|
value=active_jobs_count,
|
|
icon="fas fa-clipboard-list",
|
|
color="slate",
|
|
trend=active_jobs_trend if active_jobs_trend else None
|
|
) }}
|
|
|
|
{{ dashboard_stat_card(
|
|
title="Verfügbare Drucker",
|
|
value=available_printers_count,
|
|
icon="fas fa-print",
|
|
color="green",
|
|
trend=printer_availability_trend if printer_availability_trend else None
|
|
) }}
|
|
|
|
{{ dashboard_stat_card(
|
|
title="Aufträge (gesamt)",
|
|
value=total_jobs_count,
|
|
icon="fas fa-chart-line",
|
|
color="purple",
|
|
trend=total_jobs_trend if total_jobs_trend else None
|
|
) }}
|
|
|
|
{{ dashboard_stat_card(
|
|
title="Erfolgsrate",
|
|
value=success_rate ~ "%",
|
|
icon="fas fa-star",
|
|
color="amber",
|
|
trend=success_rate_trend if success_rate_trend else None
|
|
) }}
|
|
</div>
|
|
|
|
<!-- Active Jobs Section -->
|
|
<div class="dashboard-card p-6">
|
|
<h2 class="section-title">Aktuelle Druckaufträge</h2>
|
|
<div class="overflow-hidden">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-slate-700">
|
|
<thead>
|
|
<tr class="text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">
|
|
<th class="px-6 py-3">Status</th>
|
|
<th class="px-6 py-3">Auftrag</th>
|
|
<th class="px-6 py-3">Drucker</th>
|
|
<th class="px-6 py-3">Startzeit</th>
|
|
<th class="px-6 py-3">Fortschritt</th>
|
|
<th class="px-6 py-3">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-200 dark:divide-slate-700">
|
|
{% if active_jobs and active_jobs|length > 0 %}
|
|
{% for job in active_jobs %}
|
|
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/50">
|
|
<td colspan="6" class="p-0">
|
|
<a href="{{ url_for('jobs_page') }}#job-{{ job.id }}" class="block px-6 py-4">
|
|
<div class="grid grid-cols-6 gap-4">
|
|
<div class="col-span-1">
|
|
{{ status_indicator(job.status, job.status_text) }}
|
|
</div>
|
|
<div class="col-span-1">
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">{{ job.name }}</div>
|
|
<div class="text-xs text-slate-500 dark:text-slate-400">{{ job.file_name }}</div>
|
|
</div>
|
|
<div class="col-span-1">
|
|
<div class="text-sm text-slate-700 dark:text-slate-300">{{ job.printer }}</div>
|
|
</div>
|
|
<div class="col-span-1">
|
|
<div class="text-sm text-slate-700 dark:text-slate-300">{{ job.start_time }}</div>
|
|
</div>
|
|
<div class="col-span-1">
|
|
{{ progress_bar(job.progress) }}
|
|
</div>
|
|
<div class="col-span-1 text-right">
|
|
<span class="text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-300 font-medium">Details</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="6" class="px-6 py-8 text-center">
|
|
<div class="text-slate-500 dark:text-slate-400">
|
|
<svg class="w-12 h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
|
</svg>
|
|
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1">Keine aktiven Druckaufträge</p>
|
|
<p class="text-slate-500 dark:text-slate-400 text-sm">Starten Sie einen neuen Druckauftrag, um ihn hier zu sehen.</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="mt-4 text-right">
|
|
<a href="{{ url_for('jobs_page') }}" class="text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-300 text-sm font-medium">
|
|
Alle Druckaufträge anzeigen →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Last Row: Printer Status and Recent Activity -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<!-- Available Printers -->
|
|
<div class="dashboard-card p-6">
|
|
<h2 class="section-title">Druckerstatus</h2>
|
|
<div class="space-y-4">
|
|
{% if printers and printers|length > 0 %}
|
|
{% for printer in printers %}
|
|
{{ printer_card(printer) }}
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<svg class="w-12 h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" 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>
|
|
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1">Keine Drucker gefunden</p>
|
|
<p class="text-slate-500 dark:text-slate-400 text-sm">Prüfen Sie die Verbindung oder fügen Sie Drucker hinzu.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mt-4 text-right">
|
|
<a href="{{ url_for('printers_page') }}" class="text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-slate-300 text-sm font-medium">
|
|
Alle Drucker anzeigen →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="dashboard-card p-6">
|
|
<h2 class="section-title">Letzte Aktivitäten</h2>
|
|
<div class="space-y-3">
|
|
{% if activities and activities|length > 0 %}
|
|
{% for activity in activities %}
|
|
<div class="mb-activity-item pl-4 py-3">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex-1">
|
|
<p class="text-sm text-slate-700 dark:text-slate-300">{{ activity.description }}</p>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">{{ activity.time }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<svg class="w-12 h-12 mx-auto mb-3 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<p class="text-slate-700 dark:text-slate-300 font-medium mb-1">Keine Aktivitäten</p>
|
|
<p class="text-slate-500 dark:text-slate-400 text-sm">Ihre Aktivitäten werden hier angezeigt.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<!-- Auto-Refresh über Jinja-Macro -->
|
|
{% if config.get('AUTO_REFRESH_DASHBOARD', False) %}
|
|
{{ auto_refresh(60) }}
|
|
{% endif %}
|
|
|
|
<!-- Minimal CSS für Interaktivität ohne JavaScript -->
|
|
<style>
|
|
/* CSS-basierte Hover-Effekte für Dashboard-Karten */
|
|
.dashboard-card {
|
|
transition: transform 0.2s ease-in-out;
|
|
}
|
|
|
|
.dashboard-card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* Klickbare Drucker-Karten mit CSS-Cursor */
|
|
[data-printer-id] {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Klickbare Job-Zeilen */
|
|
tbody tr[data-job-id] {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Zebra-Streifen für bessere Lesbarkeit */
|
|
tbody tr:nth-child(even) {
|
|
background-color: rgba(0, 0, 0, 0.02);
|
|
}
|
|
|
|
.dark tbody tr:nth-child(even) {
|
|
background-color: rgba(255, 255, 255, 0.02);
|
|
}
|
|
</style>
|
|
{% endblock %} |