feat: Major updates to backend structure and security enhancements
- Removed `COMMON_ERRORS.md` file to streamline documentation. - Added `Flask-Limiter` for rate limiting and `redis` for session management in `requirements.txt`. - Expanded `ROADMAP.md` to include completed security features and planned enhancements for version 2.2. - Enhanced `setup_myp.sh` for ultra-secure kiosk installation, including system hardening and security configurations. - Updated `app.py` to integrate CSRF protection and improved logging setup. - Refactored user model to include username and active status for better user management. - Improved job scheduler with uptime tracking and task management features. - Updated various templates for a more cohesive user interface and experience.
This commit is contained in:
@@ -1,503 +1,293 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" class="h-full">
|
||||
<html lang="de" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}MYP - Mercedes 3D Printing Platform{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0">
|
||||
<meta name="description" content="MYP Platform - Mercedes-Benz 3D Druck Management System">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<!-- Dynamic theme-color meta tags for browser UI -->
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000">
|
||||
<meta name="theme-color" id="metaThemeColor" content="#000000">
|
||||
<!-- CSRF-Token für Formulare -->
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<!-- Lokale Tailwind CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.min.css') }}">
|
||||
<!-- Title -->
|
||||
<title>{% block title %}MYP Platform - Mercedes-Benz{% endblock %}</title>
|
||||
|
||||
<!-- Custom CSS für Mercedes Farben -->
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/apple-touch-icon.png') }}">
|
||||
|
||||
<!-- CSS -->
|
||||
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- Preload critical resources -->
|
||||
<link rel="preload" href="{{ url_for('static', filename='js/ui-components.js') }}" as="script">
|
||||
<link rel="preload" href="{{ url_for('static', filename='js/offline-app.js') }}" as="script">
|
||||
|
||||
<!-- Additional CSS -->
|
||||
{% block extra_css %}{% endblock %}
|
||||
|
||||
<!-- Dark Mode Script (must be in head to prevent flash) -->
|
||||
<script>
|
||||
// Temporär für sofortige Anwendung ohne Flackern
|
||||
document.documentElement.classList.add('disable-transitions');
|
||||
|
||||
const STORAGE_KEY = 'myp-dark-mode';
|
||||
const savedMode = localStorage.getItem(STORAGE_KEY);
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDark = savedMode === 'true' || (savedMode === null && prefersDark);
|
||||
|
||||
// Dark Mode sofort anwenden
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.documentElement.style.colorScheme = 'dark';
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
document.documentElement.style.colorScheme = 'light';
|
||||
}
|
||||
|
||||
// ThemeColor Meta-Tag aktualisieren
|
||||
const metaThemeColor = document.getElementById('metaThemeColor');
|
||||
if (metaThemeColor) {
|
||||
metaThemeColor.setAttribute('content', isDark ? '#000000' : '#ffffff');
|
||||
}
|
||||
|
||||
// Übergänge nach kurzer Verzögerung wieder aktivieren
|
||||
setTimeout(function() {
|
||||
document.documentElement.classList.remove('disable-transitions');
|
||||
}, 10);
|
||||
</script>
|
||||
|
||||
<!-- Disable Transitions Styling -->
|
||||
<style>
|
||||
:root {
|
||||
--mercedes-silver: #C0C0C0;
|
||||
--mercedes-dark-gray: #2D2D2D;
|
||||
--mercedes-light-gray: #F5F5F5;
|
||||
--mercedes-blue: #0066CC;
|
||||
--mercedes-green: #00B04F;
|
||||
--mercedes-red: #E60012;
|
||||
--mercedes-yellow: #FFD700;
|
||||
--mercedes-black: #000000;
|
||||
--mercedes-white: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Mercedes Color Classes */
|
||||
.bg-mercedes-silver { background-color: var(--mercedes-silver); }
|
||||
.bg-mercedes-dark-gray { background-color: var(--mercedes-dark-gray); }
|
||||
.bg-mercedes-light-gray { background-color: var(--mercedes-light-gray); }
|
||||
.bg-mercedes-blue { background-color: var(--mercedes-blue); }
|
||||
.bg-mercedes-green { background-color: var(--mercedes-green); }
|
||||
.bg-mercedes-red { background-color: var(--mercedes-red); }
|
||||
.bg-mercedes-yellow { background-color: var(--mercedes-yellow); }
|
||||
.bg-mercedes-black { background-color: var(--mercedes-black); }
|
||||
.bg-mercedes-white { background-color: var(--mercedes-white); }
|
||||
|
||||
.text-mercedes-silver { color: var(--mercedes-silver); }
|
||||
.text-mercedes-dark-gray { color: var(--mercedes-dark-gray); }
|
||||
.text-mercedes-light-gray { color: var(--mercedes-light-gray); }
|
||||
.text-mercedes-blue { color: var(--mercedes-blue); }
|
||||
.text-mercedes-green { color: var(--mercedes-green); }
|
||||
.text-mercedes-red { color: var(--mercedes-red); }
|
||||
.text-mercedes-yellow { color: var(--mercedes-yellow); }
|
||||
.text-mercedes-black { color: var(--mercedes-black); }
|
||||
.text-mercedes-white { color: var(--mercedes-white); }
|
||||
|
||||
.border-mercedes-silver { border-color: var(--mercedes-silver); }
|
||||
.border-mercedes-dark-gray { border-color: var(--mercedes-dark-gray); }
|
||||
.border-mercedes-light-gray { border-color: var(--mercedes-light-gray); }
|
||||
.border-mercedes-blue { border-color: var(--mercedes-blue); }
|
||||
.border-mercedes-green { border-color: var(--mercedes-green); }
|
||||
.border-mercedes-red { border-color: var(--mercedes-red); }
|
||||
.border-mercedes-yellow { border-color: var(--mercedes-yellow); }
|
||||
.border-mercedes-black { border-color: var(--mercedes-black); }
|
||||
.border-mercedes-white { border-color: var(--mercedes-white); }
|
||||
|
||||
/* Mercedes Gradient */
|
||||
.mercedes-gradient {
|
||||
background: linear-gradient(135deg, var(--mercedes-black) 0%, var(--mercedes-dark-gray) 100%);
|
||||
}
|
||||
|
||||
/* Mercedes Shadow */
|
||||
.mercedes-shadow {
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.mercedes-shadow-lg {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Mercedes Button */
|
||||
.mercedes-button {
|
||||
transition: all 0.2s ease-in-out;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.mercedes-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.mercedes-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Mercedes Card */
|
||||
.mercedes-card {
|
||||
background: var(--mercedes-white);
|
||||
border: 1px solid var(--mercedes-light-gray);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.mercedes-card:hover {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Mercedes Input */
|
||||
.mercedes-input {
|
||||
border: 2px solid var(--mercedes-light-gray);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
transition: all 0.2s ease-in-out;
|
||||
background: var(--mercedes-white);
|
||||
}
|
||||
|
||||
.mercedes-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--mercedes-blue);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
|
||||
}
|
||||
|
||||
/* Mercedes Table */
|
||||
.mercedes-table {
|
||||
background: var(--mercedes-white);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mercedes-table th {
|
||||
background: var(--mercedes-dark-gray);
|
||||
color: var(--mercedes-white);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.mercedes-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid var(--mercedes-light-gray);
|
||||
}
|
||||
|
||||
.mercedes-table tr:hover {
|
||||
background: var(--mercedes-light-gray);
|
||||
}
|
||||
|
||||
/* Mercedes Modal */
|
||||
.mercedes-modal {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.mercedes-modal-content {
|
||||
background: var(--mercedes-white);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Mercedes Progress Bar */
|
||||
.mercedes-progress {
|
||||
background: var(--mercedes-light-gray);
|
||||
border-radius: 9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mercedes-progress-bar {
|
||||
background: linear-gradient(90deg, var(--mercedes-blue), var(--mercedes-green));
|
||||
height: 100%;
|
||||
transition: width 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Mercedes Status Badges */
|
||||
.status-online { background: var(--mercedes-green); color: white; }
|
||||
.status-offline { background: var(--mercedes-red); color: white; }
|
||||
.status-busy { background: var(--mercedes-yellow); color: var(--mercedes-black); }
|
||||
.status-maintenance { background: var(--mercedes-silver); color: var(--mercedes-black); }
|
||||
|
||||
.status-pending { background: var(--mercedes-yellow); color: var(--mercedes-black); }
|
||||
.status-printing { background: var(--mercedes-blue); color: white; }
|
||||
.status-completed { background: var(--mercedes-green); color: white; }
|
||||
.status-failed { background: var(--mercedes-red); color: white; }
|
||||
.status-cancelled { background: var(--mercedes-silver); color: var(--mercedes-black); }
|
||||
|
||||
/* Mercedes Navigation */
|
||||
.mercedes-nav-item {
|
||||
position: relative;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.mercedes-nav-item:hover::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--mercedes-silver);
|
||||
}
|
||||
|
||||
/* Mercedes Animations */
|
||||
@keyframes mercedes-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.mercedes-pulse {
|
||||
animation: mercedes-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes mercedes-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.mercedes-spin {
|
||||
animation: mercedes-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Mercedes Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.mercedes-card {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.mercedes-table {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mercedes-button {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mercedes Dark Mode Support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.mercedes-card {
|
||||
background: var(--mercedes-dark-gray);
|
||||
border-color: var(--mercedes-silver);
|
||||
color: var(--mercedes-white);
|
||||
}
|
||||
|
||||
.mercedes-input {
|
||||
background: var(--mercedes-dark-gray);
|
||||
color: var(--mercedes-white);
|
||||
border-color: var(--mercedes-silver);
|
||||
}
|
||||
.disable-transitions,
|
||||
.disable-transitions * {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="h-full bg-gradient-to-br from-mercedes-light-gray to-white font-sans">
|
||||
|
||||
<body class="min-h-screen flex flex-col bg-white text-slate-900 dark:bg-[#050505] dark:text-slate-200 transition-colors duration-300 text-base mercedes-background">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="mercedes-gradient mercedes-shadow sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<!-- Logo und Marke -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-10 w-10 text-mercedes-silver" fill="currentColor" viewBox="0 0 80 80">
|
||||
<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 class="text-white">
|
||||
<h1 class="text-xl font-bold tracking-wide">MYP</h1>
|
||||
<p class="text-xs text-mercedes-silver">3D Printing Platform</p>
|
||||
</div>
|
||||
<nav class="navbar">
|
||||
<div class="max-w-7xl mx-auto px-2 sm:px-4 lg:px-6">
|
||||
<div class="flex items-center justify-between h-14 sm:h-16 lg:h-20">
|
||||
<!-- Brand Section -->
|
||||
<div class="flex-shrink-0">
|
||||
<a href="{{ url_for('dashboard') }}" class="navbar-brand" aria-label="Zur Startseite">
|
||||
<!-- Mercedes-Benz Logo -->
|
||||
<div class="w-8 h-8 sm:w-10 sm:h-10 lg:w-12 lg:h-12 transition-all duration-300 group-hover:rotate-12">
|
||||
<svg class="w-full h-full text-slate-900 dark:text-white transition-colors duration-300" 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>
|
||||
<!-- Brand Text -->
|
||||
<div class="flex flex-col ml-2">
|
||||
<span class="text-xl lg:text-2xl font-bold text-slate-900 dark:text-white transition-colors duration-300 tracking-tight">Mercedes-Benz</span>
|
||||
<span class="text-xs lg:text-sm text-slate-600 dark:text-slate-400 font-medium transition-colors duration-300">MYP 3D-Druck Platform</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<div class="hidden md:block">
|
||||
<div class="ml-10 flex items-baseline space-x-8">
|
||||
<a href="/dashboard" class="mercedes-nav-item text-white hover:text-mercedes-silver px-3 py-2 text-sm font-medium transition-colors duration-200">
|
||||
Dashboard
|
||||
|
||||
<!-- Desktop Navigation Menu -->
|
||||
<div class="hidden lg:flex flex-1 justify-center px-8">
|
||||
<nav class="navbar-menu" role="navigation" aria-label="Hauptnavigation">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'dashboard' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
|
||||
</svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="/printers" class="mercedes-nav-item text-white hover:text-mercedes-silver px-3 py-2 text-sm font-medium transition-colors duration-200">
|
||||
Drucker
|
||||
|
||||
<a href="{{ url_for('printers_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'printers_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
|
||||
</svg>
|
||||
<span>3D-Drucker</span>
|
||||
</a>
|
||||
<a href="/jobs" class="mercedes-nav-item text-white hover:text-mercedes-silver px-3 py-2 text-sm font-medium transition-colors duration-200">
|
||||
Jobs
|
||||
|
||||
<a href="{{ url_for('jobs_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'jobs_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 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 2"/>
|
||||
</svg>
|
||||
<span>Druckaufträge</span>
|
||||
</a>
|
||||
<a href="/stats" class="mercedes-nav-item text-white hover:text-mercedes-silver px-3 py-2 text-sm font-medium transition-colors duration-200">
|
||||
Statistiken
|
||||
|
||||
<a href="{{ url_for('stats_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'stats_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||
</svg>
|
||||
<span>Statistiken</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<a href="/admin" class="mercedes-nav-item text-mercedes-yellow hover:text-white px-3 py-2 text-sm font-medium transition-colors duration-200">
|
||||
Admin
|
||||
<a href="{{ url_for('admin_page') }}"
|
||||
class="menu-item {{ 'text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-500' if request.endpoint == 'admin_page' else '' }}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
<span>Administration</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- User Menu -->
|
||||
<div class="flex items-center space-x-4">
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="text-white text-sm">
|
||||
<span class="text-mercedes-silver">Willkommen,</span>
|
||||
<span class="font-medium">{{ current_user.email }}</span>
|
||||
</div>
|
||||
<button onclick="logout()" class="bg-mercedes-red hover:bg-red-700 text-white px-4 py-2 rounded-lg text-sm font-medium mercedes-button transition-all duration-200">
|
||||
Abmelden
|
||||
|
||||
<!-- Right Side Controls -->
|
||||
<div class="flex items-center space-x-2 sm:space-x-3">
|
||||
<!-- Dark Mode Toggle -->
|
||||
<button
|
||||
id="darkModeToggle"
|
||||
class="dark-mode-toggle"
|
||||
aria-label="Dark Mode umschalten"
|
||||
aria-pressed="false"
|
||||
data-action="toggle-dark-mode"
|
||||
title="Dark Mode aktivieren"
|
||||
>
|
||||
<svg class="w-4 h-4 sm:w-5 sm:h-5 transition-all duration-300 transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- User Profile Dropdown -->
|
||||
<div class="relative" id="user-menu-container">
|
||||
<button
|
||||
id="user-menu-button"
|
||||
class="flex items-center space-x-2 text-sm font-medium rounded-lg p-1.5 transition-all duration-300 hover:bg-blue-50 dark:hover:bg-blue-950"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Benutzermenu öffnen"
|
||||
>
|
||||
<!-- Profile Avatar -->
|
||||
<div class="h-8 w-8 rounded-full flex items-center justify-center bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 font-medium transition-colors duration-300">
|
||||
{{ current_user.email[0].upper() if current_user.email else 'U' }}
|
||||
</div>
|
||||
<!-- User Info (hidden on mobile) -->
|
||||
<div class="hidden md:block text-left ml-2">
|
||||
<div class="text-sm font-medium text-slate-900 dark:text-white transition-colors duration-300">{{ current_user.email.split('@')[0] if current_user.email else 'Benutzer' }}</div>
|
||||
<div class="text-xs text-slate-600 dark:text-slate-400 transition-colors duration-300">Mercedes-Benz Mitarbeiter</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="/login" class="bg-mercedes-blue hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium mercedes-button transition-all duration-200">
|
||||
Anmelden
|
||||
<!-- Login Button -->
|
||||
<a href="{{ url_for('auth.login') }}"
|
||||
class="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium shadow-sm hover:shadow-md transition-all duration-300 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
<span class="hidden sm:inline">Anmelden</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<div class="md:hidden">
|
||||
<button type="button" class="text-white hover:text-mercedes-silver focus:outline-none focus:text-mercedes-silver" onclick="toggleMobileMenu()">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
<div id="mobile-menu" class="md:hidden hidden">
|
||||
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-mercedes-gray">
|
||||
<a href="/dashboard" class="text-white hover:text-mercedes-silver block px-3 py-2 text-base font-medium">Dashboard</a>
|
||||
<a href="/printers" class="text-white hover:text-mercedes-silver block px-3 py-2 text-base font-medium">Drucker</a>
|
||||
<a href="/jobs" class="text-white hover:text-mercedes-silver block px-3 py-2 text-base font-medium">Jobs</a>
|
||||
<a href="/stats" class="text-white hover:text-mercedes-silver block px-3 py-2 text-base font-medium">Statistiken</a>
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<a href="/admin" class="text-mercedes-yellow hover:text-white block px-3 py-2 text-base font-medium">Admin</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Flash Messages -->
|
||||
<div id="flash-messages" class="fixed top-20 right-4 z-40 space-y-2">
|
||||
<!-- Flash messages will be inserted here by JavaScript -->
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="min-h-screen">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="mercedes-gradient text-white py-8 mt-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center">
|
||||
<div class="flex items-center space-x-4 mb-4 md:mb-0">
|
||||
<svg class="h-8 w-8 text-mercedes-silver" fill="currentColor" viewBox="0 0 80 80">
|
||||
<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>
|
||||
<p class="text-sm font-medium">MYP - 3D Printing Platform</p>
|
||||
<p class="text-xs text-mercedes-silver">Powered by Mercedes Excellence</p>
|
||||
<main id="main-content" class="flex-grow max-w-7xl w-full mx-auto px-3 sm:px-6 lg:px-8 py-4 sm:py-8">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Flash Messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages mb-4">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} mb-2">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Mercedes-Benz Footer -->
|
||||
<footer class="bg-white dark:bg-black border-t border-gray-200 dark:border-slate-700 mt-auto transition-colors duration-300">
|
||||
<div class="max-w-screen-xl w-full mx-auto px-3 sm:px-6 lg:px-8">
|
||||
<div class="py-4 sm:py-8 border-t border-gray-200 dark:border-slate-700 mt-8 sm:mt-12 pt-4 sm:pt-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6 sm:gap-8">
|
||||
<!-- Brand Section - Stack on mobile -->
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8">
|
||||
<svg class="w-full h-full text-slate-900 dark:text-white transition-colors duration-300" fill="currentColor" viewBox="0 0 80 80">
|
||||
<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>
|
||||
<div class="text-lg font-bold text-slate-900 dark:text-white transition-colors duration-300">Mercedes-Benz</div>
|
||||
<div class="text-sm text-slate-600 dark:text-slate-400 transition-colors duration-300">MYP Platform</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-600 dark:text-slate-400 leading-relaxed transition-colors duration-300">
|
||||
Das Beste oder nichts - Professionelles 3D-Druck Management für Mercedes-Benz.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- System Info -->
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white transition-colors duration-300">System</h3>
|
||||
<div class="space-y-2 text-xs text-slate-600 dark:text-slate-400 leading-relaxed transition-colors duration-300">
|
||||
<div class="flex justify-between">
|
||||
<span>Version:</span>
|
||||
<span class="text-slate-900 dark:text-white font-medium transition-colors duration-300">3.0.0</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Status:</span>
|
||||
<div id="connection-status" class="flex items-center space-x-1">
|
||||
<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||||
<span class="text-green-500 dark:text-green-400 font-medium transition-colors duration-300">Online</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copyright -->
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white transition-colors duration-300">Rechtliches</h3>
|
||||
<div class="space-y-2 text-xs text-slate-600 dark:text-slate-400 leading-relaxed transition-colors duration-300">
|
||||
<p>© 2024 Mercedes-Benz Group AG</p>
|
||||
<p>Alle Rechte vorbehalten.</p>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-500 transition-colors duration-300">
|
||||
Entwickelt für interne Mercedes-Benz Anwendungen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center md:text-right">
|
||||
<p class="text-sm text-mercedes-silver">© 2024 MYP Platform. Alle Rechte vorbehalten.</p>
|
||||
<p class="text-xs text-mercedes-silver mt-1">Version 1.0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script>
|
||||
// Mobile menu toggle
|
||||
function toggleMobileMenu() {
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
menu.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
try {
|
||||
const response = await fetch('/auth/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.href = '/login';
|
||||
} else {
|
||||
showFlashMessage('Fehler beim Abmelden', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashMessage('Netzwerkfehler beim Abmelden', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Flash message system
|
||||
function showFlashMessage(message, type = 'info') {
|
||||
const container = document.getElementById('flash-messages');
|
||||
const messageDiv = document.createElement('div');
|
||||
|
||||
let bgColor = 'bg-mercedes-blue';
|
||||
let textColor = 'text-white';
|
||||
|
||||
switch(type) {
|
||||
case 'success':
|
||||
bgColor = 'bg-mercedes-green';
|
||||
break;
|
||||
case 'error':
|
||||
bgColor = 'bg-mercedes-red';
|
||||
break;
|
||||
case 'warning':
|
||||
bgColor = 'bg-mercedes-yellow';
|
||||
textColor = 'text-mercedes-black';
|
||||
break;
|
||||
}
|
||||
|
||||
messageDiv.className = `${bgColor} ${textColor} px-6 py-3 rounded-lg shadow-lg mercedes-shadow transform transition-all duration-300 translate-x-full`;
|
||||
messageDiv.innerHTML = `
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-medium">${message}</span>
|
||||
<button onclick="this.parentElement.parentElement.remove()" class="ml-4 text-lg font-bold hover:opacity-75">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(messageDiv);
|
||||
|
||||
// Animate in
|
||||
setTimeout(() => {
|
||||
messageDiv.classList.remove('translate-x-full');
|
||||
}, 100);
|
||||
|
||||
// Auto remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
messageDiv.classList.add('translate-x-full');
|
||||
setTimeout(() => {
|
||||
if (messageDiv.parentElement) {
|
||||
messageDiv.remove();
|
||||
}
|
||||
}, 300);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// API helper function
|
||||
async function apiCall(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'API-Fehler');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
showFlashMessage(error.message, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Format date helper
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// Format duration helper
|
||||
function formatDuration(seconds) {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m ${secs}s`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m ${secs}s`;
|
||||
} else {
|
||||
return `${secs}s`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/ui-components.js') }}"></script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
|
Reference in New Issue
Block a user