590 lines
27 KiB
HTML
590 lines
27 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 - Unified Dark/Light Mode -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
|
|
<!-- Total Power Consumption -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 border border-slate-200 dark:border-slate-700 hover:border-blue-300 dark:hover:border-blue-600 transition-all duration-300 hover:shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" 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>
|
|
<h3 class="text-sm font-semibold text-slate-600 dark:text-slate-400 uppercase tracking-wider mb-2">Gesamtverbrauch</h3>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white" id="total-power">{{ stats.total_power_consumption or '0.0' }} kWh</p>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-2">24h Periode</p>
|
|
</div>
|
|
|
|
<!-- Current Power -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 border border-slate-200 dark:border-slate-700 hover:border-green-300 dark:hover:border-green-600 transition-all duration-300 hover:shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-600 dark:text-slate-400 uppercase tracking-wider mb-2">Aktuelle Leistung</h3>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white" id="current-power">{{ stats.current_power or '0.0' }} W</p>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-2">Momentan</p>
|
|
</div>
|
|
|
|
<!-- Active Devices -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 border border-slate-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 transition-all duration-300 hover:shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="w-12 h-12 bg-purple-100 dark:bg-purple-900/30 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-600 dark:text-slate-400 uppercase tracking-wider mb-2">Aktive Geräte</h3>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white" id="active-devices">{{ stats.active_devices or '0' }}</p>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-2">Von 6 Druckern</p>
|
|
</div>
|
|
|
|
<!-- Energy Cost -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 border border-slate-200 dark:border-slate-700 hover:border-orange-300 dark:hover:border-orange-600 transition-all duration-300 hover:shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="w-12 h-12 bg-orange-100 dark:bg-orange-900/30 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-orange-600 dark:text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-600 dark:text-slate-400 uppercase tracking-wider mb-2">Kosten (€/kWh 0.30)</h3>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white" id="energy-cost">{{ "%.2f"|format((stats.total_power_consumption or 0) * 0.30) }} €</p>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-2">24h Periode</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section - Unified Dark/Light Mode -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
|
|
|
<!-- Power Consumption Over Time -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 border border-slate-200 dark:border-slate-700 hover:shadow-lg transition-all duration-300">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Verbrauch der letzten 24 Stunden</h3>
|
|
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
<div class="relative h-80 bg-slate-50 dark:bg-slate-900 rounded-xl p-4">
|
|
<canvas id="powerChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device Comparison -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 border border-slate-200 dark:border-slate-700 hover:shadow-lg transition-all duration-300">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Verbrauch nach Gerät</h3>
|
|
<div class="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="relative h-80 bg-slate-50 dark:bg-slate-900 rounded-xl p-4">
|
|
<canvas id="deviceChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device List - Unified Dark/Light Mode -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl border border-slate-200 dark:border-slate-700 overflow-hidden">
|
|
<div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Geräteübersicht</h3>
|
|
<div class="flex items-center space-x-2">
|
|
<div class="w-8 h-8 bg-green-100 dark:bg-green-900/30 rounded-lg flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Live-Updates</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full">
|
|
<thead class="bg-slate-50 dark:bg-slate-900">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Gerät</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Status</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Aktuelle Leistung</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Tagesverbrauch</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="device-table-body" class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
|
|
<!-- 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/dashboard');
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
if (result.success && result.data) {
|
|
updateEnergyMetrics(result.data);
|
|
} else {
|
|
console.error('Fehler in API-Response:', result.error || 'Unbekannter Fehler');
|
|
// Status-Indikator auf offline setzen
|
|
const liveIndicator = document.getElementById('live-indicator');
|
|
if (liveIndicator) {
|
|
liveIndicator.className = 'w-2 h-2 status-indicator-offline rounded-full';
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Energiedaten:', error);
|
|
// Status-Indikator auf offline setzen
|
|
const liveIndicator = document.getElementById('live-indicator');
|
|
if (liveIndicator) {
|
|
liveIndicator.className = 'w-2 h-2 status-indicator-offline rounded-full';
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadDeviceList() {
|
|
try {
|
|
const response = await fetch('/api/energy/dashboard');
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
if (result.success && result.data && result.data.devices) {
|
|
updateDeviceTable(result.data.devices);
|
|
} else {
|
|
console.error('Fehler beim Laden der Geräteliste:', result.error || 'Keine Gerätedaten verfügbar');
|
|
updateDeviceTable([]); // Leere Liste anzeigen
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Geräteliste:', error);
|
|
updateDeviceTable([]); // Leere Liste anzeigen
|
|
}
|
|
}
|
|
|
|
function updateEnergyMetrics(data) {
|
|
// Dashboard-Datenstruktur verwenden
|
|
if (data.energy_totals && data.energy_totals.today_total !== undefined) {
|
|
document.getElementById('total-power').textContent = data.energy_totals.today_total.toFixed(1) + ' kWh';
|
|
}
|
|
if (data.current_consumption && data.current_consumption.total_power !== undefined) {
|
|
document.getElementById('current-power').textContent = data.current_consumption.total_power.toFixed(1) + ' W';
|
|
}
|
|
if (data.overview && data.overview.online_devices !== undefined) {
|
|
document.getElementById('active-devices').textContent = data.overview.online_devices;
|
|
}
|
|
if (data.energy_totals && data.energy_totals.today_total !== undefined) {
|
|
const cost = (data.energy_totals.today_total * 0.30).toFixed(2);
|
|
document.getElementById('energy-cost').textContent = cost + ' €';
|
|
}
|
|
|
|
// Status-Indikator auf online setzen wenn Daten erfolgreich geladen
|
|
const liveIndicator = document.getElementById('live-indicator');
|
|
if (liveIndicator) {
|
|
liveIndicator.className = 'w-2 h-2 status-indicator-online rounded-full animate-pulse';
|
|
}
|
|
}
|
|
|
|
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 text-slate-500 dark:text-slate-400">
|
|
<div class="flex flex-col items-center py-8">
|
|
<svg class="w-12 h-12 text-slate-300 dark:text-slate-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
|
</svg>
|
|
<p class="text-lg font-medium">Keine Geräte gefunden</p>
|
|
<p class="text-sm">Überprüfen Sie die Hardware-Verbindungen</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
devices.forEach(device => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors duration-200';
|
|
|
|
const isOnline = device.online === true;
|
|
const statusClass = isOnline
|
|
? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'
|
|
: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400';
|
|
const statusText = isOnline ? 'Online' : 'Offline';
|
|
const statusIcon = isOnline
|
|
? '<div class="w-2 h-2 bg-green-400 rounded-full mr-1.5"></div>'
|
|
: '<div class="w-2 h-2 bg-red-400 rounded-full mr-1.5"></div>';
|
|
|
|
row.innerHTML = `
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="w-10 h-10 bg-slate-100 dark:bg-slate-700 rounded-lg flex items-center justify-center mr-4">
|
|
<svg class="w-5 h-5 text-slate-600 dark:text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">${device.name}</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">${device.model || 'Unbekanntes Modell'}</div>
|
|
</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}">
|
|
${statusIcon}${statusText}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">${device.current_power || '0.0'} W</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Momentan</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">${device.daily_consumption || '0.0'} kWh</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Heute</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<a href="/energy/device/${device.id}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 transition-colors duration-200">
|
|
Details anzeigen
|
|
</a>
|
|
</td>
|
|
`;
|
|
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |