585 lines
32 KiB
HTML
585 lines
32 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ page_title }} - Mercedes-Benz MYP{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
|
|
<!-- Header mit Breadcrumb -->
|
|
<div class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="py-6">
|
|
<!-- Breadcrumb -->
|
|
<nav class="flex mb-4" aria-label="Breadcrumb">
|
|
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
|
{% for item in breadcrumb %}
|
|
<li class="inline-flex items-center">
|
|
{% if not loop.last %}
|
|
<a href="{{ item.url }}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400">
|
|
{{ item.name }}
|
|
</a>
|
|
{% if not loop.last %}
|
|
<svg class="w-6 h-6 text-gray-400 mx-1" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
{% endif %}
|
|
{% else %}
|
|
<span class="ml-1 text-sm font-medium text-gray-500 dark:text-gray-400">{{ item.name }}</span>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Titel und Beschreibung -->
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white flex items-center">
|
|
<span class="mr-3">🔌</span>
|
|
{{ page_title }}
|
|
</h1>
|
|
<p class="mt-2 text-lg text-gray-600 dark:text-gray-300">
|
|
Übersicht aller Steckdosen-Statusänderungen und Smart Plug Monitoring-Daten
|
|
</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<button id="refreshBtn"
|
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200 flex items-center">
|
|
<svg class="w-5 h-5 mr-2" 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"></path>
|
|
</svg>
|
|
Aktualisieren
|
|
</button>
|
|
<button id="cleanupBtn"
|
|
class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200 flex items-center">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
</svg>
|
|
Alte Logs löschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hauptinhalt -->
|
|
<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="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 rounded-lg bg-blue-100 dark:bg-blue-900">
|
|
<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="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"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Gesamt-Logs</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="totalLogs">{{ stats.total_logs or 0 }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 rounded-lg bg-green-100 dark:bg-green-900">
|
|
<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="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Ø Antwortzeit</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="avgResponseTime">
|
|
{% if stats.average_response_time_ms %}
|
|
{{ "%.0f"|format(stats.average_response_time_ms) }}ms
|
|
{% else %}
|
|
--
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 rounded-lg bg-red-100 dark:bg-red-900">
|
|
<svg class="w-6 h-6 text-red-600 dark:text-red-400" 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.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Fehlerrate</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="errorRate">{{ "%.1f"|format(stats.error_rate) }}%</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 rounded-lg bg-purple-100 dark:bg-purple-900">
|
|
<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="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2m-9 0h10m-10 0a1 1 0 00-1 1v14a1 1 0 001 1h10a1 1 0 001-1V5a1 1 0 00-1-1"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Aktive Drucker</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="activePrinters">{{ printers|length }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter-Sektion -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Filter</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div>
|
|
<label for="printerFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Drucker</label>
|
|
<select id="printerFilter" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
|
|
<option value="">Alle Drucker</option>
|
|
{% for printer in printers %}
|
|
<option value="{{ printer.id }}">{{ printer.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="statusFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Status</label>
|
|
<select id="statusFilter" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
|
|
<option value="">Alle Status</option>
|
|
<option value="connected">🔌 Verbunden</option>
|
|
<option value="disconnected">❌ Getrennt</option>
|
|
<option value="on">🟢 Eingeschaltet</option>
|
|
<option value="off">🔴 Ausgeschaltet</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="timeFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Zeitraum</label>
|
|
<select id="timeFilter" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-blue-500 focus:border-blue-500">
|
|
<option value="24">Letzte 24 Stunden</option>
|
|
<option value="48">Letzte 48 Stunden</option>
|
|
<option value="72">Letzte 3 Tage</option>
|
|
<option value="168">Letzte 7 Tage</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex items-end">
|
|
<button id="applyFilters" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors duration-200">
|
|
Filter anwenden
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status-Verteilung -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Status-Verteilung (24h)</h3>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
{% for status, count in stats.status_distribution.items() %}
|
|
<div class="text-center p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<div class="text-2xl mb-2">
|
|
{% if status == 'connected' %}🔌
|
|
{% elif status == 'disconnected' %}❌
|
|
{% elif status == 'on' %}🟢
|
|
{% elif status == 'off' %}🔴
|
|
{% else %}❓{% endif %}
|
|
</div>
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ count }}</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400 capitalize">{{ status }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs-Tabelle -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
<div class="flex justify-between items-center">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Steckdosen-Logs</h3>
|
|
<div class="flex items-center space-x-2">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400" id="logCount">Lade Daten...</span>
|
|
<button id="exportBtn" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium text-sm">
|
|
Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading-Indikator -->
|
|
<div id="loadingIndicator" class="p-8 text-center">
|
|
<div class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
|
<svg class="w-4 h-4 mr-3 animate-spin" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Lade Steckdosen-Logs...
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabelle -->
|
|
<div id="logsTable" class="hidden">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead class="bg-gray-50 dark:bg-gray-700">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Status</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Drucker</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Zeitstempel</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Quelle</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Details</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="logsTableBody" class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
<!-- Wird dynamisch gefüllt -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Paginierung -->
|
|
<div id="pagination" class="bg-white dark:bg-gray-800 px-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-gray-700 sm:px-6">
|
|
<!-- Wird dynamisch gefüllt -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Keine Daten -->
|
|
<div id="noData" class="hidden p-8 text-center">
|
|
<div class="text-gray-500 dark:text-gray-400">
|
|
<svg class="w-12 h-12 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 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"></path>
|
|
</svg>
|
|
<p class="text-lg font-medium">Keine Logs gefunden</p>
|
|
<p class="text-sm">Für die ausgewählten Filter wurden keine Steckdosen-Logs gefunden.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cleanup-Modal -->
|
|
<div id="cleanupModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
|
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div class="sm:flex sm:items-start">
|
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900 sm:mx-0 sm:h-10 sm:w-10">
|
|
<svg class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">Alte Logs löschen</h3>
|
|
<div class="mt-2">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Wie viele Tage alte Logs sollen gelöscht werden?
|
|
</p>
|
|
<div class="mt-4">
|
|
<label for="cleanupDays" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tage</label>
|
|
<input type="number" id="cleanupDays" value="30" min="1" max="365"
|
|
class="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-50 dark:bg-gray-700 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button type="button" id="confirmCleanup"
|
|
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
|
|
Löschen
|
|
</button>
|
|
<button type="button" id="cancelCleanup"
|
|
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
|
|
Abbrechen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Globale Variablen
|
|
let currentPage = 1;
|
|
let currentFilters = {
|
|
printer_id: '',
|
|
status: '',
|
|
hours: 24
|
|
};
|
|
|
|
// DOM-Elemente
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|
const logsTable = document.getElementById('logsTable');
|
|
const noData = document.getElementById('noData');
|
|
const logsTableBody = document.getElementById('logsTableBody');
|
|
const pagination = document.getElementById('pagination');
|
|
const logCount = document.getElementById('logCount');
|
|
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
const cleanupBtn = document.getElementById('cleanupBtn');
|
|
const applyFiltersBtn = document.getElementById('applyFilters');
|
|
|
|
const printerFilter = document.getElementById('printerFilter');
|
|
const statusFilter = document.getElementById('statusFilter');
|
|
const timeFilter = document.getElementById('timeFilter');
|
|
|
|
const cleanupModal = document.getElementById('cleanupModal');
|
|
const confirmCleanup = document.getElementById('confirmCleanup');
|
|
const cancelCleanup = document.getElementById('cancelCleanup');
|
|
const cleanupDays = document.getElementById('cleanupDays');
|
|
|
|
// Event-Listener
|
|
refreshBtn.addEventListener('click', loadLogs);
|
|
cleanupBtn.addEventListener('click', showCleanupModal);
|
|
applyFiltersBtn.addEventListener('click', applyFilters);
|
|
|
|
confirmCleanup.addEventListener('click', performCleanup);
|
|
cancelCleanup.addEventListener('click', hideCleanupModal);
|
|
|
|
// Initial laden
|
|
loadLogs();
|
|
|
|
function showLoading() {
|
|
loadingIndicator.classList.remove('hidden');
|
|
logsTable.classList.add('hidden');
|
|
noData.classList.add('hidden');
|
|
}
|
|
|
|
function hideLoading() {
|
|
loadingIndicator.classList.add('hidden');
|
|
}
|
|
|
|
function showTable() {
|
|
logsTable.classList.remove('hidden');
|
|
noData.classList.add('hidden');
|
|
}
|
|
|
|
function showNoData() {
|
|
noData.classList.remove('hidden');
|
|
logsTable.classList.add('hidden');
|
|
}
|
|
|
|
function applyFilters() {
|
|
currentFilters = {
|
|
printer_id: printerFilter.value,
|
|
status: statusFilter.value,
|
|
hours: parseInt(timeFilter.value)
|
|
};
|
|
currentPage = 1;
|
|
loadLogs();
|
|
}
|
|
|
|
function loadLogs() {
|
|
showLoading();
|
|
|
|
const params = new URLSearchParams({
|
|
page: currentPage,
|
|
per_page: 50,
|
|
...currentFilters
|
|
});
|
|
|
|
fetch(`/api/admin/plug-monitoring/logs?${params}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
hideLoading();
|
|
|
|
if (data.success && data.logs.length > 0) {
|
|
renderLogs(data.logs);
|
|
renderPagination(data.pagination);
|
|
updateLogCount(data.pagination.total);
|
|
showTable();
|
|
} else {
|
|
showNoData();
|
|
logCount.textContent = 'Keine Logs gefunden';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
hideLoading();
|
|
console.error('Fehler beim Laden der Logs:', error);
|
|
showToast('Fehler beim Laden der Logs', 'error');
|
|
showNoData();
|
|
});
|
|
}
|
|
|
|
function renderLogs(logs) {
|
|
logsTableBody.innerHTML = '';
|
|
|
|
logs.forEach(log => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700';
|
|
|
|
row.innerHTML = `
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<span class="text-lg mr-2">${log.status_icon}</span>
|
|
<span class="text-sm font-medium ${log.status_color} capitalize">${log.status}</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">${log.printer_name || 'Unbekannt'}</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">${log.ip_address || '--'}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900 dark:text-white">${formatTimestamp(log.timestamp)}</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">${log.timestamp_relative}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSourceColor(log.source)}">
|
|
${log.source}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<div class="text-sm text-gray-900 dark:text-white">
|
|
${log.response_time_ms ? `⏱️ ${log.response_time_ms}ms` : ''}
|
|
${log.power_consumption ? `⚡ ${log.power_consumption}W` : ''}
|
|
${log.error_message ? `❌ ${log.error_message}` : ''}
|
|
</div>
|
|
${log.notes ? `<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">${log.notes}</div>` : ''}
|
|
</td>
|
|
`;
|
|
|
|
logsTableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function renderPagination(paginationData) {
|
|
if (paginationData.total_pages <= 1) {
|
|
pagination.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
const pages = [];
|
|
const startPage = Math.max(1, paginationData.page - 2);
|
|
const endPage = Math.min(paginationData.total_pages, paginationData.page + 2);
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
pages.push(i);
|
|
}
|
|
|
|
pagination.innerHTML = `
|
|
<div class="flex-1 flex justify-between sm:hidden">
|
|
<button ${!paginationData.has_prev ? 'disabled' : ''}
|
|
onclick="changePage(${paginationData.page - 1})"
|
|
class="relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 ${!paginationData.has_prev ? 'opacity-50 cursor-not-allowed' : ''}">
|
|
Zurück
|
|
</button>
|
|
<button ${!paginationData.has_next ? 'disabled' : ''}
|
|
onclick="changePage(${paginationData.page + 1})"
|
|
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 ${!paginationData.has_next ? 'opacity-50 cursor-not-allowed' : ''}">
|
|
Weiter
|
|
</button>
|
|
</div>
|
|
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
|
<div>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300">
|
|
Zeige <span class="font-medium">${(paginationData.page - 1) * paginationData.per_page + 1}</span> bis
|
|
<span class="font-medium">${Math.min(paginationData.page * paginationData.per_page, paginationData.total)}</span> von
|
|
<span class="font-medium">${paginationData.total}</span> Einträgen
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
|
|
<button ${!paginationData.has_prev ? 'disabled' : ''}
|
|
onclick="changePage(${paginationData.page - 1})"
|
|
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 ${!paginationData.has_prev ? 'opacity-50 cursor-not-allowed' : ''}">
|
|
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
${pages.map(page => `
|
|
<button onclick="changePage(${page})"
|
|
class="relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm font-medium ${page === paginationData.page
|
|
? 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900'
|
|
: 'text-gray-700 dark:text-gray-300'} hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
${page}
|
|
</button>
|
|
`).join('')}
|
|
<button ${!paginationData.has_next ? 'disabled' : ''}
|
|
onclick="changePage(${paginationData.page + 1})"
|
|
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 ${!paginationData.has_next ? 'opacity-50 cursor-not-allowed' : ''}">
|
|
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function updateLogCount(total) {
|
|
logCount.textContent = `${total} Logs gefunden`;
|
|
}
|
|
|
|
function formatTimestamp(timestamp) {
|
|
if (!timestamp) return '--';
|
|
const date = new Date(timestamp);
|
|
return date.toLocaleString('de-DE');
|
|
}
|
|
|
|
function getSourceColor(source) {
|
|
const colors = {
|
|
'system': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
|
'manual': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
|
'api': 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200',
|
|
'scheduler': 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200'
|
|
};
|
|
return colors[source] || 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200';
|
|
}
|
|
|
|
function showCleanupModal() {
|
|
cleanupModal.classList.remove('hidden');
|
|
}
|
|
|
|
function hideCleanupModal() {
|
|
cleanupModal.classList.add('hidden');
|
|
}
|
|
|
|
function performCleanup() {
|
|
const days = parseInt(cleanupDays.value);
|
|
|
|
if (days < 1 || days > 365) {
|
|
showToast('Ungültiger Wert für Tage (1-365)', 'error');
|
|
return;
|
|
}
|
|
|
|
fetch('/api/admin/plug-monitoring/cleanup', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': '{{ csrf_token() }}'
|
|
},
|
|
body: JSON.stringify({ days: days })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
hideCleanupModal();
|
|
if (data.success) {
|
|
showToast(`${data.deleted_count} alte Logs wurden gelöscht`, 'success');
|
|
loadLogs(); // Tabelle aktualisieren
|
|
} else {
|
|
showToast('Fehler beim Löschen der Logs: ' + data.error, 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
hideCleanupModal();
|
|
console.error('Fehler beim Löschen der Logs:', error);
|
|
showToast('Fehler beim Löschen der Logs', 'error');
|
|
});
|
|
}
|
|
|
|
// Globale Funktionen für Pagination
|
|
window.changePage = function(page) {
|
|
currentPage = page;
|
|
loadLogs();
|
|
};
|
|
|
|
// Hilfsfunktion für Toast-Nachrichten
|
|
function showToast(message, type = 'info') {
|
|
if (typeof window.showToast === 'function') {
|
|
window.showToast(message, type);
|
|
} else {
|
|
alert(message);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |