- backend/logs/admin/admin.log - backend/logs/admin_api/admin_api.log - backend/logs/api/api.log - backend/logs/app/app.log - backend/logs/auth/auth.log - backend/logs/calendar/calendar.log - backend/
643 lines
32 KiB
HTML
643 lines
32 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Tapo-Steckdosen-Monitoring - Admin | MYP Platform{% endblock %}
|
|
|
|
{% block head %}
|
|
{{ super() }}
|
|
<!-- CSRF Token für AJAX-Anfragen -->
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
|
|
<!-- Auto-refresh für Live-Monitoring -->
|
|
<meta http-equiv="refresh" content="30">
|
|
|
|
<style>
|
|
/* Optimierte Tapo-Monitoring Styles mit Unified CSS Variables */
|
|
.status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
margin-right: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.status-online {
|
|
background-color: var(--text-success);
|
|
box-shadow: 0 0 6px rgba(16, 185, 129, 0.3);
|
|
}
|
|
.status-offline {
|
|
background-color: var(--text-muted);
|
|
box-shadow: 0 0 6px rgba(107, 114, 128, 0.2);
|
|
}
|
|
.status-error {
|
|
background-color: var(--text-error);
|
|
box-shadow: 0 0 6px rgba(239, 68, 68, 0.3);
|
|
}
|
|
|
|
.monitoring-card {
|
|
background: var(--gradient-card);
|
|
border: 1px solid var(--border-primary);
|
|
box-shadow: var(--shadow-card);
|
|
backdrop-filter: var(--glass-blur);
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.monitoring-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-card-hover);
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.pulse-animation {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
.health-excellent {
|
|
border-left: 4px solid var(--text-success);
|
|
background: linear-gradient(90deg, rgba(16, 185, 129, 0.02) 0%, transparent 15%);
|
|
}
|
|
.health-good {
|
|
border-left: 4px solid var(--text-warning);
|
|
background: linear-gradient(90deg, rgba(245, 158, 11, 0.02) 0%, transparent 15%);
|
|
}
|
|
.health-poor {
|
|
border-left: 4px solid var(--text-error);
|
|
background: linear-gradient(90deg, rgba(239, 68, 68, 0.02) 0%, transparent 15%);
|
|
}
|
|
|
|
/* Loading Overlay mit Unified Variables */
|
|
.loading-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: var(--bg-overlay);
|
|
backdrop-filter: var(--glass-blur);
|
|
z-index: 9999;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Dark Mode Optimierungen */
|
|
.dark .status-online {
|
|
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
|
}
|
|
|
|
.dark .status-error {
|
|
box-shadow: 0 0 8px rgba(239, 68, 68, 0.4);
|
|
}
|
|
|
|
.dark .health-excellent {
|
|
background: linear-gradient(90deg, rgba(16, 185, 129, 0.08) 0%, transparent 15%);
|
|
}
|
|
|
|
.dark .health-good {
|
|
background: linear-gradient(90deg, rgba(245, 158, 11, 0.08) 0%, transparent 15%);
|
|
}
|
|
|
|
.dark .health-poor {
|
|
background: linear-gradient(90deg, rgba(239, 68, 68, 0.08) 0%, transparent 15%);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Loading Overlay mit Unified Variables -->
|
|
<div id="loading-overlay" class="loading-overlay">
|
|
<div class="modal">
|
|
<div class="flex items-center space-x-4 p-8">
|
|
<div class="spinner"></div>
|
|
<span class="text-lg font-medium" style="color: var(--text-primary);">Tapo-Steckdosen werden überprüft...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hero Header mit Mercedes-Branding -->
|
|
<div class="relative overflow-hidden text-white rounded-3xl mx-4 mb-8" style="background: linear-gradient(135deg, var(--mb-primary) 0%, var(--mb-primary-dark) 50%, var(--mb-black) 100%);">
|
|
<div class="absolute inset-0" style="background: rgba(0, 0, 0, 0.1);"></div>
|
|
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
<div class="text-center">
|
|
<!-- Mercedes Icon -->
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full mb-6 border" style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(8px); border-color: rgba(255, 255, 255, 0.2);">
|
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<h1 class="text-4xl md:text-5xl font-bold mb-4 tracking-tight text-white">
|
|
Tapo-Steckdosen-Monitoring
|
|
</h1>
|
|
<p class="text-xl max-w-3xl mx-auto leading-relaxed" style="color: rgba(255, 255, 255, 0.9);">
|
|
Real-Time-Überwachung und Verwaltung aller TP-Link Tapo Smart Plugs im TBA Mercedes-Benz System
|
|
</p>
|
|
|
|
<!-- Quick Stats mit Mercedes-Styling -->
|
|
<div class="flex flex-wrap justify-center gap-6 mt-8">
|
|
<div class="rounded-xl px-4 py-2 border" style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(8px); border-color: rgba(255, 255, 255, 0.2);">
|
|
<div class="text-2xl font-bold text-white">{{ stats.printers_with_tapo }}</div>
|
|
<div class="text-sm" style="color: rgba(255, 255, 255, 0.8);">Konfigurierte Steckdosen</div>
|
|
</div>
|
|
<div class="rounded-xl px-4 py-2 border" style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(8px); border-color: rgba(255, 255, 255, 0.2);">
|
|
<div class="text-2xl font-bold" style="color: var(--text-success);">{{ stats.online_count }}</div>
|
|
<div class="text-sm" style="color: rgba(255, 255, 255, 0.8);">Online</div>
|
|
</div>
|
|
<div class="rounded-xl px-4 py-2 border" style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(8px); border-color: rgba(255, 255, 255, 0.2);">
|
|
<div class="text-2xl font-bold text-white">{{ stats.coverage_percentage }}%</div>
|
|
<div class="text-sm" style="color: rgba(255, 255, 255, 0.8);">Abdeckung</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Overview Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
<!-- Gesamt-Status mit Unified Variables -->
|
|
<div class="monitoring-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 rounded-xl" style="background: rgba(0, 115, 206, 0.1); border: 1px solid rgba(0, 115, 206, 0.2);">
|
|
<svg class="w-6 h-6" style="color: var(--mb-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold" style="color: var(--text-primary);">{{ stats.printers_with_tapo }}</div>
|
|
<div class="text-sm" style="color: var(--text-secondary);">Smart Plugs</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<div class="status-indicator {{ 'status-online' if stats.tapo_available else 'status-error' }}"></div>
|
|
<span class="text-sm font-medium" style="color: {{ 'var(--text-success)' if stats.tapo_available else 'var(--text-error)' }};">
|
|
{{ 'System verfügbar' if stats.tapo_available else 'System offline' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Online Steckdosen mit Unified Variables -->
|
|
<div class="monitoring-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 rounded-xl" style="background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.2);">
|
|
<svg class="w-6 h-6" style="color: var(--text-success);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold" style="color: var(--text-success);">{{ stats.online_count }}</div>
|
|
<div class="text-sm" style="color: var(--text-secondary);">Eingeschaltet</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full rounded-full h-2" style="background: var(--bg-tertiary);">
|
|
<div class="h-2 rounded-full transition-all duration-500" style="background: var(--text-success); width: {{ (stats.online_count / stats.printers_with_tapo * 100) if stats.printers_with_tapo else 0 }}%;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Offline Steckdosen mit Unified Variables -->
|
|
<div class="monitoring-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 rounded-xl" style="background: rgba(107, 114, 128, 0.1); border: 1px solid rgba(107, 114, 128, 0.2);">
|
|
<svg class="w-6 h-6" style="color: var(--text-muted);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold" style="color: var(--text-muted);">{{ stats.offline_count }}</div>
|
|
<div class="text-sm" style="color: var(--text-secondary);">Ausgeschaltet</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full rounded-full h-2" style="background: var(--bg-tertiary);">
|
|
<div class="h-2 rounded-full transition-all duration-500" style="background: var(--text-muted); width: {{ (stats.offline_count / stats.printers_with_tapo * 100) if stats.printers_with_tapo else 0 }}%;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fehlerhafte Steckdosen mit Unified Variables -->
|
|
<div class="monitoring-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 rounded-xl" style="background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2);">
|
|
<svg class="w-6 h-6" style="color: var(--text-error);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold" style="color: var(--text-error);">{{ stats.error_count }}</div>
|
|
<div class="text-sm" style="color: var(--text-secondary);">Nicht erreichbar</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full rounded-full h-2" style="background: var(--bg-tertiary);">
|
|
<div class="h-2 rounded-full transition-all duration-500" style="background: var(--text-error); width: {{ (stats.error_count / stats.printers_with_tapo * 100) if stats.printers_with_tapo else 0 }}%;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Control Panel mit Unified Variables -->
|
|
<div class="monitoring-card p-6 mb-8">
|
|
<div class="flex flex-wrap items-center justify-between gap-4 mb-6">
|
|
<h2 class="text-xl font-semibold" style="color: var(--text-primary);">
|
|
<svg class="w-6 h-6 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100-4m0 4v2m0-6V4"/>
|
|
</svg>
|
|
Massensteuerung
|
|
</h2>
|
|
|
|
<div class="flex flex-wrap gap-3">
|
|
<button id="refresh-status-btn" class="btn-secondary flex items-center space-x-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
|
</svg>
|
|
<span>Status aktualisieren</span>
|
|
</button>
|
|
|
|
<button id="health-check-btn" class="btn-primary flex items-center space-x-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
<span>Gesundheitscheck</span>
|
|
</button>
|
|
|
|
<button id="bulk-on-btn" class="btn-success flex items-center space-x-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
<span>Alle einschalten</span>
|
|
</button>
|
|
|
|
<button id="bulk-off-btn" class="btn-danger flex items-center space-x-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"/>
|
|
</svg>
|
|
<span>Alle ausschalten</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Selection mit Unified Variables -->
|
|
<div class="flex items-center space-x-4 mb-4">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" id="select-all" class="mr-2">
|
|
<span class="text-sm font-medium" style="color: var(--text-primary);">Alle auswählen</span>
|
|
</label>
|
|
<span id="selected-count" class="text-sm" style="color: var(--text-secondary);">0 Steckdosen ausgewählt</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Drucker-Steckdosen-Status mit Unified Variables -->
|
|
<div class="monitoring-card overflow-hidden">
|
|
<div class="p-6" style="border-bottom: 1px solid var(--border-primary);">
|
|
<h2 class="text-xl font-semibold" style="color: var(--text-primary);">
|
|
<svg class="w-6 h-6 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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 2m2 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>
|
|
Drucker-Steckdosen-Status
|
|
</h2>
|
|
<p class="text-sm mt-1" style="color: var(--text-secondary);">
|
|
Live-Status aller konfigurierten Tapo-Steckdosen (automatische Aktualisierung alle 30 Sekunden)
|
|
</p>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
{% if printer_status %}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{% for printer in printer_status %}
|
|
<div class="printer-card rounded-xl p-4 hover-lift transition-all duration-300 {{ 'health-excellent' if printer.plug_reachable and printer.plug_status == 'on' else 'health-good' if printer.plug_reachable else 'health-poor' }}" style="border: 1px solid var(--border-primary);">
|
|
|
|
<!-- Header mit Checkbox -->
|
|
<div class="flex items-start justify-between mb-3">
|
|
<div class="flex items-center space-x-3">
|
|
<input type="checkbox" class="printer-checkbox" value="{{ printer.id }}" data-printer-name="{{ printer.name }}">
|
|
<div>
|
|
<h3 class="font-semibold" style="color: var(--text-primary);">{{ printer.name }}</h3>
|
|
<p class="text-sm" style="color: var(--text-secondary);">{{ printer.model }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Indicator -->
|
|
<div class="flex flex-col items-end">
|
|
<div class="flex items-center space-x-2 mb-1">
|
|
<div class="status-indicator {{ 'status-online' if printer.plug_status == 'on' else 'status-offline' if printer.plug_status == 'off' else 'status-error' }}
|
|
{{ 'pulse-animation' if printer.plug_status == 'on' else '' }}"></div>
|
|
<span class="text-sm font-medium" style="color: {% if printer.plug_status == 'on' %}var(--text-success){% elif printer.plug_status == 'off' %}var(--text-muted){% else %}var(--text-error){% endif %};">
|
|
{% if printer.plug_status == 'on' %}
|
|
Eingeschaltet
|
|
{% elif printer.plug_status == 'off' %}
|
|
Ausgeschaltet
|
|
{% else %}
|
|
Nicht erreichbar
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
<span class="text-xs" style="color: var(--text-muted);">{{ printer.plug_ip }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details mit Unified Variables -->
|
|
<div class="space-y-2 mb-4">
|
|
<div class="flex justify-between text-sm">
|
|
<span style="color: var(--text-secondary);">Standort:</span>
|
|
<span class="font-medium" style="color: var(--text-primary);">{{ printer.location or 'Nicht angegeben' }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span style="color: var(--text-secondary);">Aktive Jobs:</span>
|
|
<span class="font-medium" style="color: var(--text-primary);">{{ printer.active_jobs }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span style="color: var(--text-secondary);">Letzte Prüfung:</span>
|
|
<span class="font-medium" style="color: var(--text-primary);">{{ printer.last_checked.strftime('%H:%M:%S') }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Warnungen mit Unified Variables -->
|
|
{% if printer.has_issues %}
|
|
<div class="rounded-lg p-3 mb-4" style="background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3);">
|
|
<div class="flex items-start space-x-2">
|
|
<svg class="w-5 h-5 flex-shrink-0 mt-0.5" style="color: var(--text-warning);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"/>
|
|
</svg>
|
|
<div>
|
|
<p class="text-sm font-medium" style="color: var(--text-warning);">Achtung:</p>
|
|
<ul class="text-sm mt-1 list-disc list-inside" style="color: var(--text-warning);">
|
|
{% if not printer.plug_reachable %}
|
|
<li>Steckdose nicht erreichbar</li>
|
|
{% endif %}
|
|
{% if printer.active_jobs > 0 %}
|
|
<li>{{ printer.active_jobs }} aktive Job(s) - Vorsicht beim Ausschalten</li>
|
|
{% endif %}
|
|
{% if printer.error %}
|
|
<li>{{ printer.error }}</li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Aktionen -->
|
|
<div class="flex space-x-2">
|
|
{% if printer.plug_reachable %}
|
|
<button class="toggle-single-btn flex-1 px-3 py-2 {{ 'btn-danger' if printer.plug_status == 'on' else 'btn-success' }} text-sm font-medium"
|
|
data-printer-id="{{ printer.id }}"
|
|
data-action="{{ 'off' if printer.plug_status == 'on' else 'on' }}">
|
|
{% if printer.plug_status == 'on' %}
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"/>
|
|
</svg>
|
|
Ausschalten
|
|
{% else %}
|
|
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
Einschalten
|
|
{% endif %}
|
|
</button>
|
|
{% else %}
|
|
<button class="flex-1 px-3 py-2 rounded-lg text-sm font-medium cursor-not-allowed" disabled style="background: var(--text-muted); color: white;">
|
|
Nicht verfügbar
|
|
</button>
|
|
{% endif %}
|
|
|
|
<button class="configure-btn px-3 py-2 rounded-lg text-sm" style="background: var(--mb-primary); color: white;"
|
|
data-printer-id="{{ printer.id }}" title="Konfiguration">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.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>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-12">
|
|
<svg class="w-16 h-16 mx-auto mb-4" style="color: var(--text-muted);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
<h3 class="text-lg font-medium mb-2" style="color: var(--text-primary);">Keine Tapo-Steckdosen konfiguriert</h3>
|
|
<p class="mb-4" style="color: var(--text-secondary);">
|
|
Es wurden noch keine Drucker mit Tapo-Steckdosen eingerichtet.
|
|
</p>
|
|
<a href="{{ url_for('admin.printers_overview') }}" class="btn-primary inline-flex items-center">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
</svg>
|
|
Drucker konfigurieren
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JavaScript für Live-Funktionalität -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// CSRF Token abrufen
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
|
|
// Utility Funktionen
|
|
function showLoading() {
|
|
document.getElementById('loading-overlay').style.display = 'flex';
|
|
}
|
|
|
|
function hideLoading() {
|
|
document.getElementById('loading-overlay').style.display = 'none';
|
|
}
|
|
|
|
function showNotification(message, type = 'info') {
|
|
// Einfaches Notification System mit Toast-Stil
|
|
const notification = document.createElement('div');
|
|
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg z-50 transition-all duration-300`;
|
|
notification.style.background = type === 'success' ? 'var(--text-success)' :
|
|
type === 'error' ? 'var(--text-error)' :
|
|
type === 'warning' ? 'var(--text-warning)' : 'var(--mb-primary)';
|
|
notification.style.color = 'white';
|
|
notification.style.maxWidth = '400px';
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Auto-remove nach 5 Sekunden
|
|
setTimeout(() => {
|
|
notification.style.opacity = '0';
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 5000);
|
|
|
|
// Click to dismiss
|
|
notification.addEventListener('click', () => {
|
|
notification.style.opacity = '0';
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => notification.remove(), 300);
|
|
});
|
|
}
|
|
|
|
// Checkbox Management
|
|
const selectAllCheckbox = document.getElementById('select-all');
|
|
const printerCheckboxes = document.querySelectorAll('.printer-checkbox');
|
|
const selectedCountSpan = document.getElementById('selected-count');
|
|
|
|
function updateSelectedCount() {
|
|
const selectedCount = document.querySelectorAll('.printer-checkbox:checked').length;
|
|
selectedCountSpan.textContent = `${selectedCount} Steckdosen ausgewählt`;
|
|
}
|
|
|
|
selectAllCheckbox.addEventListener('change', function() {
|
|
printerCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = this.checked;
|
|
});
|
|
updateSelectedCount();
|
|
});
|
|
|
|
printerCheckboxes.forEach(checkbox => {
|
|
checkbox.addEventListener('change', updateSelectedCount);
|
|
});
|
|
|
|
// Refresh Status
|
|
document.getElementById('refresh-status-btn').addEventListener('click', function() {
|
|
location.reload(); // Einfache Seiten-Aktualisierung
|
|
});
|
|
|
|
// Health Check
|
|
document.getElementById('health-check-btn').addEventListener('click', async function() {
|
|
showLoading();
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/tapo/health-check', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': csrfToken
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success !== false) {
|
|
const healthStatus = data.overall_status;
|
|
let message = `Gesundheitscheck abgeschlossen:\n`;
|
|
message += `Gesamtstatus: ${healthStatus}\n`;
|
|
message += `Gesunde Steckdosen: ${data.summary.healthy}\n`;
|
|
message += `Warnungen: ${data.summary.warning}\n`;
|
|
message += `Kritische Probleme: ${data.summary.critical}\n`;
|
|
|
|
if (data.recommendations.length > 0) {
|
|
message += `\nEmpfehlungen:\n${data.recommendations.join('\n')}`;
|
|
}
|
|
|
|
showNotification(message, healthStatus === 'healthy' ? 'success' : 'warning');
|
|
} else {
|
|
showNotification('Fehler beim Gesundheitscheck: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Netzwerkfehler beim Gesundheitscheck: ' + error.message, 'error');
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
});
|
|
|
|
// Bulk Actions
|
|
async function performBulkAction(action) {
|
|
const selectedCheckboxes = document.querySelectorAll('.printer-checkbox:checked');
|
|
|
|
if (selectedCheckboxes.length === 0) {
|
|
showNotification('Bitte wählen Sie mindestens eine Steckdose aus.', 'warning');
|
|
return;
|
|
}
|
|
|
|
const printerIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value));
|
|
const printerNames = Array.from(selectedCheckboxes).map(cb => cb.dataset.printerName);
|
|
|
|
if (!confirm(`Möchten Sie ${printerIds.length} Steckdosen wirklich ${action === 'on' ? 'einschalten' : 'ausschalten'}?\n\nBetroffene Drucker:\n${printerNames.join('\n')}`)) {
|
|
return;
|
|
}
|
|
|
|
showLoading();
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/tapo/bulk-control', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': csrfToken
|
|
},
|
|
body: JSON.stringify({
|
|
action: action,
|
|
printer_ids: printerIds
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const summary = data.summary;
|
|
showNotification(`Bulk-Aktion abgeschlossen:\n${summary.success} erfolgreich, ${summary.errors} Fehler`,
|
|
summary.errors === 0 ? 'success' : 'warning');
|
|
|
|
// Seite nach kurzer Verzögerung neu laden
|
|
setTimeout(() => location.reload(), 2000);
|
|
} else {
|
|
showNotification('Fehler bei Bulk-Aktion: ' + data.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Netzwerkfehler bei Bulk-Aktion: ' + error.message, 'error');
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
}
|
|
|
|
document.getElementById('bulk-on-btn').addEventListener('click', () => performBulkAction('on'));
|
|
document.getElementById('bulk-off-btn').addEventListener('click', () => performBulkAction('off'));
|
|
|
|
// Individual Toggle
|
|
document.querySelectorAll('.toggle-single-btn').forEach(button => {
|
|
button.addEventListener('click', async function() {
|
|
const printerId = this.dataset.printerId;
|
|
const action = this.dataset.action;
|
|
|
|
showLoading();
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/tapo/bulk-control', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': csrfToken
|
|
},
|
|
body: JSON.stringify({
|
|
action: action,
|
|
printer_ids: [parseInt(printerId)]
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.results[0].success) {
|
|
showNotification(data.results[0].message, 'success');
|
|
setTimeout(() => location.reload(), 1000);
|
|
} else {
|
|
showNotification('Fehler: ' + (data.results[0].error || data.error), 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Netzwerkfehler: ' + error.message, 'error');
|
|
} finally {
|
|
hideLoading();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Configuration Buttons (placeholder)
|
|
document.querySelectorAll('.configure-btn').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const printerId = this.dataset.printerId;
|
|
showNotification(`Konfiguration für Drucker ${printerId} wird geöffnet...`, 'info');
|
|
// Hier könnte ein Modal oder eine neue Seite geöffnet werden
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|