779 lines
30 KiB
HTML
779 lines
30 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 {
|
|
@apply bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-6 mb-6 transition-all duration-300;
|
|
}
|
|
|
|
.fc-event {
|
|
font-size: 11px;
|
|
border-radius: 6px;
|
|
padding: 2px 6px;
|
|
border: none !important;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.fc-event-title {
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* FullCalendar Dark Mode */
|
|
.dark .fc-theme-standard td,
|
|
.dark .fc-theme-standard th {
|
|
border-color: #374151;
|
|
}
|
|
|
|
.dark .fc-theme-standard .fc-scrollgrid {
|
|
border-color: #374151;
|
|
}
|
|
|
|
.dark .fc-col-header-cell {
|
|
background: #1f2937;
|
|
color: #e5e7eb;
|
|
}
|
|
|
|
.dark .fc-daygrid-day {
|
|
background: #1f2937;
|
|
color: #e5e7eb;
|
|
}
|
|
|
|
.dark .fc-day-today {
|
|
background-color: #065f46 !important;
|
|
}
|
|
|
|
.dark .fc-button-primary {
|
|
background: #3b82f6;
|
|
border-color: #3b82f6;
|
|
}
|
|
|
|
.dark .fc-button-primary:hover {
|
|
background: #2563eb;
|
|
border-color: #2563eb;
|
|
}
|
|
|
|
.dark .fc-toolbar-title {
|
|
color: #e5e7eb;
|
|
}
|
|
|
|
.stats-card {
|
|
@apply bg-gradient-to-br from-blue-500 via-blue-600 to-indigo-700 dark:from-blue-600 dark:via-blue-700 dark:to-indigo-800 rounded-xl p-6 text-white shadow-lg border border-blue-200 dark:border-blue-800 transition-all duration-300 hover:shadow-xl hover:scale-105;
|
|
}
|
|
|
|
.stats-card-success {
|
|
@apply bg-gradient-to-br from-emerald-500 via-emerald-600 to-green-700 dark:from-emerald-600 dark:via-emerald-700 dark:to-green-800;
|
|
}
|
|
|
|
.stats-card-warning {
|
|
@apply bg-gradient-to-br from-amber-500 via-orange-500 to-orange-600 dark:from-amber-600 dark:via-orange-600 dark:to-orange-700;
|
|
}
|
|
|
|
.stats-card-danger {
|
|
@apply bg-gradient-to-br from-red-500 via-red-600 to-rose-700 dark:from-red-600 dark:via-red-700 dark:to-rose-800;
|
|
}
|
|
|
|
.control-panel {
|
|
@apply bg-white dark:bg-gray-800 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 p-6 mb-6 transition-all duration-300;
|
|
}
|
|
|
|
.filter-group {
|
|
@apply flex gap-4 items-center flex-wrap;
|
|
}
|
|
|
|
.btn-calendar {
|
|
@apply bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white border-none px-4 py-2 rounded-lg cursor-pointer transition-all duration-200 font-medium shadow-md hover:shadow-lg;
|
|
}
|
|
|
|
.btn-calendar.active {
|
|
@apply bg-blue-800 dark:bg-blue-500 shadow-lg scale-105;
|
|
}
|
|
|
|
.btn-danger {
|
|
@apply bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all duration-200 font-medium shadow-md hover:shadow-lg;
|
|
}
|
|
|
|
.btn-primary {
|
|
@apply bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-all duration-200 font-medium shadow-md hover:shadow-lg;
|
|
}
|
|
|
|
.legend {
|
|
@apply flex gap-6 flex-wrap mt-4 pt-4 border-t border-gray-200 dark:border-gray-600;
|
|
}
|
|
|
|
.legend-item {
|
|
@apply flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300;
|
|
}
|
|
|
|
.legend-color {
|
|
@apply w-4 h-4 rounded;
|
|
}
|
|
|
|
.select-custom {
|
|
@apply border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200;
|
|
}
|
|
|
|
.page-header {
|
|
@apply bg-gradient-to-r from-blue-50 via-indigo-50 to-purple-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900 border-b border-gray-200 dark:border-gray-700;
|
|
}
|
|
|
|
.modal-overlay {
|
|
@apply fixed inset-0 bg-black bg-opacity-50 dark:bg-opacity-70 z-50 transition-opacity duration-300;
|
|
}
|
|
|
|
.modal-content {
|
|
@apply bg-white dark:bg-gray-800 rounded-xl max-w-2xl w-full p-6 shadow-2xl border border-gray-200 dark:border-gray-700 transition-all duration-300;
|
|
}
|
|
|
|
.modal-header {
|
|
@apply flex justify-between items-center mb-6 pb-4 border-b border-gray-200 dark:border-gray-600;
|
|
}
|
|
|
|
.modal-title {
|
|
@apply text-xl font-semibold text-gray-900 dark:text-gray-100;
|
|
}
|
|
|
|
.close-button {
|
|
@apply text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors duration-200 text-xl;
|
|
}
|
|
|
|
.detail-grid {
|
|
@apply grid grid-cols-1 md:grid-cols-2 gap-4 text-sm;
|
|
}
|
|
|
|
.detail-item {
|
|
@apply space-y-1;
|
|
}
|
|
|
|
.detail-label {
|
|
@apply font-medium text-gray-600 dark:text-gray-400;
|
|
}
|
|
|
|
.detail-value {
|
|
@apply text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-700 px-3 py-2 rounded-lg;
|
|
}
|
|
|
|
.error-message {
|
|
@apply bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 p-3 rounded-lg border border-red-200 dark:border-red-800;
|
|
}
|
|
|
|
.notes-section {
|
|
@apply bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 p-3 rounded-lg border border-blue-200 dark:border-blue-800;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
|
|
<!-- Header mit Breadcrumb -->
|
|
<div class="page-header">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="py-8">
|
|
<nav class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-6">
|
|
<ol class="list-none p-0 inline-flex">
|
|
<li class="flex items-center">
|
|
<a href="{{ url_for('admin_page') }}" class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200">
|
|
<i class="fas fa-tachometer-alt mr-2"></i>
|
|
Admin-Dashboard
|
|
</a>
|
|
<svg class="fill-current w-4 h-4 mx-3 text-gray-400" 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 dark:text-gray-100 font-semibold">
|
|
<i class="fas fa-plug mr-2"></i>
|
|
Steckdosenschaltzeiten
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4">
|
|
<div>
|
|
<h1 class="text-4xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
|
<i class="fas fa-plug text-blue-600 dark:text-blue-400 mr-4"></i>
|
|
Steckdosenschaltzeiten
|
|
</h1>
|
|
<p class="text-lg text-gray-600 dark:text-gray-400">
|
|
Kalenderübersicht aller Drucker-Steckdosenschaltungen mit detaillierter Analyse
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex gap-3">
|
|
<button id="refreshData" class="btn-primary">
|
|
<i class="fas fa-sync-alt mr-2"></i>
|
|
Aktualisieren
|
|
</button>
|
|
|
|
<button id="cleanupLogs" class="btn-danger">
|
|
<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 py-8">
|
|
<!-- Statistik-Karten -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<div class="stats-card">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm opacity-90 mb-1">Schaltungen (24h)</p>
|
|
<p class="text-3xl font-bold" id="totalLogs">{{ stats.total_logs or 0 }}</p>
|
|
<p class="text-xs opacity-80 mt-1">Gesamt-Events</p>
|
|
</div>
|
|
<div class="p-3 bg-white/20 rounded-xl">
|
|
<i class="fas fa-chart-line text-2xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-card stats-card-success">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm opacity-90 mb-1">Erfolgsrate</p>
|
|
<p class="text-3xl font-bold" id="successRate">{{ "%.1f"|format(100 - stats.error_rate) }}%</p>
|
|
<p class="text-xs opacity-80 mt-1">Erfolgreiche Schaltungen</p>
|
|
</div>
|
|
<div class="p-3 bg-white/20 rounded-xl">
|
|
<i class="fas fa-check-circle text-2xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-card stats-card-warning">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm opacity-90 mb-1">Ø Antwortzeit</p>
|
|
<p class="text-3xl font-bold" id="avgResponseTime">
|
|
{% if stats.average_response_time_ms %}
|
|
{{ "%.0f"|format(stats.average_response_time_ms) }}ms
|
|
{% else %}
|
|
N/A
|
|
{% endif %}
|
|
</p>
|
|
<p class="text-xs opacity-80 mt-1">Durchschnittlich</p>
|
|
</div>
|
|
<div class="p-3 bg-white/20 rounded-xl">
|
|
<i class="fas fa-stopwatch text-2xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-card stats-card-danger">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm opacity-90 mb-1">Fehlerzahl</p>
|
|
<p class="text-3xl font-bold" id="errorCount">{{ stats.error_count or 0 }}</p>
|
|
<p class="text-xs opacity-80 mt-1">Gescheiterte Versuche</p>
|
|
</div>
|
|
<div class="p-3 bg-white/20 rounded-xl">
|
|
<i class="fas fa-exclamation-triangle text-2xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter und Steuerung -->
|
|
<div class="control-panel">
|
|
<div class="filter-group">
|
|
<div class="flex items-center gap-3">
|
|
<label for="printerFilter" class="font-medium text-gray-700 dark:text-gray-300">
|
|
<i class="fas fa-filter mr-2"></i>
|
|
Drucker filtern:
|
|
</label>
|
|
<select id="printerFilter" class="select-custom">
|
|
<option value="">Alle Drucker anzeigen</option>
|
|
{% for printer in printers %}
|
|
<option value="{{ printer.id }}">{{ printer.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3 border-l border-gray-300 dark:border-gray-600 pl-6 ml-6">
|
|
<label class="font-medium text-gray-700 dark:text-gray-300">
|
|
<i class="fas fa-eye mr-2"></i>
|
|
Ansicht:
|
|
</label>
|
|
<div class="flex gap-2">
|
|
<button id="monthView" class="btn-calendar active">
|
|
<i class="fas fa-calendar-alt mr-1"></i>
|
|
Monat
|
|
</button>
|
|
<button id="weekView" class="btn-calendar">
|
|
<i class="fas fa-calendar-week mr-1"></i>
|
|
Woche
|
|
</button>
|
|
<button id="dayView" class="btn-calendar">
|
|
<i class="fas fa-calendar-day mr-1"></i>
|
|
Tag
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legende -->
|
|
<div class="legend">
|
|
<h4 class="font-medium text-gray-700 dark:text-gray-300 w-full mb-2">
|
|
<i class="fas fa-info-circle mr-2"></i>
|
|
Status-Legende:
|
|
</h4>
|
|
<div class="legend-item">
|
|
<div class="legend-color bg-green-500"></div>
|
|
<span><i class="fas fa-toggle-on mr-1"></i>Steckdose EIN</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color bg-orange-500"></div>
|
|
<span><i class="fas fa-toggle-off mr-1"></i>Steckdose AUS</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color bg-blue-500"></div>
|
|
<span><i class="fas fa-plug mr-1"></i>Verbunden</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color bg-red-500"></div>
|
|
<span><i class="fas fa-unlink mr-1"></i>Getrennt</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kalender-Container -->
|
|
<div class="calendar-container">
|
|
<div id="calendar"></div>
|
|
</div>
|
|
|
|
<!-- Detailansicht Modal -->
|
|
<div id="eventDetailModal" class="modal-overlay hidden">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">
|
|
<i class="fas fa-info-circle mr-2 text-blue-600 dark:text-blue-400"></i>
|
|
Schaltung Details
|
|
</h3>
|
|
<button id="closeModal" class="close-button">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="modalContent">
|
|
<!-- Wird dynamisch gefüllt -->
|
|
</div>
|
|
|
|
<div class="mt-6 flex justify-end gap-3">
|
|
<button id="closeModalBtn" class="px-6 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-all duration-200 font-medium">
|
|
<i class="fas fa-times mr-2"></i>
|
|
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';
|
|
|
|
// Hover-Effekt
|
|
info.el.addEventListener('mouseenter', function() {
|
|
this.style.transform = 'scale(1.05)';
|
|
this.style.zIndex = '10';
|
|
});
|
|
|
|
info.el.addEventListener('mouseleave', function() {
|
|
this.style.transform = 'scale(1)';
|
|
this.style.zIndex = '1';
|
|
});
|
|
}
|
|
});
|
|
|
|
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', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
});
|
|
|
|
let detailsHtml = `
|
|
<div class="space-y-6">
|
|
<div class="flex items-center p-4 bg-gray-50 dark:bg-gray-700 rounded-xl">
|
|
<span class="w-6 h-6 rounded-lg mr-4 shadow-md" style="background-color: ${event.backgroundColor}"></span>
|
|
<div>
|
|
<h4 class="font-semibold text-lg text-gray-900 dark:text-gray-100">${event.title}</h4>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">${startTime}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detail-grid">
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-printer mr-1"></i>
|
|
Drucker:
|
|
</div>
|
|
<div class="detail-value">${props.printer_name}</div>
|
|
</div>
|
|
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-info-circle mr-1"></i>
|
|
Status:
|
|
</div>
|
|
<div class="detail-value capitalize">${props.status}</div>
|
|
</div>
|
|
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-source mr-1"></i>
|
|
Quelle:
|
|
</div>
|
|
<div class="detail-value capitalize">${props.source}</div>
|
|
</div>
|
|
`;
|
|
|
|
if (props.user_name) {
|
|
detailsHtml += `
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-user mr-1"></i>
|
|
Benutzer:
|
|
</div>
|
|
<div class="detail-value">${props.user_name}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (props.response_time_ms) {
|
|
detailsHtml += `
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-clock mr-1"></i>
|
|
Antwortzeit:
|
|
</div>
|
|
<div class="detail-value">${props.response_time_ms}ms</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (props.power_consumption) {
|
|
detailsHtml += `
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-bolt mr-1"></i>
|
|
Verbrauch:
|
|
</div>
|
|
<div class="detail-value">${props.power_consumption}W</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (props.voltage) {
|
|
detailsHtml += `
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-tachometer-alt mr-1"></i>
|
|
Spannung:
|
|
</div>
|
|
<div class="detail-value">${props.voltage}V</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (props.current) {
|
|
detailsHtml += `
|
|
<div class="detail-item">
|
|
<div class="detail-label">
|
|
<i class="fas fa-wave-square mr-1"></i>
|
|
Strom:
|
|
</div>
|
|
<div class="detail-value">${props.current}A</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
detailsHtml += `</div></div>`;
|
|
|
|
if (props.notes) {
|
|
detailsHtml += `
|
|
<div class="mt-6">
|
|
<div class="notes-section">
|
|
<div class="flex items-start gap-2">
|
|
<i class="fas fa-sticky-note mt-0.5"></i>
|
|
<div>
|
|
<span class="font-medium">Notizen:</span>
|
|
<p class="mt-1">${props.notes}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (props.error_message) {
|
|
detailsHtml += `
|
|
<div class="mt-6">
|
|
<div class="error-message">
|
|
<div class="flex items-start gap-2">
|
|
<i class="fas fa-exclamation-triangle mt-0.5"></i>
|
|
<div>
|
|
<span class="font-medium">Fehlermeldung:</span>
|
|
<p class="mt-1">${props.error_message}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
content.innerHTML = detailsHtml;
|
|
modal.classList.remove('hidden');
|
|
|
|
// Animation
|
|
setTimeout(() => {
|
|
modal.style.opacity = '1';
|
|
modal.querySelector('.modal-content').style.transform = 'scale(1)';
|
|
}, 10);
|
|
}
|
|
|
|
// Modal schließen
|
|
function closeModal() {
|
|
const modal = document.getElementById('eventDetailModal');
|
|
modal.style.opacity = '0';
|
|
modal.querySelector('.modal-content').style.transform = 'scale(0.95)';
|
|
|
|
setTimeout(() => {
|
|
modal.classList.add('hidden');
|
|
}, 300);
|
|
}
|
|
|
|
// Event-Listener
|
|
document.getElementById('closeModal').addEventListener('click', closeModal);
|
|
document.getElementById('closeModalBtn').addEventListener('click', closeModal);
|
|
|
|
// Modal schließen bei Klick außerhalb
|
|
document.getElementById('eventDetailModal').addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
// Drucker-Filter
|
|
document.getElementById('printerFilter').addEventListener('change', function() {
|
|
currentPrinterFilter = this.value;
|
|
calendar.refetchEvents();
|
|
|
|
// Visual Feedback
|
|
this.style.transform = 'scale(1.02)';
|
|
setTimeout(() => {
|
|
this.style.transform = 'scale(1)';
|
|
}, 150);
|
|
});
|
|
|
|
// 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() {
|
|
// Loading-Animation
|
|
const originalContent = this.innerHTML;
|
|
this.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Lädt...';
|
|
this.disabled = true;
|
|
|
|
calendar.refetchEvents();
|
|
loadStatistics();
|
|
|
|
// Reset nach 2 Sekunden
|
|
setTimeout(() => {
|
|
this.innerHTML = originalContent;
|
|
this.disabled = false;
|
|
}, 2000);
|
|
});
|
|
|
|
// Cleanup-Button
|
|
document.getElementById('cleanupLogs').addEventListener('click', function() {
|
|
if (confirm('Möchten Sie wirklich alte Logs löschen? (älter als 30 Tage)\n\nDieser Vorgang kann nicht rückgängig gemacht werden.')) {
|
|
// Loading-Animation
|
|
const originalContent = this.innerHTML;
|
|
this.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Lösche...';
|
|
this.disabled = true;
|
|
|
|
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');
|
|
})
|
|
.finally(() => {
|
|
this.innerHTML = originalContent;
|
|
this.disabled = false;
|
|
});
|
|
}
|
|
});
|
|
|
|
// Statistiken laden
|
|
function loadStatistics() {
|
|
fetch('/api/admin/plug-schedules/statistics')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const stats = data.statistics;
|
|
|
|
// Animierte Zahlen-Updates
|
|
animateValue('totalLogs', stats.total_logs || 0);
|
|
animateValue('successRate', (100 - (stats.error_rate || 0)), '%');
|
|
animateValue('errorCount', stats.error_count || 0);
|
|
|
|
const avgTime = stats.average_response_time_ms;
|
|
document.getElementById('avgResponseTime').textContent =
|
|
avgTime ? Math.round(avgTime) + 'ms' : 'N/A';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler beim Laden der Statistiken:', error);
|
|
});
|
|
}
|
|
|
|
// Animierte Werte-Updates
|
|
function animateValue(elementId, endValue, suffix = '') {
|
|
const element = document.getElementById(elementId);
|
|
const startValue = parseInt(element.textContent) || 0;
|
|
const difference = endValue - startValue;
|
|
const duration = 1000; // 1 Sekunde
|
|
const steps = 30;
|
|
const stepValue = difference / steps;
|
|
const stepDuration = duration / steps;
|
|
|
|
let currentValue = startValue;
|
|
let currentStep = 0;
|
|
|
|
const timer = setInterval(() => {
|
|
currentStep++;
|
|
currentValue += stepValue;
|
|
|
|
if (currentStep >= steps) {
|
|
currentValue = endValue;
|
|
clearInterval(timer);
|
|
}
|
|
|
|
element.textContent = Math.round(currentValue) + suffix;
|
|
}, stepDuration);
|
|
}
|
|
|
|
// Kalender initialisieren
|
|
initCalendar();
|
|
|
|
// Initiales Laden der Statistiken
|
|
loadStatistics();
|
|
});
|
|
</script>
|
|
{% endblock %} |