- 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/
494 lines
17 KiB
HTML
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 %} |