🎉 Improved database performance and session management in backend/backend/database/myp.db, backend/blueprints/__pycache__/tapo_control.cpython-313.pyc, backend/blueprints/tapo_control.py, backend/config/settings.py
This commit is contained in:
@@ -27,8 +27,8 @@
|
||||
|
||||
<!-- CSS - Performance Optimized -->
|
||||
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
|
||||
<!-- Verbesserte Navbar CSS ersetzt die alten Glassmorphism-Styles -->
|
||||
<link href="{{ url_for('static', filename='css/navbar-improved.css') }}" rel="stylesheet">
|
||||
<!-- Moderne Navbar CSS -->
|
||||
<link href="{{ url_for('static', filename='css/navbar.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/performance-optimized.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/components.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/professional-theme.min.css') }}" rel="stylesheet">
|
||||
@@ -272,373 +272,8 @@
|
||||
|
||||
<body class="min-h-screen flex flex-col bg-white text-slate-900 dark:bg-black dark:text-white transition-colors duration-300 text-base mercedes-background">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="max-w-7xl mx-auto px-3 sm:px-4 lg:px-6">
|
||||
<div class="flex items-center justify-between h-16 sm:h-18 lg:h-20">
|
||||
<!-- Brand Section -->
|
||||
<div class="flex-shrink-0">
|
||||
<a href="{{ url_for('dashboard') }}" class="navbar-brand group" aria-label="Zur Startseite">
|
||||
<!-- Mercedes-Benz Logo -->
|
||||
<div class="w-5 h-5 sm:w-6 sm:h-6 lg:w-7 lg:h-7">
|
||||
<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-3">
|
||||
<span class="text-sm lg:text-base font-bold text-slate-900 dark:text-white transition-colors duration-300 tracking-tight">Mercedes-Benz</span>
|
||||
<span class="text-xs font-medium text-slate-600 dark:text-slate-400 transition-colors duration-300">Manage Your Printer - </span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation Menu - überarbeitetes Design -->
|
||||
<div class="hidden lg:flex flex-1 justify-center">
|
||||
<nav class="navbar-menu-new" role="navigation" aria-label="Hauptnavigation">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'dashboard' else '' }}">
|
||||
<i class="fas fa-tachometer-alt w-5 h-5"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('printers_page') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'printers_page' else '' }}">
|
||||
<i class="fas fa-print w-5 h-5"></i>
|
||||
<span>Drucker</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('CONTROL_PRINTER') %}
|
||||
<a href="{{ url_for('tapo.tapo_dashboard') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'tapo.tapo_dashboard' else '' }}">
|
||||
<i class="fas fa-plug w-5 h-5"></i>
|
||||
<span>Tapo-Steckdosen</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('jobs_page') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'jobs_page' else '' }}">
|
||||
<i class="fas fa-clipboard-list w-5 h-5"></i>
|
||||
<span>Reservierungen</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('stats_page') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'stats_page' else '' }}">
|
||||
<i class="fas fa-chart-bar w-5 h-5"></i>
|
||||
<span>Statistiken</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('calendar.calendar_view') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'calendar.calendar_view' else '' }}">
|
||||
<i class="fas fa-calendar-alt w-5 h-5"></i>
|
||||
<span>Schichtplan</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('guest.guest_request_form') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'guest.guest_request_form' else '' }}">
|
||||
<i class="fas fa-user-plus w-5 h-5"></i>
|
||||
<span>Antrag stellen</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('guest.guest_requests_overview') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'guest.guest_requests_overview' else '' }}">
|
||||
<i class="fas fa-file-alt w-5 h-5"></i>
|
||||
<span>Meine Anträge</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<a href="{{ url_for('admin.admin_dashboard') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'admin.admin_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="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>Ausbilder-Bereich</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Seite der Navbar -->
|
||||
<div class="flex items-center space-x-2 sm:space-x-3">
|
||||
<!-- Mobile Menu Toggle (neu) -->
|
||||
<button
|
||||
id="mobileMenuToggle"
|
||||
class="lg:hidden p-2 rounded-full text-slate-700 dark:text-slate-300 hover:bg-slate-100/80 dark:hover:bg-slate-800/50"
|
||||
aria-label="Menü öffnen"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- Benachrichtigungen - kompakteres Design -->
|
||||
<div class="relative">
|
||||
<button
|
||||
id="notificationToggle"
|
||||
class="relative p-1.5 rounded-full text-slate-700 dark:text-slate-300 hover:bg-slate-100/80 dark:hover:bg-slate-800/50 transition-all duration-200"
|
||||
aria-label="Benachrichtigungen anzeigen"
|
||||
title="Benachrichtigungen"
|
||||
>
|
||||
<i class="fas fa-bell text-lg"></i>
|
||||
<!-- Badge für ungelesene Benachrichtigungen -->
|
||||
<span id="notificationBadge" class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-medium hidden">
|
||||
0
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Benachrichtigungs-Dropdown -->
|
||||
<div id="notificationDropdown" class="absolute right-0 mt-2 w-72 sm:w-80 bg-white dark:bg-slate-800 rounded-lg shadow-lg border border-slate-200 dark:border-slate-600 z-50 hidden">
|
||||
<div class="p-3 border-b border-slate-200 dark:border-slate-600">
|
||||
<h3 class="text-base font-semibold text-slate-900 dark:text-white">Benachrichtigungen</h3>
|
||||
</div>
|
||||
<div id="notificationList" class="max-h-80 overflow-y-auto">
|
||||
<div class="p-3 text-center text-slate-500 dark:text-slate-400 text-sm">
|
||||
Keine neuen Benachrichtigungen
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 border-t border-slate-200 dark:border-slate-600">
|
||||
<button id="markAllRead" class="w-full text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 transition-colors">
|
||||
Alle als gelesen markieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dark Mode Toggle - Clean and Modern -->
|
||||
<button
|
||||
class="dark-mode-toggle darkModeToggle"
|
||||
aria-label="Dark Mode umschalten"
|
||||
title="Design wechseln"
|
||||
>
|
||||
<span class="dark-mode-toggle-slider">
|
||||
<i class="fas fa-sun dark-mode-toggle-icon sun absolute inset-0 flex items-center justify-center"></i>
|
||||
<i class="fas fa-moon dark-mode-toggle-icon moon absolute inset-0 flex items-center justify-center opacity-0"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- User Profile Dropdown - kompakteres Design -->
|
||||
<div class="relative" id="user-menu-container">
|
||||
<button
|
||||
id="user-menu-button"
|
||||
class="flex items-center space-x-1 rounded-full p-1 text-slate-700 dark:text-slate-300 hover:bg-slate-100/80 dark:hover:bg-slate-800/50 transition-all duration-200"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="Benutzermenu öffnen"
|
||||
>
|
||||
<!-- Profile Avatar -->
|
||||
<div class="w-6 h-6 rounded-full bg-blue-500 flex items-center justify-center text-white text-xs font-medium">
|
||||
{{ current_user.email[0].upper() if current_user.email else 'U' }}
|
||||
</div>
|
||||
<!-- User Info (nur auf größeren Geräten) -->
|
||||
<div class="hidden sm:block text-left ml-1">
|
||||
<div class="text-xs 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>
|
||||
</button>
|
||||
|
||||
<!-- User Menu Dropdown -->
|
||||
<div id="user-dropdown" class="absolute right-0 mt-2 w-64 bg-white dark:bg-slate-800 rounded-lg shadow-lg border border-slate-200 dark:border-slate-600 z-50 hidden origin-top-right">
|
||||
<!-- User Info Header -->
|
||||
<div class="px-4 py-3 border-b border-slate-200 dark:border-slate-600">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-medium">
|
||||
{{ current_user.email[0].upper() if current_user.email else 'U' }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-slate-900 dark:text-white truncate">
|
||||
{{ current_user.full_name if current_user.full_name else current_user.email.split('@')[0] if current_user.email else 'Benutzer' }}
|
||||
</p>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400 truncate">
|
||||
{{ current_user.email if current_user.email else 'Keine E-Mail' }}
|
||||
</p>
|
||||
{% if current_user.is_admin %}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200 mt-1">
|
||||
Ausbilder
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Items -->
|
||||
<div class="py-1">
|
||||
<a href="{{ url_for('users.user_profile') }}"
|
||||
class="flex items-center px-4 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
Mein Profil
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('users.user_settings') }}"
|
||||
class="flex items-center px-4 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" 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>
|
||||
Einstellungen
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('jobs_page') }}"
|
||||
class="flex items-center px-4 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
||||
</svg>
|
||||
Neue Reservierung
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('stats_page') }}"
|
||||
class="flex items-center px-4 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" 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>
|
||||
Analytik
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-200 dark:border-slate-600">
|
||||
<a href="{{ url_for('privacy') }}"
|
||||
class="flex items-center px-4 py-2 text-sm text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||
</svg>
|
||||
Datenschutz
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('terms') }}"
|
||||
class="flex items-center px-4 py-2 text-sm text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<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>
|
||||
Nutzungsbedingungen
|
||||
</a>
|
||||
|
||||
<button onclick="handleLogout()"
|
||||
class="flex items-center w-full px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors duration-200">
|
||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Dark Mode Toggle für nicht angemeldete Benutzer -->
|
||||
<button
|
||||
id="darkModeToggle"
|
||||
class="dark-mode-toggle"
|
||||
aria-label="Dark Mode umschalten"
|
||||
title="Design wechseln"
|
||||
>
|
||||
<span class="dark-mode-toggle-slider">
|
||||
<i class="fas fa-sun dark-mode-toggle-icon sun absolute inset-0 flex items-center justify-center"></i>
|
||||
<i class="fas fa-moon dark-mode-toggle-icon moon absolute inset-0 flex items-center justify-center opacity-0"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- Login Button - kompakteres Design -->
|
||||
<a href="{{ url_for('auth.login') }}"
|
||||
class="flex items-center space-x-1 py-1 px-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white text-xs transition-colors duration-200">
|
||||
<svg class="w-3.5 h-3.5" 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>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Menu (neu) -->
|
||||
<div id="mobileMenu" class="mobile-menu-new hidden lg:hidden">
|
||||
<nav class="flex flex-col space-y-1 px-3 py-4" role="navigation" aria-label="Mobile Navigation">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="mobile-nav-item {{ 'active' 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="{{ url_for('printers_page') }}"
|
||||
class="mobile-nav-item {{ 'active' 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="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
<span>Drucker</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('CONTROL_PRINTER') %}
|
||||
<a href="{{ url_for('tapo.tapo_dashboard') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'tapo.tapo_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="M13 16h-3a2 2 0 00-2 2v1a2 2 0 002 2h3m4-6v1a2 2 0 01-2 2H9a2 2 0 01-2-2v-1m8-2V9a2 2 0 00-2-2H9a2 2 0 00-2 2v3.5M13 9h3l2 5H5l2-5h3"/>
|
||||
</svg>
|
||||
<span>Tapo-Steckdosen</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url_for('jobs_page') }}"
|
||||
class="mobile-nav-item {{ 'active' 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>Reservierungen</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('stats_page') }}"
|
||||
class="mobile-nav-item {{ 'active' 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>
|
||||
|
||||
<a href="{{ url_for('calendar.calendar_view') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'calendar.calendar_view' 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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<span>Schichtplan</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('guest.guest_request_form') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'guest.guest_request_form' 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="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
<span>Antrag stellen</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('guest.guest_requests_overview') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'guest.guest_requests_overview' 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 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>Meine Anträge</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<a href="{{ url_for('admin.admin_dashboard') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'admin.admin_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="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>Ausbilder-Bereich</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
<!-- Moderne MYP Platform Navbar -->
|
||||
{% include 'includes/navbar.html' %}
|
||||
|
||||
<!-- Main Content -->
|
||||
<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">
|
||||
@@ -784,8 +419,8 @@
|
||||
<!-- Glassmorphism Notification System - Modernisiert -->
|
||||
<script src="{{ url_for('static', filename='js/glassmorphism-notifications.min.js') }}"></script>
|
||||
|
||||
<!-- Mobile Navigation Handler -->
|
||||
<script src="{{ url_for('static', filename='js/navbar-mobile.js') }}"></script>
|
||||
<!-- Modern Navbar Controller -->
|
||||
<script src="{{ url_for('static', filename='js/navbar.js') }}"></script>
|
||||
|
||||
<!-- Additional JavaScript Functions -->
|
||||
<script>
|
||||
|
304
backend/templates/includes/navbar.html
Normal file
304
backend/templates/includes/navbar.html
Normal file
@@ -0,0 +1,304 @@
|
||||
<!-- =============================================
|
||||
MYP Platform - Moderne Navbar
|
||||
Mercedes-Benz Design System
|
||||
============================================= -->
|
||||
|
||||
<nav class="navbar" role="navigation" aria-label="Hauptnavigation">
|
||||
<div class="navbar-container">
|
||||
|
||||
<!-- Brand Section -->
|
||||
<a href="{{ url_for('dashboard') }}" class="navbar-brand" aria-label="Zur Startseite">
|
||||
<!-- Mercedes-Benz Logo -->
|
||||
<svg class="brand-icon" viewBox="0 0 80 80" fill="currentColor" 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.5C27,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,40c0-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.8C53.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.9C67.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,40c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
|
||||
</svg>
|
||||
|
||||
<!-- Brand Text -->
|
||||
<div class="brand-text">
|
||||
<span class="brand-title">Mercedes-Benz</span>
|
||||
<span class="brand-subtitle">MYP Platform</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="navbar-nav">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'dashboard' else '' }}"
|
||||
aria-label="Dashboard">
|
||||
<i class="fas fa-tachometer-alt" aria-hidden="true"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('printers_page') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'printers_page' else '' }}"
|
||||
aria-label="Drucker verwalten">
|
||||
<i class="fas fa-print" aria-hidden="true"></i>
|
||||
<span>Drucker</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('VIEW_JOBS') %}
|
||||
<a href="{{ url_for('jobs.jobs_page') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'jobs.jobs_page' else '' }}"
|
||||
aria-label="Druckaufträge anzeigen">
|
||||
<i class="fas fa-tasks" aria-hidden="true"></i>
|
||||
<span>Aufträge</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('CONTROL_PRINTER') %}
|
||||
<a href="{{ url_for('tapo.tapo_dashboard') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'tapo.tapo_dashboard' else '' }}"
|
||||
aria-label="Smart-Plugs verwalten">
|
||||
<i class="fas fa-plug" aria-hidden="true"></i>
|
||||
<span>Smart-Plugs</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('VIEW_CALENDAR') %}
|
||||
<a href="{{ url_for('calendar') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'calendar' else '' }}"
|
||||
aria-label="Kalender anzeigen">
|
||||
<i class="fas fa-calendar-alt" aria-hidden="true"></i>
|
||||
<span>Kalender</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_role('admin') %}
|
||||
<a href="{{ url_for('admin.admin_dashboard') }}"
|
||||
class="nav-item {{ 'active' if request.endpoint == 'admin.admin_dashboard' else '' }}"
|
||||
aria-label="Administrationsbereich">
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
<span>Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Navbar Actions (Rechte Seite) -->
|
||||
<div class="navbar-actions">
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- Benachrichtigungen -->
|
||||
<div class="dropdown">
|
||||
<button class="navbar-btn"
|
||||
id="notificationToggle"
|
||||
aria-label="Benachrichtigungen anzeigen"
|
||||
aria-expanded="false">
|
||||
<i class="fas fa-bell" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu" id="notificationDropdown">
|
||||
<div class="dropdown-header">
|
||||
<i class="fas fa-bell mr-2" aria-hidden="true"></i>
|
||||
Benachrichtigungen
|
||||
</div>
|
||||
|
||||
<div id="notificationList">
|
||||
<!-- Wird dynamisch geladen -->
|
||||
<div class="dropdown-item">
|
||||
<i class="fas fa-info-circle text-blue-500" aria-hidden="true"></i>
|
||||
<div>
|
||||
<div class="font-medium">Keine neuen Benachrichtigungen</div>
|
||||
<div class="text-sm text-gray-500">Alle Meldungen sind aktuell</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{{ url_for('notifications') }}" class="dropdown-item">
|
||||
<i class="fas fa-eye" aria-hidden="true"></i>
|
||||
Alle anzeigen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Menu -->
|
||||
<div class="dropdown">
|
||||
<div class="user-avatar"
|
||||
aria-label="Benutzerprofil öffnen"
|
||||
aria-expanded="false">
|
||||
{{ current_user.get_initials() }}
|
||||
</div>
|
||||
|
||||
<div class="dropdown-menu" id="userDropdown">
|
||||
<div class="dropdown-header">
|
||||
<i class="fas fa-user mr-2" aria-hidden="true"></i>
|
||||
{{ current_user.display_name or current_user.username }}
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('users.profile') }}" class="dropdown-item">
|
||||
<i class="fas fa-user-circle" aria-hidden="true"></i>
|
||||
Mein Profil
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('users.settings') }}" class="dropdown-item">
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
Einstellungen
|
||||
</a>
|
||||
|
||||
{% if current_user.has_role('admin') %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{{ url_for('admin.admin_dashboard') }}" class="dropdown-item">
|
||||
<i class="fas fa-shield-alt" aria-hidden="true"></i>
|
||||
Administration
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{{ url_for('auth.logout') }}" class="dropdown-item">
|
||||
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
||||
Abmelden
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- Nicht angemeldet -->
|
||||
<a href="{{ url_for('auth.login') }}" class="navbar-btn" aria-label="Anmelden">
|
||||
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Dark Mode Toggle -->
|
||||
<button class="navbar-btn"
|
||||
id="darkModeToggle"
|
||||
aria-label="Dark Mode umschalten"
|
||||
title="Dark Mode umschalten">
|
||||
<i class="fas fa-moon" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<!-- Mobile Menu Toggle -->
|
||||
<button class="mobile-menu-toggle"
|
||||
id="mobileMenuToggle"
|
||||
aria-label="Menü öffnen/schließen"
|
||||
aria-expanded="false">
|
||||
<i class="fas fa-bars" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<div class="mobile-nav">
|
||||
<a href="{{ url_for('dashboard') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'dashboard' else '' }}">
|
||||
<i class="fas fa-tachometer-alt" aria-hidden="true"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('printers_page') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'printers_page' else '' }}">
|
||||
<i class="fas fa-print" aria-hidden="true"></i>
|
||||
<span>Drucker</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('VIEW_JOBS') %}
|
||||
<a href="{{ url_for('jobs.jobs_page') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'jobs.jobs_page' else '' }}">
|
||||
<i class="fas fa-tasks" aria-hidden="true"></i>
|
||||
<span>Druckaufträge</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('CONTROL_PRINTER') %}
|
||||
<a href="{{ url_for('tapo.tapo_dashboard') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'tapo.tapo_dashboard' else '' }}">
|
||||
<i class="fas fa-plug" aria-hidden="true"></i>
|
||||
<span>Smart-Plugs</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('VIEW_CALENDAR') %}
|
||||
<a href="{{ url_for('calendar') }}"
|
||||
class="mobile-nav-item {{ 'active' if request.endpoint == 'calendar' else '' }}">
|
||||
<i class="fas fa-calendar-alt" aria-hidden="true"></i>
|
||||
<span>Kalender</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<a href="{{ url_for('users.profile') }}"
|
||||
class="mobile-nav-item">
|
||||
<i class="fas fa-user-circle" aria-hidden="true"></i>
|
||||
<span>Mein Profil</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ url_for('users.settings') }}"
|
||||
class="mobile-nav-item">
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
<span>Einstellungen</span>
|
||||
</a>
|
||||
|
||||
{% if current_user.has_role('admin') %}
|
||||
<a href="{{ url_for('admin.admin_dashboard') }}"
|
||||
class="mobile-nav-item">
|
||||
<i class="fas fa-shield-alt" aria-hidden="true"></i>
|
||||
<span>Administration</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<a href="{{ url_for('auth.logout') }}"
|
||||
class="mobile-nav-item">
|
||||
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
|
||||
<span>Abmelden</span>
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.login') }}"
|
||||
class="mobile-nav-item">
|
||||
<i class="fas fa-sign-in-alt" aria-hidden="true"></i>
|
||||
<span>Anmelden</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navbar JavaScript -->
|
||||
<script>
|
||||
// Dark Mode Toggle Funktionalität
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
const icon = darkModeToggle.querySelector('i');
|
||||
|
||||
// Aktuellen Dark Mode Status prüfen
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
updateDarkModeIcon(isDark);
|
||||
|
||||
darkModeToggle.addEventListener('click', function() {
|
||||
const htmlElement = document.documentElement;
|
||||
const newIsDark = !htmlElement.classList.contains('dark');
|
||||
|
||||
htmlElement.classList.toggle('dark');
|
||||
updateDarkModeIcon(newIsDark);
|
||||
|
||||
// Status in localStorage speichern
|
||||
localStorage.setItem('darkMode', newIsDark ? 'true' : 'false');
|
||||
|
||||
// Optional: Server-Seite informieren
|
||||
fetch('/api/user/theme', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ theme: newIsDark ? 'dark' : 'light' })
|
||||
}).catch(() => {
|
||||
// Stumm fehlschlagen - Theme funktioniert auch offline
|
||||
});
|
||||
});
|
||||
|
||||
function updateDarkModeIcon(isDark) {
|
||||
if (isDark) {
|
||||
icon.className = 'fas fa-sun';
|
||||
darkModeToggle.setAttribute('aria-label', 'Light Mode aktivieren');
|
||||
} else {
|
||||
icon.className = 'fas fa-moon';
|
||||
darkModeToggle.setAttribute('aria-label', 'Dark Mode aktivieren');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@@ -14,7 +14,7 @@ Tapo-Steckdosen-Steuerung | MYP Platform
|
||||
Tapo-Steckdosen-Steuerung
|
||||
</h1>
|
||||
<p class="text-slate-600 dark:text-slate-300">
|
||||
Direkte Kontrolle aller TP-Link Tapo-Steckdosen
|
||||
Mercedes-Benz TBA Marienfelde - 6 Arbeitsplätze für 3D-Drucker
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,18 +22,19 @@ Tapo-Steckdosen-Steuerung | MYP Platform
|
||||
|
||||
{% block page_actions %}
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button onclick="refreshAllStatus()"
|
||||
class="btn-secondary flex items-center space-x-2">
|
||||
<a href="{{ url_for('tapo.tapo_dashboard') }}"
|
||||
class="btn-secondary flex items-center space-x-2">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
<span>Status aktualisieren</span>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.has_permission('ADMIN') %}
|
||||
<button onclick="discoverOutlets()"
|
||||
class="btn-primary flex items-center space-x-2">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>Steckdosen suchen</span>
|
||||
</button>
|
||||
<form method="POST" action="{{ url_for('tapo.discover_outlets') }}" class="inline">
|
||||
<button type="submit" class="btn-primary flex items-center space-x-2">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>Steckdosen suchen</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a href="{{ url_for('tapo.manual_control') }}"
|
||||
class="btn-outline flex items-center space-x-2">
|
||||
@@ -54,10 +55,10 @@ Tapo-Steckdosen-Steuerung | MYP Platform
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-700 dark:text-slate-300">
|
||||
Gesamt
|
||||
Arbeitsplätze Gesamt
|
||||
</h3>
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white" id="total-count">
|
||||
{{ total_outlets }}
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
{{ total_outlets or 6 }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,8 +73,8 @@ Tapo-Steckdosen-Steuerung | MYP Platform
|
||||
<h3 class="text-lg font-semibold text-slate-700 dark:text-slate-300">
|
||||
Online
|
||||
</h3>
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white" id="online-count">
|
||||
{{ online_outlets }}
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
{{ online_outlets or 0 }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,377 +89,238 @@ Tapo-Steckdosen-Steuerung | MYP Platform
|
||||
<h3 class="text-lg font-semibold text-slate-700 dark:text-slate-300">
|
||||
Aktive
|
||||
</h3>
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white" id="active-count">
|
||||
0
|
||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
{{ outlets.values() | selectattr('status', 'equalto', 'on') | list | length }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Steckdosen-Grid -->
|
||||
<!-- Mercedes-Benz 6 Arbeitsplätze -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-xl font-semibold text-slate-800 dark:text-white flex items-center">
|
||||
<i class="fas fa-list mr-3"></i>
|
||||
Alle Tapo-Steckdosen
|
||||
<i class="fas fa-industry mr-3"></i>
|
||||
Mercedes-Benz TBA Marienfelde - 3D-Drucker Arbeitsplätze
|
||||
</h2>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400 mt-2">
|
||||
Feste Installation mit 6 konfigurierten Arbeitsplätzen
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
{% if outlets %}
|
||||
<!-- Steckdosen-Grid - Immer 6 Arbeitsplätze anzeigen -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="outlets-grid">
|
||||
{% for ip, outlet in outlets.items() %}
|
||||
<div class="outlet-card border rounded-lg p-4 {{ 'border-green-300 bg-green-50 dark:bg-green-900/20' if outlet.reachable else 'border-red-300 bg-red-50 dark:bg-red-900/20' }}"
|
||||
data-ip="{{ ip }}">
|
||||
|
||||
<!-- Steckdosen-Header -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="status-indicator w-4 h-4 rounded-full {{ 'bg-green-500' if outlet.reachable else 'bg-red-500' }}"
|
||||
data-ip="{{ ip }}"></div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-white">
|
||||
{{ outlet.printer_name }}
|
||||
</h3>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||
{{ ip }}
|
||||
</p>
|
||||
{% if outlets %}
|
||||
{% for ip, outlet in outlets.items() %}
|
||||
<div class="outlet-card border rounded-lg p-4 {{ 'border-green-300 bg-green-50 dark:bg-green-900/20' if outlet.reachable else 'border-red-300 bg-red-50 dark:bg-red-900/20' }}"
|
||||
data-ip="{{ ip }}">
|
||||
|
||||
<!-- Arbeitsplatz-Header -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="status-indicator w-4 h-4 rounded-full {{ 'bg-green-500' if outlet.reachable else 'bg-red-500' }}"></div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-white">
|
||||
{{ outlet.printer_name }}
|
||||
</h3>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||
IP: {{ ip }}
|
||||
</p>
|
||||
{% if outlet.model %}
|
||||
<p class="text-xs text-slate-500 dark:text-slate-500">
|
||||
{{ outlet.model }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- Konfigurationsstatus -->
|
||||
{% if outlet.configured_in_db %}
|
||||
<span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full dark:bg-blue-900 dark:text-blue-300"
|
||||
title="In Datenbank konfiguriert">
|
||||
<i class="fas fa-database text-xs"></i>
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="bg-yellow-100 text-yellow-800 text-xs px-2 py-1 rounded-full dark:bg-yellow-900 dark:text-yellow-300"
|
||||
title="Nicht in Datenbank konfiguriert">
|
||||
<i class="fas fa-exclamation-triangle text-xs"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- Position-Badge -->
|
||||
<span class="bg-slate-100 text-slate-700 text-xs px-2 py-1 rounded-full dark:bg-slate-700 dark:text-slate-300">
|
||||
#{{ outlet.position or loop.index }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<button onclick="refreshOutletStatus('{{ ip }}')"
|
||||
class="p-2 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors"
|
||||
title="Status aktualisieren">
|
||||
<i class="fas fa-sync-alt text-sm"></i>
|
||||
</button>
|
||||
|
||||
<button onclick="testConnection('{{ ip }}')"
|
||||
class="p-2 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors"
|
||||
title="Verbindung testen">
|
||||
<i class="fas fa-network-wired text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status-Info -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Status:</span>
|
||||
<span class="status-text font-medium" data-ip="{{ ip }}">
|
||||
{% if outlet.reachable %}
|
||||
{% if outlet.status == 'on' %}
|
||||
<span class="text-green-600 dark:text-green-400">
|
||||
<i class="fas fa-power-off mr-1"></i>EIN
|
||||
</span>
|
||||
{% elif outlet.status == 'off' %}
|
||||
<span class="text-slate-600 dark:text-slate-400">
|
||||
<i class="fas fa-power-off mr-1"></i>AUS
|
||||
</span>
|
||||
<!-- Status-Informationen -->
|
||||
<div class="mb-4 space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Status:</span>
|
||||
<span class="status-text font-medium">
|
||||
{% if outlet.reachable %}
|
||||
{% if outlet.status == 'on' %}
|
||||
<span class="text-green-600 dark:text-green-400">
|
||||
<i class="fas fa-power-off mr-1"></i>EIN
|
||||
</span>
|
||||
{% elif outlet.status == 'off' %}
|
||||
<span class="text-slate-600 dark:text-slate-400">
|
||||
<i class="fas fa-power-off mr-1"></i>AUS
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-yellow-600 dark:text-yellow-400">
|
||||
<i class="fas fa-question mr-1"></i>UNBEKANNT
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-yellow-600 dark:text-yellow-400">
|
||||
<i class="fas fa-question mr-1"></i>UNBEKANNT
|
||||
<span class="text-red-600 dark:text-red-400">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>OFFLINE
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-red-600 dark:text-red-400">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>OFFLINE
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Standort:</span>
|
||||
<span class="text-sm font-medium text-slate-800 dark:text-white">
|
||||
{{ outlet.location }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if outlet.configured_in_db and outlet.printer_id %}
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Drucker-ID:</span>
|
||||
<span class="text-sm font-medium text-slate-800 dark:text-white">
|
||||
#{{ outlet.printer_id }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Steuerungsformen (ohne JavaScript) -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- EIN-Button -->
|
||||
<form method="POST" action="{{ url_for('tapo.control_outlet_form') }}" class="inline">
|
||||
<input type="hidden" name="ip" value="{{ ip }}">
|
||||
<input type="hidden" name="action" value="on">
|
||||
<button type="submit"
|
||||
class="w-full py-2 px-3 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors font-medium text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
{% if not outlet.reachable %}disabled{% endif %}>
|
||||
<i class="fas fa-power-off mr-1"></i>
|
||||
EIN
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- AUS-Button -->
|
||||
<form method="POST" action="{{ url_for('tapo.control_outlet_form') }}" class="inline">
|
||||
<input type="hidden" name="ip" value="{{ ip }}">
|
||||
<input type="hidden" name="action" value="off">
|
||||
<button type="submit"
|
||||
class="w-full py-2 px-3 bg-slate-600 hover:bg-slate-700 text-white rounded-lg transition-colors font-medium text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
{% if not outlet.reachable %}disabled{% endif %}>
|
||||
<i class="fas fa-power-off mr-1"></i>
|
||||
AUS
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if outlet.error %}
|
||||
<div class="mt-3 p-2 bg-red-100 dark:bg-red-900/30 border border-red-300 dark:border-red-700 rounded text-sm text-red-700 dark:text-red-300">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
{{ outlet.error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Zusätzliche Aktionen für Administratoren -->
|
||||
{% if current_user.has_permission('ADMIN') %}
|
||||
<div class="mt-3 pt-3 border-t border-slate-200 dark:border-slate-600">
|
||||
<div class="flex space-x-2">
|
||||
<form method="POST" action="{{ url_for('tapo.test_connection_form') }}" class="flex-1">
|
||||
<input type="hidden" name="ip" value="{{ ip }}">
|
||||
<button type="submit"
|
||||
class="w-full text-xs py-1 px-2 text-slate-600 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200 border border-slate-300 dark:border-slate-600 rounded transition-colors">
|
||||
<i class="fas fa-network-wired mr-1"></i>Test
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if not outlet.configured_in_db %}
|
||||
<a href="{{ url_for('admin.add_printer') }}?preset_plug_ip={{ ip }}"
|
||||
class="flex-1 text-xs py-1 px-2 text-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200 border border-blue-300 dark:border-blue-600 rounded transition-colors">
|
||||
<i class="fas fa-plus mr-1"></i>Setup
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<!-- Fallback: Zeige 6 Platzhalter-Arbeitsplätze -->
|
||||
{% for i in range(1, 7) %}
|
||||
<div class="outlet-card border border-red-300 bg-red-50 dark:bg-red-900/20 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="status-indicator w-4 h-4 rounded-full bg-red-500"></div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-slate-800 dark:text-white">
|
||||
3D-Drucker {{ i }}
|
||||
</h3>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||
192.168.1.20{{ i }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="bg-slate-100 text-slate-700 text-xs px-2 py-1 rounded-full dark:bg-slate-700 dark:text-slate-300">
|
||||
#{{ i }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Standort:</span>
|
||||
<span class="text-sm font-medium text-slate-800 dark:text-white">
|
||||
{{ outlet.location }}
|
||||
</span>
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Status:</span>
|
||||
<span class="text-red-600 dark:text-red-400">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>NICHT KONFIGURIERT
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400">Standort:</span>
|
||||
<span class="text-sm font-medium text-slate-800 dark:text-white">
|
||||
Arbeitsplatz {{ i }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button disabled class="w-full py-2 px-3 bg-slate-400 text-white rounded-lg opacity-50 cursor-not-allowed text-sm">
|
||||
<i class="fas fa-power-off mr-1"></i>EIN
|
||||
</button>
|
||||
<button disabled class="w-full py-2 px-3 bg-slate-400 text-white rounded-lg opacity-50 cursor-not-allowed text-sm">
|
||||
<i class="fas fa-power-off mr-1"></i>AUS
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 p-2 bg-yellow-100 dark:bg-yellow-900/30 border border-yellow-300 dark:border-yellow-700 rounded text-sm text-yellow-700 dark:text-yellow-300">
|
||||
<i class="fas fa-info-circle mr-1"></i>
|
||||
Arbeitsplatz {{ i }} noch nicht eingerichtet
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Steuerungsbuttons -->
|
||||
<div class="flex space-x-3">
|
||||
<button onclick="controlOutlet('{{ ip }}', 'on')"
|
||||
class="flex-1 py-2 px-3 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors font-medium text-sm"
|
||||
{% if not outlet.reachable %}disabled{% endif %}>
|
||||
<i class="fas fa-power-off mr-1"></i>
|
||||
EIN
|
||||
</button>
|
||||
|
||||
<button onclick="controlOutlet('{{ ip }}', 'off')"
|
||||
class="flex-1 py-2 px-3 bg-slate-600 hover:bg-slate-700 text-white rounded-lg transition-colors font-medium text-sm"
|
||||
{% if not outlet.reachable %}disabled{% endif %}>
|
||||
<i class="fas fa-power-off mr-1"></i>
|
||||
AUS
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if outlet.error %}
|
||||
<div class="mt-3 p-2 bg-red-100 dark:bg-red-900/30 border border-red-300 dark:border-red-700 rounded text-sm text-red-700 dark:text-red-300">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
{{ outlet.error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<div class="text-slate-400 dark:text-slate-500 mb-4">
|
||||
<i class="fas fa-plug text-6xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-slate-600 dark:text-slate-400 mb-2">
|
||||
Keine Tapo-Steckdosen konfiguriert
|
||||
</h3>
|
||||
<p class="text-slate-500 dark:text-slate-400 mb-6">
|
||||
Es wurden noch keine Drucker mit Tapo-Steckdosen eingerichtet.
|
||||
</p>
|
||||
{% if current_user.is_authenticated and current_user.has_permission('ADMIN') %}
|
||||
<div class="flex justify-center space-x-4">
|
||||
<button onclick="discoverOutlets()"
|
||||
class="btn-primary">
|
||||
<i class="fas fa-search mr-2"></i>
|
||||
Steckdosen suchen
|
||||
</button>
|
||||
<a href="{{ url_for('admin.printers_overview') }}"
|
||||
class="btn-secondary">
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
Drucker verwalten
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loading-overlay" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white dark:bg-slate-800 p-6 rounded-lg shadow-lg">
|
||||
<div class="flex items-center space-x-3">
|
||||
<i class="fas fa-spinner fa-spin text-blue-600 text-xl"></i>
|
||||
<span class="text-slate-800 dark:text-white font-medium" id="loading-text">
|
||||
Lädt...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Refresh nur falls explizit gewünscht (minimales JavaScript) -->
|
||||
{% if request.args.get('auto_refresh') %}
|
||||
<script>
|
||||
// Globale Variablen
|
||||
let outlets = {{ outlets | tojson }};
|
||||
|
||||
// Utility-Funktionen
|
||||
function showLoading(text = 'Lädt...') {
|
||||
document.getElementById('loading-text').textContent = text;
|
||||
document.getElementById('loading-overlay').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
document.getElementById('loading-overlay').classList.add('hidden');
|
||||
}
|
||||
|
||||
function updateOutletDisplay(ip, data) {
|
||||
const card = document.querySelector(`[data-ip="${ip}"]`);
|
||||
if (!card) return;
|
||||
|
||||
const statusIndicator = card.querySelector('.status-indicator');
|
||||
const statusText = card.querySelector('.status-text');
|
||||
const buttons = card.querySelectorAll('button[onclick*="controlOutlet"]');
|
||||
|
||||
// Status-Indikator aktualisieren
|
||||
statusIndicator.className = `status-indicator w-4 h-4 rounded-full ${data.reachable ? 'bg-green-500' : 'bg-red-500'}`;
|
||||
|
||||
// Status-Text aktualisieren
|
||||
if (data.reachable) {
|
||||
let statusIcon, statusColor;
|
||||
if (data.status === 'on') {
|
||||
statusIcon = 'fas fa-power-off';
|
||||
statusColor = 'text-green-600 dark:text-green-400';
|
||||
statusText.innerHTML = `<span class="${statusColor}"><i class="${statusIcon} mr-1"></i>EIN</span>`;
|
||||
} else if (data.status === 'off') {
|
||||
statusIcon = 'fas fa-power-off';
|
||||
statusColor = 'text-slate-600 dark:text-slate-400';
|
||||
statusText.innerHTML = `<span class="${statusColor}"><i class="${statusIcon} mr-1"></i>AUS</span>`;
|
||||
} else {
|
||||
statusIcon = 'fas fa-question';
|
||||
statusColor = 'text-yellow-600 dark:text-yellow-400';
|
||||
statusText.innerHTML = `<span class="${statusColor}"><i class="${statusIcon} mr-1"></i>UNBEKANNT</span>`;
|
||||
}
|
||||
|
||||
// Buttons aktivieren
|
||||
buttons.forEach(btn => btn.disabled = false);
|
||||
} else {
|
||||
statusText.innerHTML = '<span class="text-red-600 dark:text-red-400"><i class="fas fa-exclamation-triangle mr-1"></i>OFFLINE</span>';
|
||||
|
||||
// Buttons deaktivieren
|
||||
buttons.forEach(btn => btn.disabled = true);
|
||||
}
|
||||
|
||||
// Kartenrand aktualisieren
|
||||
card.className = card.className.replace(/border-\w+-300|bg-\w+-50|dark:bg-\w+-900\/20/g, '');
|
||||
if (data.reachable) {
|
||||
card.className += ' border-green-300 bg-green-50 dark:bg-green-900/20';
|
||||
} else {
|
||||
card.className += ' border-red-300 bg-red-50 dark:bg-red-900/20';
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatistics() {
|
||||
const totalCount = Object.keys(outlets).length;
|
||||
const onlineCount = Object.values(outlets).filter(o => o.reachable).length;
|
||||
const activeCount = Object.values(outlets).filter(o => o.status === 'on').length;
|
||||
|
||||
document.getElementById('total-count').textContent = totalCount;
|
||||
document.getElementById('online-count').textContent = onlineCount;
|
||||
document.getElementById('active-count').textContent = activeCount;
|
||||
}
|
||||
|
||||
// API-Funktionen
|
||||
async function controlOutlet(ip, action) {
|
||||
try {
|
||||
showLoading(`Steckdose wird ${action === 'on' ? 'eingeschaltet' : 'ausgeschaltet'}...`);
|
||||
|
||||
const response = await fetch('/tapo/control', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ ip, action })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showFlashMessage(data.message, 'success');
|
||||
|
||||
// Status in lokalen Daten aktualisieren
|
||||
if (outlets[ip]) {
|
||||
outlets[ip].status = action;
|
||||
outlets[ip].reachable = true;
|
||||
updateOutletDisplay(ip, outlets[ip]);
|
||||
updateStatistics();
|
||||
}
|
||||
} else {
|
||||
showFlashMessage(data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashMessage('Fehler bei der Steckdosen-Steuerung: ' + error.message, 'error');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshOutletStatus(ip) {
|
||||
try {
|
||||
const response = await fetch(`/tapo/status/${ip}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (outlets[ip]) {
|
||||
outlets[ip].status = data.status;
|
||||
outlets[ip].reachable = data.reachable;
|
||||
updateOutletDisplay(ip, outlets[ip]);
|
||||
updateStatistics();
|
||||
}
|
||||
} else {
|
||||
showFlashMessage(`Fehler beim Status-Check für ${ip}: ${data.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashMessage('Fehler beim Status-Check: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAllStatus() {
|
||||
showLoading('Status aller Steckdosen wird aktualisiert...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/tapo/all-status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Lokale Daten aktualisieren
|
||||
for (const [ip, status] of Object.entries(data.outlets)) {
|
||||
if (outlets[ip]) {
|
||||
outlets[ip].status = status.status;
|
||||
outlets[ip].reachable = status.reachable;
|
||||
updateOutletDisplay(ip, outlets[ip]);
|
||||
}
|
||||
}
|
||||
updateStatistics();
|
||||
showFlashMessage('Status aller Steckdosen aktualisiert', 'success');
|
||||
} else {
|
||||
showFlashMessage('Fehler beim Aktualisieren: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashMessage('Fehler beim Status-Update: ' + error.message, 'error');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection(ip) {
|
||||
try {
|
||||
showLoading(`Verbindung zu ${ip} wird getestet...`);
|
||||
|
||||
const response = await fetch(`/tapo/test/${ip}`, { method: 'POST' });
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const testResult = data.test_result;
|
||||
if (testResult.success) {
|
||||
showFlashMessage(`✅ Verbindung zu ${ip} erfolgreich!`, 'success');
|
||||
} else {
|
||||
showFlashMessage(`❌ Verbindung zu ${ip} fehlgeschlagen: ${testResult.error}`, 'error');
|
||||
}
|
||||
} else {
|
||||
showFlashMessage(`Verbindungstest fehlgeschlagen: ${data.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashMessage('Fehler beim Verbindungstest: ' + error.message, 'error');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async function discoverOutlets() {
|
||||
try {
|
||||
showLoading('Suche nach neuen Tapo-Steckdosen...');
|
||||
|
||||
const response = await fetch('/tapo/discover', { method: 'POST' });
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showFlashMessage(data.message, 'success');
|
||||
|
||||
// Seite neu laden wenn neue Steckdosen gefunden wurden
|
||||
if (data.discovered_count > 0) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
showFlashMessage('Fehler bei der Erkennung: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showFlashMessage('Fehler bei der Steckdosen-Suche: ' + error.message, 'error');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-Refresh alle 30 Sekunden
|
||||
setInterval(() => {
|
||||
if (Object.keys(outlets).length > 0) {
|
||||
refreshAllStatus();
|
||||
}
|
||||
// Minimales Auto-Refresh alle 30 Sekunden
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 30000);
|
||||
|
||||
// Initial statistics update
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateStatistics();
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
Reference in New Issue
Block a user