Projektarbeit-MYP/backend/templates/admin_plug_monitoring.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 %}