602 lines
23 KiB
HTML
602 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Energiemonitoring - Mercedes-Benz MYP Platform{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
/* Energiemonitoring Dashboard Styles */
|
|
.energy-card {
|
|
background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.8) 100%);
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|
border-radius: 16px;
|
|
backdrop-filter: blur(20px);
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .energy-card {
|
|
background: linear-gradient(135deg, rgba(51,65,85,0.9) 0%, rgba(51,65,85,0.8) 100%);
|
|
border: 1px solid rgba(71,85,105,0.3);
|
|
}
|
|
|
|
.energy-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 12px 40px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.energy-metric {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.device-status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
.device-status-online {
|
|
background-color: #10b981;
|
|
box-shadow: 0 0 10px rgba(16, 185, 129, 0.6);
|
|
}
|
|
|
|
.device-status-offline {
|
|
background-color: #ef4444;
|
|
box-shadow: 0 0 10px rgba(239, 68, 68, 0.6);
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 300px;
|
|
width: 100%;
|
|
}
|
|
|
|
.power-gradient {
|
|
background: linear-gradient(90deg, #10b981 0%, #f59e0b 50%, #ef4444 100%);
|
|
height: 6px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-slate-900 dark:via-slate-900 dark:to-slate-800">
|
|
|
|
<!-- Header Section -->
|
|
<div class="bg-gradient-to-r from-blue-600 via-purple-600 to-indigo-600 text-white">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-4xl font-bold tracking-tight">🔋 Energiemonitoring</h1>
|
|
<p class="mt-2 text-xl text-blue-100">
|
|
Überwachen Sie den Energieverbrauch Ihrer 3D-Drucker in Echtzeit
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center space-x-4">
|
|
<button id="refreshData" class="bg-white/20 hover:bg-white/30 px-4 py-2 rounded-xl backdrop-blur-sm transition-all duration-300">
|
|
<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>
|
|
</button>
|
|
<button id="exportData" class="bg-white/20 hover:bg-white/30 px-4 py-2 rounded-xl backdrop-blur-sm transition-all duration-300">
|
|
<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="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10">
|
|
|
|
<!-- KPI Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<!-- Gesamtverbrauch -->
|
|
<div class="energy-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl">
|
|
<svg class="w-6 h-6 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>
|
|
<div class="text-right">
|
|
<div id="totalPower" class="energy-metric">{{ stats.total_current_power or 0 }}W</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Gesamtverbrauch</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<div class="device-status-indicator device-status-online"></div>
|
|
<span class="text-xs text-green-600 dark:text-green-400 font-medium">Live-Daten</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Online Geräte -->
|
|
<div class="energy-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-green-500 to-green-600 rounded-xl">
|
|
<svg class="w-6 h-6 text-white" 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 id="onlineDevices" class="energy-metric">{{ stats.online_devices or 0 }}</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Online Geräte</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm text-slate-600 dark:text-slate-300">
|
|
von <span id="totalDevices">{{ stats.total_devices or 0 }}</span> Geräten
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Heute Verbrauch -->
|
|
<div class="energy-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl">
|
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div id="todayEnergy" class="energy-metric">{{ stats.total_today_energy or 0 }}Wh</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Heute</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm text-slate-600 dark:text-slate-300">
|
|
Ø <span id="avgTodayEnergy">{{ stats.avg_today_energy or 0 }}</span>Wh pro Gerät
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Monatsverbrauch -->
|
|
<div class="energy-card p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl">
|
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
</div>
|
|
<div class="text-right">
|
|
<div id="monthEnergy" class="energy-metric">{{ stats.total_month_energy or 0 }}Wh</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Diesen Monat</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm text-slate-600 dark:text-slate-300">
|
|
Ø <span id="avgMonthEnergy">{{ stats.avg_month_energy or 0 }}</span>Wh pro Gerät
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
|
<!-- Verbrauchstrend Chart -->
|
|
<div class="energy-card p-6">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">📈 Verbrauchstrend</h3>
|
|
<select id="periodSelector" class="px-3 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm">
|
|
<option value="today">Heute (24h)</option>
|
|
<option value="week">Diese Woche</option>
|
|
<option value="month">Dieser Monat</option>
|
|
<option value="year">Dieses Jahr</option>
|
|
</select>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="consumptionChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Geräteverbrauch Chart -->
|
|
<div class="energy-card p-6">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">🔌 Geräteverbrauch</h3>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Live-Verbrauch</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="deviceChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device List -->
|
|
<div class="energy-card p-6 mb-8">
|
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">🖨️ Geräteübersicht</h3>
|
|
<div id="deviceList" class="space-y-4">
|
|
<!-- Wird dynamisch gefüllt -->
|
|
<div class="flex justify-center items-center py-8">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div id="loadingOverlay" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center hidden">
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 flex items-center space-x-4">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<span class="text-slate-900 dark:text-white">Lade Energiedaten...</span>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<!-- Chart.js -->
|
|
<script src="{{ url_for('static', filename='js/charts/chart.min.js') }}"></script>
|
|
|
|
<script>
|
|
/**
|
|
* MYP Energiemonitoring Dashboard
|
|
* Echtzeit-Überwachung des Energieverbrauchs von Tapo P110 Smart Plugs
|
|
*/
|
|
|
|
class EnergyMonitoringDashboard {
|
|
constructor() {
|
|
this.currentPeriod = 'today';
|
|
this.charts = {};
|
|
this.updateInterval = null;
|
|
this.data = {};
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupEventListeners();
|
|
this.loadInitialData();
|
|
this.startAutoUpdate();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Period Selector
|
|
document.getElementById('periodSelector').addEventListener('change', (e) => {
|
|
this.currentPeriod = e.target.value;
|
|
this.loadConsumptionData();
|
|
});
|
|
|
|
// Refresh Button
|
|
document.getElementById('refreshData').addEventListener('click', () => {
|
|
this.loadAllData();
|
|
});
|
|
|
|
// Export Button
|
|
document.getElementById('exportData').addEventListener('click', () => {
|
|
this.exportData();
|
|
});
|
|
}
|
|
|
|
async loadInitialData() {
|
|
this.showLoading();
|
|
await this.loadAllData();
|
|
this.hideLoading();
|
|
}
|
|
|
|
async loadAllData() {
|
|
try {
|
|
await Promise.all([
|
|
this.loadDashboardData(),
|
|
this.loadConsumptionData(),
|
|
this.loadDeviceData()
|
|
]);
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Energiedaten:', error);
|
|
this.showError('Fehler beim Laden der Energiedaten');
|
|
}
|
|
}
|
|
|
|
async loadDashboardData() {
|
|
try {
|
|
const response = await fetch('/api/energy/dashboard');
|
|
if (!response.ok) throw new Error('Dashboard-Daten konnten nicht geladen werden');
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
this.data.dashboard = result.data;
|
|
this.updateKPIs(result.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('Dashboard-Daten-Fehler:', error);
|
|
}
|
|
}
|
|
|
|
async loadConsumptionData() {
|
|
try {
|
|
const response = await fetch(`/api/energy/statistics?period=${this.currentPeriod}`);
|
|
if (!response.ok) throw new Error('Verbrauchsdaten konnten nicht geladen werden');
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
this.data.consumption = result.data;
|
|
this.updateConsumptionChart(result.data.chart_data);
|
|
}
|
|
} catch (error) {
|
|
console.error('Verbrauchsdaten-Fehler:', error);
|
|
}
|
|
}
|
|
|
|
async loadDeviceData() {
|
|
try {
|
|
const response = await fetch('/api/energy/live');
|
|
if (!response.ok) throw new Error('Gerätedaten konnten nicht geladen werden');
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
this.data.devices = result.data;
|
|
this.updateDeviceChart(result.data.devices);
|
|
this.updateDeviceList(result.data.devices);
|
|
}
|
|
} catch (error) {
|
|
console.error('Gerätedaten-Fehler:', error);
|
|
}
|
|
}
|
|
|
|
updateKPIs(dashboardData) {
|
|
const overview = dashboardData.overview || {};
|
|
const consumption = dashboardData.current_consumption || {};
|
|
const totals = dashboardData.energy_totals || {};
|
|
|
|
// KPI Updates
|
|
document.getElementById('totalPower').textContent = `${consumption.total_power || 0}W`;
|
|
document.getElementById('onlineDevices').textContent = overview.online_devices || 0;
|
|
document.getElementById('totalDevices').textContent = overview.total_devices || 0;
|
|
document.getElementById('todayEnergy').textContent = `${totals.today_total || 0}Wh`;
|
|
document.getElementById('avgTodayEnergy').textContent = totals.today_average || 0;
|
|
document.getElementById('monthEnergy').textContent = `${totals.month_total || 0}Wh`;
|
|
document.getElementById('avgMonthEnergy').textContent = totals.month_average || 0;
|
|
}
|
|
|
|
updateConsumptionChart(chartData) {
|
|
const canvas = document.getElementById('consumptionChart');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Destroy existing chart
|
|
if (this.charts.consumption) {
|
|
this.charts.consumption.destroy();
|
|
}
|
|
|
|
this.charts.consumption = new Chart(ctx, {
|
|
type: 'line',
|
|
data: chartData,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top'
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
callbacks: {
|
|
label: function(context) {
|
|
return `${context.dataset.label}: ${context.parsed.y}Wh`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
display: true,
|
|
grid: {
|
|
color: 'rgba(0,0,0,0.1)'
|
|
}
|
|
},
|
|
y: {
|
|
display: true,
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0,0,0,0.1)'
|
|
},
|
|
ticks: {
|
|
callback: function(value) {
|
|
return value + 'Wh';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
mode: 'nearest',
|
|
axis: 'x',
|
|
intersect: false
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
updateDeviceChart(devices) {
|
|
const canvas = document.getElementById('deviceChart');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Destroy existing chart
|
|
if (this.charts.devices) {
|
|
this.charts.devices.destroy();
|
|
}
|
|
|
|
const onlineDevices = devices.filter(d => d.online && d.power > 0);
|
|
|
|
if (onlineDevices.length === 0) {
|
|
// Zeige "Keine Daten" Nachricht
|
|
ctx.font = '16px Arial';
|
|
ctx.fillStyle = '#64748b';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('Keine aktiven Geräte', canvas.width / 2, canvas.height / 2);
|
|
return;
|
|
}
|
|
|
|
const chartData = {
|
|
labels: onlineDevices.map(d => d.name),
|
|
datasets: [{
|
|
label: 'Aktueller Verbrauch (W)',
|
|
data: onlineDevices.map(d => d.power),
|
|
backgroundColor: [
|
|
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
|
|
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
|
],
|
|
borderColor: '#ffffff',
|
|
borderWidth: 2
|
|
}]
|
|
};
|
|
|
|
this.charts.devices = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: chartData,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
padding: 20,
|
|
usePointStyle: true
|
|
}
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
const device = onlineDevices[context.dataIndex];
|
|
return `${device.name}: ${device.power}W`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
updateDeviceList(devices) {
|
|
const container = document.getElementById('deviceList');
|
|
|
|
if (devices.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-center py-8 text-slate-500 dark:text-slate-400">
|
|
<svg class="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 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>
|
|
<p>Keine Energiemonitoring-Geräte gefunden</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = devices.map(device => `
|
|
<div class="flex items-center justify-between p-4 bg-white/60 dark:bg-slate-700/60 rounded-xl border border-slate-200 dark:border-slate-600">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="device-status-indicator ${device.online ? 'device-status-online' : 'device-status-offline'}"></div>
|
|
<div>
|
|
<h4 class="font-semibold text-slate-900 dark:text-white">${device.name}</h4>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400">ID: ${device.id}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-xl font-bold text-slate-900 dark:text-white">
|
|
${device.power}W
|
|
</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">
|
|
${device.online ? 'Online' : 'Offline'}
|
|
</div>
|
|
</div>
|
|
<div class="w-16">
|
|
<div class="power-gradient"></div>
|
|
<div class="text-xs text-center mt-1 text-slate-500 dark:text-slate-400">
|
|
${Math.round((device.power / 100) * 100)}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
startAutoUpdate() {
|
|
// Update alle 30 Sekunden
|
|
this.updateInterval = setInterval(() => {
|
|
this.loadDeviceData(); // Nur Live-Daten für bessere Performance
|
|
}, 30000);
|
|
}
|
|
|
|
stopAutoUpdate() {
|
|
if (this.updateInterval) {
|
|
clearInterval(this.updateInterval);
|
|
this.updateInterval = null;
|
|
}
|
|
}
|
|
|
|
async exportData() {
|
|
try {
|
|
const response = await fetch(`/api/energy/statistics?period=${this.currentPeriod}&format=csv`);
|
|
if (!response.ok) throw new Error('Export fehlgeschlagen');
|
|
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `energy-monitoring-${this.currentPeriod}-${new Date().toISOString().split('T')[0]}.csv`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
this.showSuccess('Daten erfolgreich exportiert');
|
|
|
|
} catch (error) {
|
|
console.error('Export-Fehler:', error);
|
|
this.showError('Fehler beim Exportieren der Daten');
|
|
}
|
|
}
|
|
|
|
showLoading() {
|
|
document.getElementById('loadingOverlay').classList.remove('hidden');
|
|
}
|
|
|
|
hideLoading() {
|
|
document.getElementById('loadingOverlay').classList.add('hidden');
|
|
}
|
|
|
|
showError(message) {
|
|
// Verwende bestehende Flash-Message-Funktion falls verfügbar
|
|
if (typeof showFlashMessage === 'function') {
|
|
showFlashMessage(message, 'error');
|
|
} else {
|
|
alert(message);
|
|
}
|
|
}
|
|
|
|
showSuccess(message) {
|
|
if (typeof showFlashMessage === 'function') {
|
|
showFlashMessage(message, 'success');
|
|
} else {
|
|
alert(message);
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.stopAutoUpdate();
|
|
|
|
// Charts zerstören
|
|
Object.values(this.charts).forEach(chart => {
|
|
if (chart) chart.destroy();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Dashboard initialisieren
|
|
let energyDashboard;
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
energyDashboard = new EnergyMonitoringDashboard();
|
|
});
|
|
|
|
// Cleanup bei Seitenwechsel
|
|
window.addEventListener('beforeunload', () => {
|
|
if (energyDashboard) {
|
|
energyDashboard.destroy();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |