Files
Projektarbeit-MYP/backend/templates/energy_dashboard.html
Till Tomczak 48a9783ce2 🎉 Große Änderungen vorgenommen! Die folgenden Dateien wurden aktualisiert:
- 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/
2025-06-19 22:57:29 +02:00

494 lines
17 KiB
HTML

{% extends "base.html" %}
{% block title %}Energiemonitoring - Mercedes-Benz TBA Marienfelde{% endblock %}
{% block head %}
{{ super() }}
<!-- Chart.js für Energiediagramme -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Enhanced CSS für Energiemonitoring mit Dark/Light Mode -->
<style>
.energy-card {
background: var(--gradient-card);
border: 1px solid var(--border-primary);
border-radius: 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
box-shadow: var(--shadow-card);
}
.energy-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-card-hover);
border-color: var(--border-hover);
}
.energy-metric {
background: var(--gradient-button);
color: var(--text-on-primary);
border-radius: 12px;
padding: 1rem;
text-align: center;
box-shadow: var(--shadow-sm);
}
.dark .energy-metric {
background: var(--gradient-button);
color: var(--text-on-primary);
}
/* Hero Section Optimization */
.energy-hero {
background: var(--gradient-primary);
color: var(--text-primary);
}
.dark .energy-hero {
background: var(--gradient-primary);
}
/* Status Indicator */
.status-indicator-online {
background: var(--text-success);
}
.status-indicator-offline {
background: var(--text-error);
}
/* Live Indicator */
.live-badge {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
color: var(--text-primary);
}
/* Chart Containers */
.chart-container {
background: var(--bg-card);
border-radius: 16px;
padding: 1.5rem;
box-shadow: var(--shadow-card);
}
/* Table Styling */
.energy-table {
background: var(--bg-card);
border: 1px solid var(--border-primary);
border-radius: 12px;
overflow: hidden;
}
.energy-table th {
background: var(--bg-tertiary);
color: var(--text-tertiary);
border-bottom: 1px solid var(--border-primary);
}
.energy-table td {
color: var(--text-secondary);
border-bottom: 1px solid var(--border-primary);
}
.energy-table tbody tr:hover {
background: var(--hover-card);
}
/* Status Badges */
.status-badge-online {
background: rgba(16, 185, 129, 0.1);
color: var(--text-success);
border: 1px solid var(--border-success);
}
.status-badge-offline {
background: rgba(239, 68, 68, 0.1);
color: var(--text-error);
border: 1px solid var(--border-error);
}
.dark .status-badge-online {
background: rgba(16, 185, 129, 0.2);
}
.dark .status-badge-offline {
background: rgba(239, 68, 68, 0.2);
}
/* Error State */
.error-card {
background: rgba(239, 68, 68, 0.05);
border: 1px solid var(--border-error);
color: var(--text-error);
}
.dark .error-card {
background: rgba(239, 68, 68, 0.1);
}
/* Loading States */
.loading-skeleton {
background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-secondary) 50%, var(--bg-tertiary) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Responsive optimizations */
@media (max-width: 768px) {
.energy-card {
margin: 0.5rem;
padding: 1rem;
border-radius: 12px;
}
.energy-metric {
padding: 0.75rem;
border-radius: 8px;
}
}
</style>
{% endblock %}
{% block content %}
<div class="min-h-screen" style="background: var(--gradient-primary);">
<!-- Hero Header -->
<div class="energy-hero relative overflow-hidden rounded-3xl mx-4 mt-4">
<div class="absolute inset-0" style="background: var(--bg-overlay);"></div>
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent"></div>
<!-- Live Status Indicator -->
<div class="absolute top-4 right-4 flex items-center space-x-2">
<div class="live-badge flex items-center space-x-2 rounded-full px-3 py-1">
<div id="live-indicator" class="w-2 h-2 status-indicator-online rounded-full animate-pulse"></div>
<span class="text-sm font-medium">Live</span>
</div>
<div class="live-badge rounded-full px-3 py-1">
<span id="live-time" class="text-sm font-medium"></span>
</div>
</div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div class="text-center">
<!-- Mercedes-Benz Logo -->
<div class="inline-flex items-center justify-center w-20 h-20 live-badge rounded-full mb-6 logo-adaptive">
<svg class="w-10 h-10" style="color: var(--text-primary);" viewBox="0 0 80 80" fill="currentColor">
<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>
</div>
<h1 class="text-5xl md:text-6xl font-bold mb-4 tracking-tight" style="color: var(--text-primary);">
Energiemonitoring
</h1>
<p class="text-xl md:text-2xl max-w-3xl mx-auto leading-relaxed" style="color: var(--text-secondary);">
Überwachung des Energieverbrauchs aller 3D-Drucker mit TP-Link Tapo Smart Plugs
</p>
</div>
</div>
</div>
<!-- Main Content -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{% if stats.error %}
<!-- Error State -->
<div class="error-card rounded-xl p-6 mb-8">
<div class="flex items-center">
<svg class="w-6 h-6 mr-3" 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>
<h3 class="text-lg font-semibold" style="color: var(--text-error);">Energiemonitoring nicht verfügbar</h3>
<p class="mt-1" style="color: var(--text-error);">{{ stats.error }}</p>
</div>
</div>
</div>
{% else %}
<!-- Energy Overview Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Total Power Consumption -->
<div class="energy-card p-6">
<div class="energy-metric">
<h3 class="text-sm font-semibold uppercase tracking-wider mb-2">Gesamtverbrauch</h3>
<p class="text-3xl font-bold" id="total-power">{{ stats.total_power_consumption or '0.0' }} kWh</p>
</div>
</div>
<!-- Current Power -->
<div class="energy-card p-6">
<div class="energy-metric">
<h3 class="text-sm font-semibold uppercase tracking-wider mb-2">Aktuelle Leistung</h3>
<p class="text-3xl font-bold" id="current-power">{{ stats.current_power or '0.0' }} W</p>
</div>
</div>
<!-- Active Devices -->
<div class="energy-card p-6">
<div class="energy-metric">
<h3 class="text-sm font-semibold uppercase tracking-wider mb-2">Aktive Geräte</h3>
<p class="text-3xl font-bold" id="active-devices">{{ stats.active_devices or '0' }}</p>
</div>
</div>
<!-- Energy Cost -->
<div class="energy-card p-6">
<div class="energy-metric">
<h3 class="text-sm font-semibold uppercase tracking-wider mb-2">Kosten (€/kWh 0.30)</h3>
<p class="text-3xl font-bold" id="energy-cost">{{ "%.2f"|format((stats.total_power_consumption or 0) * 0.30) }} €</p>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Power Consumption Over Time -->
<div class="energy-card p-6">
<h3 class="text-xl font-bold mb-4" style="color: var(--text-primary);">Verbrauch der letzten 24 Stunden</h3>
<div class="relative h-80">
<canvas id="powerChart"></canvas>
</div>
</div>
<!-- Device Comparison -->
<div class="energy-card p-6">
<h3 class="text-xl font-bold mb-4" style="color: var(--text-primary);">Verbrauch nach Gerät</h3>
<div class="relative h-80">
<canvas id="deviceChart"></canvas>
</div>
</div>
</div>
<!-- Device List -->
<div class="energy-card p-6">
<h3 class="text-xl font-bold mb-6" style="color: var(--text-primary);">Geräteübersicht</h3>
<div class="overflow-x-auto">
<table class="energy-table min-w-full">
<thead>
<tr>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Gerät</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Aktuelle Leistung</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Tagesverbrauch</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Aktionen</th>
</tr>
</thead>
<tbody id="device-table-body">
<!-- Dynamisch geladen via JavaScript -->
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
</div>
<!-- JavaScript für Live-Updates und Charts -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Live-Zeit aktualisieren
function updateLiveTime() {
const timeElement = document.getElementById('live-time');
if (timeElement) {
timeElement.textContent = new Date().toLocaleTimeString('de-DE');
}
}
// Alle Sekunde aktualisieren
setInterval(updateLiveTime, 1000);
updateLiveTime();
// Charts initialisieren
initializeCharts();
// Geräteliste laden
loadDeviceList();
// Auto-Update alle 30 Sekunden
setInterval(() => {
loadEnergyData();
loadDeviceList();
}, 30000);
});
function initializeCharts() {
// Power Chart
const powerCtx = document.getElementById('powerChart');
if (powerCtx) {
new Chart(powerCtx, {
type: 'line',
data: {
labels: [], // Wird dynamisch geladen
datasets: [{
label: 'Leistung (W)',
data: [],
borderColor: '#0073ce',
backgroundColor: 'rgba(0, 115, 206, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Leistung (W)'
}
},
x: {
title: {
display: true,
text: 'Zeit'
}
}
}
}
});
}
// Device Chart
const deviceCtx = document.getElementById('deviceChart');
if (deviceCtx) {
new Chart(deviceCtx, {
type: 'doughnut',
data: {
labels: [], // Wird dynamisch geladen
datasets: [{
data: [],
backgroundColor: [
'#0073ce',
'#005ba3',
'#aaa9ad',
'#5e5e5e',
'#f59e0b',
'#10b981'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
}
async function loadEnergyData() {
try {
const response = await fetch('/api/energy/overview');
if (response.ok) {
const data = await response.json();
updateEnergyMetrics(data);
}
} catch (error) {
console.error('Fehler beim Laden der Energiedaten:', error);
}
}
async function loadDeviceList() {
try {
const response = await fetch('/api/energy/devices');
if (response.ok) {
const data = await response.json();
updateDeviceTable(data.devices || []);
}
} catch (error) {
console.error('Fehler beim Laden der Geräteliste:', error);
}
}
function updateEnergyMetrics(data) {
if (data.total_power_consumption !== undefined) {
document.getElementById('total-power').textContent = data.total_power_consumption.toFixed(1) + ' kWh';
}
if (data.current_power !== undefined) {
document.getElementById('current-power').textContent = data.current_power.toFixed(1) + ' W';
}
if (data.active_devices !== undefined) {
document.getElementById('active-devices').textContent = data.active_devices;
}
if (data.total_power_consumption !== undefined) {
const cost = (data.total_power_consumption * 0.30).toFixed(2);
document.getElementById('energy-cost').textContent = cost + ' €';
}
}
function updateDeviceTable(devices) {
const tbody = document.getElementById('device-table-body');
if (!tbody) return;
tbody.innerHTML = '';
if (devices.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="5" class="px-6 py-4 text-center" style="color: var(--text-muted);">
Keine Geräte gefunden
</td>
</tr>
`;
return;
}
devices.forEach(device => {
const row = document.createElement('tr');
row.className = '';
row.style.transition = 'background-color 0.2s ease';
row.addEventListener('mouseenter', () => row.style.backgroundColor = 'var(--hover-card)');
row.addEventListener('mouseleave', () => row.style.backgroundColor = 'transparent');
const statusClass = device.status === 'online' ? 'status-badge-online' : 'status-badge-offline';
const statusText = device.status === 'online' ? 'Online' : 'Offline';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="text-sm font-medium" style="color: var(--text-primary);">${device.name}</div>
<div class="text-sm" style="color: var(--text-tertiary);">${device.model || ''}</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusClass}">
${statusText}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm" style="color: var(--text-primary);">
${device.current_power || '0.0'} W
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm" style="color: var(--text-primary);">
${device.daily_consumption || '0.0'} kWh
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="/energy/device/${device.id}" style="color: var(--text-accent); transition: color 0.2s ease;" onmouseover="this.style.color='var(--text-link-hover)'" onmouseout="this.style.color='var(--text-accent)'">
Details anzeigen
</a>
</td>
`;
tbody.appendChild(row);
});
}
</script>
{% endblock %}