Projektarbeit-MYP/backend/templates/admin_plug_schedules.html

524 lines
19 KiB
HTML

{% extends "base.html" %}
{% block title %}Steckdosenschaltzeiten - Admin{% endblock %}
{% block head %}
<!-- FullCalendar CSS -->
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/main.min.css' rel='stylesheet' />
<link href='https://cdn.jsdelivr.net/npm/@fullcalendar/daygrid@6.1.10/main.min.css' rel='stylesheet' />
<link href='https://cdn.jsdelivr.net/npm/@fullcalendar/timegrid@6.1.10/main.min.css' rel='stylesheet' />
<!-- Chart.js für Statistiken -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.calendar-container {
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
.fc-event {
font-size: 11px;
border-radius: 4px;
padding: 2px 4px;
}
.fc-event-title {
font-weight: 500;
}
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 20px;
color: white;
margin-bottom: 20px;
}
.control-panel {
background: white;
border-radius: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
margin-bottom: 20px;
}
.filter-group {
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.btn-calendar {
background: #3b82f6;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-calendar:hover {
background: #2563eb;
}
.btn-calendar.active {
background: #1d4ed8;
}
.legend {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.legend-color {
width: 16px;
height: 16px;
border-radius: 4px;
}
</style>
{% endblock %}
{% block content %}
<div class="min-h-screen">
<!-- Header mit Breadcrumb -->
<div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-6">
<nav class="text-sm font-medium text-gray-600 mb-4">
<ol class="list-none p-0 inline-flex">
<li class="flex items-center">
<a href="{{ url_for('admin_page') }}" class="hover:text-blue-600">Admin-Dashboard</a>
<svg class="fill-current w-3 h-3 mx-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path d="m285.476 272.971c4.686 4.686 4.686 12.284 0 16.97l-133.952 133.954c-4.686 4.686-12.284 4.686-16.97 0l-133.952-133.954c-4.686-4.686-4.686-12.284 0-16.97 4.686-4.686 12.284-4.686 16.97 0l125.462 125.463 125.462-125.463c4.686-4.686 12.284-4.686 16.97 0z"/>
</svg>
</li>
<li class="text-gray-900 font-semibold">
Steckdosenschaltzeiten
</li>
</ol>
</nav>
<div class="flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-gray-900">
<i class="fas fa-plug text-blue-600 mr-3"></i>
Steckdosenschaltzeiten
</h1>
<p class="mt-2 text-gray-600">
Kalenderübersicht aller Drucker-Steckdosenschaltungen mit detaillierter Analyse
</p>
</div>
<div class="flex gap-3">
<button id="refreshData" class="btn-calendar">
<i class="fas fa-sync-alt mr-2"></i>
Aktualisieren
</button>
<button id="cleanupLogs" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700">
<i class="fas fa-trash mr-2"></i>
Alte Logs löschen
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Haupt-Container -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8">
<!-- Statistik-Karten -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div class="stats-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-80">Schaltungen (24h)</p>
<p class="text-2xl font-bold" id="totalLogs">{{ stats.total_logs or 0 }}</p>
</div>
<i class="fas fa-chart-line text-2xl opacity-60"></i>
</div>
</div>
<div class="stats-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-80">Erfolgsrate</p>
<p class="text-2xl font-bold" id="successRate">{{ "%.1f"|format(100 - stats.error_rate) }}%</p>
</div>
<i class="fas fa-check-circle text-2xl opacity-60"></i>
</div>
</div>
<div class="stats-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-80">Ø Antwortzeit</p>
<p class="text-2xl font-bold" id="avgResponseTime">
{% if stats.average_response_time_ms %}
{{ "%.0f"|format(stats.average_response_time_ms) }}ms
{% else %}
N/A
{% endif %}
</p>
</div>
<i class="fas fa-stopwatch text-2xl opacity-60"></i>
</div>
</div>
<div class="stats-card">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-80">Fehlerzahl</p>
<p class="text-2xl font-bold" id="errorCount">{{ stats.error_count or 0 }}</p>
</div>
<i class="fas fa-exclamation-triangle text-2xl opacity-60"></i>
</div>
</div>
</div>
<!-- Filter und Steuerung -->
<div class="control-panel">
<div class="filter-group">
<label for="printerFilter" class="font-medium text-gray-700">Drucker filtern:</label>
<select id="printerFilter" class="border border-gray-300 rounded-lg px-3 py-2 bg-white">
<option value="">Alle Drucker</option>
{% for printer in printers %}
<option value="{{ printer.id }}">{{ printer.name }}</option>
{% endfor %}
</select>
<div class="border-l border-gray-300 pl-4 ml-4">
<label class="font-medium text-gray-700 mr-3">Ansicht:</label>
<button id="monthView" class="btn-calendar active">Monat</button>
<button id="weekView" class="btn-calendar">Woche</button>
<button id="dayView" class="btn-calendar">Tag</button>
</div>
</div>
<!-- Legende -->
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #10b981;"></div>
<span>Steckdose EIN</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #f59e0b;"></div>
<span>Steckdose AUS</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #3b82f6;"></div>
<span>Verbunden</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ef4444;"></div>
<span>Getrennt</span>
</div>
</div>
</div>
<!-- Kalender-Container -->
<div class="calendar-container">
<div id="calendar"></div>
</div>
<!-- Detailansicht Modal -->
<div id="eventDetailModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg max-w-lg w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Schaltung Details</h3>
<button id="closeModal" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<div id="modalContent">
<!-- Wird dynamisch gefüllt -->
</div>
<div class="mt-6 flex justify-end">
<button id="closeModalBtn" class="px-4 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400">
Schließen
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- FullCalendar JS -->
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
let calendar;
let currentPrinterFilter = '';
// Kalender initialisieren
function initCalendar() {
const calendarEl = document.getElementById('calendar');
calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
locale: 'de',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
height: 'auto',
events: function(info, successCallback, failureCallback) {
loadCalendarEvents(info.start, info.end, currentPrinterFilter, successCallback, failureCallback);
},
eventClick: function(info) {
showEventDetails(info.event);
},
eventDidMount: function(info) {
// Tooltip hinzufügen
info.el.title = info.event.title + '\nKlicken für Details';
}
});
calendar.render();
}
// Events vom Server laden
function loadCalendarEvents(start, end, printerFilter, successCallback, failureCallback) {
const params = new URLSearchParams({
start: start.toISOString(),
end: end.toISOString()
});
if (printerFilter) {
params.append('printer_id', printerFilter);
}
fetch(`/api/admin/plug-schedules/calendar?${params}`)
.then(response => response.json())
.then(data => {
successCallback(data);
})
.catch(error => {
console.error('Fehler beim Laden der Kalender-Daten:', error);
failureCallback(error);
});
}
// Event-Details anzeigen
function showEventDetails(event) {
const props = event.extendedProps;
const modal = document.getElementById('eventDetailModal');
const content = document.getElementById('modalContent');
const startTime = new Date(event.start).toLocaleString('de-DE');
let detailsHtml = `
<div class="space-y-3">
<div class="flex items-center">
<span class="w-4 h-4 rounded mr-3" style="background-color: ${event.backgroundColor}"></span>
<span class="font-medium">${event.title}</span>
</div>
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<span class="font-medium text-gray-600">Zeitpunkt:</span>
<p>${startTime}</p>
</div>
<div>
<span class="font-medium text-gray-600">Drucker:</span>
<p>${props.printer_name}</p>
</div>
<div>
<span class="font-medium text-gray-600">Status:</span>
<p class="capitalize">${props.status}</p>
</div>
<div>
<span class="font-medium text-gray-600">Quelle:</span>
<p class="capitalize">${props.source}</p>
</div>
`;
if (props.user_name) {
detailsHtml += `
<div>
<span class="font-medium text-gray-600">Benutzer:</span>
<p>${props.user_name}</p>
</div>
`;
}
if (props.response_time_ms) {
detailsHtml += `
<div>
<span class="font-medium text-gray-600">Antwortzeit:</span>
<p>${props.response_time_ms}ms</p>
</div>
`;
}
if (props.power_consumption) {
detailsHtml += `
<div>
<span class="font-medium text-gray-600">Verbrauch:</span>
<p>${props.power_consumption}W</p>
</div>
`;
}
if (props.voltage) {
detailsHtml += `
<div>
<span class="font-medium text-gray-600">Spannung:</span>
<p>${props.voltage}V</p>
</div>
`;
}
if (props.current) {
detailsHtml += `
<div>
<span class="font-medium text-gray-600">Strom:</span>
<p>${props.current}A</p>
</div>
`;
}
detailsHtml += `</div></div>`;
if (props.notes) {
detailsHtml += `
<div class="mt-4">
<span class="font-medium text-gray-600">Notizen:</span>
<p class="text-sm bg-gray-50 p-2 rounded mt-1">${props.notes}</p>
</div>
`;
}
if (props.error_message) {
detailsHtml += `
<div class="mt-4">
<span class="font-medium text-red-600">Fehlermeldung:</span>
<p class="text-sm bg-red-50 text-red-700 p-2 rounded mt-1">${props.error_message}</p>
</div>
`;
}
content.innerHTML = detailsHtml;
modal.classList.remove('hidden');
}
// Modal schließen
function closeModal() {
document.getElementById('eventDetailModal').classList.add('hidden');
}
// Event-Listener
document.getElementById('closeModal').addEventListener('click', closeModal);
document.getElementById('closeModalBtn').addEventListener('click', closeModal);
// Drucker-Filter
document.getElementById('printerFilter').addEventListener('change', function() {
currentPrinterFilter = this.value;
calendar.refetchEvents();
});
// Ansicht-Buttons
document.getElementById('monthView').addEventListener('click', function() {
calendar.changeView('dayGridMonth');
updateActiveViewButton(this);
});
document.getElementById('weekView').addEventListener('click', function() {
calendar.changeView('timeGridWeek');
updateActiveViewButton(this);
});
document.getElementById('dayView').addEventListener('click', function() {
calendar.changeView('timeGridDay');
updateActiveViewButton(this);
});
function updateActiveViewButton(activeBtn) {
document.querySelectorAll('.btn-calendar').forEach(btn => {
btn.classList.remove('active');
});
activeBtn.classList.add('active');
}
// Aktualisieren-Button
document.getElementById('refreshData').addEventListener('click', function() {
calendar.refetchEvents();
loadStatistics();
});
// Cleanup-Button
document.getElementById('cleanupLogs').addEventListener('click', function() {
if (confirm('Möchten Sie wirklich alte Logs löschen? (älter als 30 Tage)')) {
fetch('/api/admin/plug-schedules/cleanup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ days: 30 })
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Erfolgreich ${data.deleted_count} alte Einträge gelöscht`);
calendar.refetchEvents();
loadStatistics();
} else {
alert('Fehler beim Löschen: ' + data.error);
}
})
.catch(error => {
console.error('Fehler:', error);
alert('Fehler beim Löschen der Logs');
});
}
});
// Statistiken laden
function loadStatistics() {
fetch('/api/admin/plug-schedules/statistics')
.then(response => response.json())
.then(data => {
if (data.success) {
const stats = data.statistics;
document.getElementById('totalLogs').textContent = stats.total_logs || 0;
document.getElementById('successRate').textContent = (100 - (stats.error_rate || 0)).toFixed(1) + '%';
document.getElementById('avgResponseTime').textContent =
stats.average_response_time_ms ? Math.round(stats.average_response_time_ms) + 'ms' : 'N/A';
document.getElementById('errorCount').textContent = stats.error_count || 0;
}
})
.catch(error => {
console.error('Fehler beim Laden der Statistiken:', error);
});
}
// Kalender initialisieren
initCalendar();
});
</script>
{% endblock %}