2025-06-04 10:03:22 +02:00

511 lines
21 KiB
HTML

{% extends "base.html" %}
{% block title %}Steckdosen-Test - Mercedes-Benz TBA Marienfelde{% endblock %}
{% block extra_css %}
<style>
.test-card {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e2e8f0;
border-radius: 16px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.dark .test-card {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #334155;
}
.risk-low { border-left: 4px solid #10b981; }
.risk-medium { border-left: 4px solid #f59e0b; }
.risk-high { border-left: 4px solid #ef4444; }
.socket-online { color: #10b981; }
.socket-offline { color: #ef4444; }
.socket-error { color: #f59e0b; }
.test-button {
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
border: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.test-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.btn-test-on { background: #16a34a; color: white; }
.btn-test-off { background: #ef4444; color: white; }
.btn-test-status { background: #0073ce; color: white; }
.warning-banner {
background: linear-gradient(90deg, rgba(239, 68, 68, 0.1) 0%, rgba(220, 38, 38, 0.05) 100%);
border: 1px solid rgba(239, 68, 68, 0.2);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
.info-banner {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.05) 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
.loading-spinner {
border: 2px solid #f3f4f6;
border-top: 2px solid #0073ce;
border-radius: 50%;
width: 1rem;
height: 1rem;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<div class="space-y-8">
<!-- Header -->
<div class="dashboard-card p-6">
<div class="flex items-center gap-6">
<div class="w-16 h-16 bg-red-600 text-white rounded-xl flex items-center justify-center">
<svg class="w-8 h-8" 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"/>
</svg>
</div>
<div>
<h1 class="text-4xl font-bold text-mercedes-black dark:text-white">⚡ Steckdosen-Test</h1>
<p class="text-mercedes-gray dark:text-slate-400 mt-1">Sichere Testfunktion für Ausbilder und Administratoren</p>
</div>
</div>
</div>
<!-- Sicherheitshinweis -->
<div class="warning-banner">
<div class="flex items-start gap-3">
<svg class="w-6 h-6 text-red-600 flex-shrink-0 mt-0.5" 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 0L3.268 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div>
<h3 class="font-semibold text-red-800 dark:text-red-200">⚠️ SICHERHEITSHINWEIS</h3>
<p class="text-red-700 dark:text-red-300 mt-1">
Diese Funktion ist nur für geschulte Ausbilder und Administratoren bestimmt.
Prüfen Sie immer den Status vor dem Ein-/Ausschalten von Steckdosen.
</p>
</div>
</div>
</div>
<!-- Übersicht aller Steckdosen -->
<div class="dashboard-card p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-mercedes-black dark:text-white">Übersicht aller Steckdosen</h2>
<button onclick="loadAllSocketsStatus()" class="test-button btn-test-status">
<svg class="w-5 h-5" 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"/>
</svg>
Alle Status aktualisieren
</button>
</div>
<!-- Statistiken -->
<div id="socket-summary" class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
<!-- Wird per JavaScript gefüllt -->
</div>
<!-- Steckdosen-Liste -->
<div id="all-sockets-list" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="flex items-center justify-center p-8">
<div class="loading-spinner"></div>
<span class="ml-2">Lade Steckdosen-Status...</span>
</div>
</div>
</div>
<!-- Einzeltest -->
<div class="dashboard-card p-6">
<h2 class="text-2xl font-bold text-mercedes-black dark:text-white mb-6">Einzelne Steckdose testen</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Drucker-Auswahl -->
<div class="space-y-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Drucker auswählen:
</label>
<select id="printer-select" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">Bitte Drucker auswählen...</option>
</select>
<button onclick="loadSingleSocketStatus()" id="load-status-btn"
class="test-button btn-test-status w-full" disabled>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
</svg>
Status prüfen
</button>
</div>
<!-- Status-Anzeige -->
<div id="single-socket-status" class="space-y-4">
<div class="info-banner">
<p class="text-blue-700 dark:text-blue-300">
Wählen Sie einen Drucker aus um den Steckdosen-Status zu prüfen.
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Test-Bestätigungsmodal -->
<div id="test-confirmation-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center p-4">
<div class="bg-white dark:bg-gray-800 rounded-lg max-w-md w-full p-6">
<div class="flex items-center gap-3 mb-4">
<svg class="w-8 h-8 text-red-600" 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 0L3.268 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<h3 class="text-lg font-semibold">Test bestätigen</h3>
</div>
<div id="test-modal-content">
<!-- Wird per JavaScript gefüllt -->
</div>
<div class="flex gap-3 mt-6">
<button onclick="closeTestModal()" class="test-button bg-gray-500 text-white flex-1">
Abbrechen
</button>
<button onclick="executeTest()" id="confirm-test-btn" class="test-button bg-red-600 text-white flex-1">
Test durchführen
</button>
</div>
</div>
</div>
<script>
let currentTestData = null;
let printers = [];
// Seite initialisieren
document.addEventListener('DOMContentLoaded', function() {
loadPrinters();
loadAllSocketsStatus();
});
// Drucker laden
async function loadPrinters() {
try {
const response = await fetch('/api/printers');
const data = await response.json();
if (data.success) {
printers = data.printers;
const select = document.getElementById('printer-select');
select.innerHTML = '<option value="">Bitte Drucker auswählen...</option>';
printers.forEach(printer => {
if (printer.plug_ip) { // Nur Drucker mit Steckdose
const option = document.createElement('option');
option.value = printer.id;
option.textContent = `${printer.name} (${printer.location || 'Unbekannter Standort'})`;
select.appendChild(option);
}
});
select.addEventListener('change', function() {
const loadBtn = document.getElementById('load-status-btn');
loadBtn.disabled = !this.value;
});
}
} catch (error) {
console.error('Fehler beim Laden der Drucker:', error);
showNotification('Fehler beim Laden der Drucker', 'error');
}
}
// Alle Steckdosen-Status laden
async function loadAllSocketsStatus() {
try {
const response = await fetch('/api/printers/test/all-sockets');
const data = await response.json();
if (data.success) {
displaySocketsSummary(data.summary);
displayAllSockets(data.sockets);
} else {
throw new Error(data.error || 'Unbekannter Fehler');
}
} catch (error) {
console.error('Fehler beim Laden der Steckdosen:', error);
showNotification('Fehler beim Laden der Steckdosen: ' + error.message, 'error');
}
}
// Einzelnen Steckdosen-Status laden
async function loadSingleSocketStatus() {
const printerId = document.getElementById('printer-select').value;
if (!printerId) return;
const statusDiv = document.getElementById('single-socket-status');
statusDiv.innerHTML = '<div class="flex items-center"><div class="loading-spinner"></div><span class="ml-2">Status wird geladen...</span></div>';
try {
const response = await fetch(`/api/printers/test/socket/${printerId}`);
const data = await response.json();
if (data.success) {
displaySingleSocketStatus(data);
} else {
statusDiv.innerHTML = `<div class="warning-banner"><p class="text-red-700">${data.error}</p></div>`;
}
} catch (error) {
console.error('Fehler beim Laden des Socket-Status:', error);
statusDiv.innerHTML = `<div class="warning-banner"><p class="text-red-700">Fehler: ${error.message}</p></div>`;
}
}
// Zusammenfassung anzeigen
function displaySocketsSummary(summary) {
const summaryDiv = document.getElementById('socket-summary');
summaryDiv.innerHTML = `
<div class="bg-blue-50 dark:bg-blue-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">${summary.total_sockets}</div>
<div class="text-sm text-blue-800 dark:text-blue-300">Gesamt</div>
</div>
<div class="bg-green-50 dark:bg-green-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">${summary.online}</div>
<div class="text-sm text-green-800 dark:text-green-300">Online</div>
</div>
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<div class="text-2xl font-bold text-gray-600 dark:text-gray-400">${summary.offline}</div>
<div class="text-sm text-gray-800 dark:text-gray-300">Offline</div>
</div>
<div class="bg-red-50 dark:bg-red-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-red-600 dark:text-red-400">${summary.error}</div>
<div class="text-sm text-red-800 dark:text-red-300">Fehler</div>
</div>
<div class="bg-orange-50 dark:bg-orange-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400">${summary.with_warnings}</div>
<div class="text-sm text-orange-800 dark:text-orange-300">Mit Warnungen</div>
</div>
`;
}
// Alle Steckdosen anzeigen
function displayAllSockets(sockets) {
const listDiv = document.getElementById('all-sockets-list');
if (sockets.length === 0) {
listDiv.innerHTML = '<div class="col-span-full text-center p-8"><p class="text-gray-500">Keine konfigurierten Steckdosen gefunden.</p></div>';
return;
}
listDiv.innerHTML = sockets.map(socket => `
<div class="test-card p-4 ${getRiskClass(socket.warnings.length)}">
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="font-semibold text-lg">${socket.printer.name}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">${socket.printer.location || 'Unbekannter Standort'}</p>
</div>
<div class="text-right">
<div class="socket-${socket.socket.status} font-semibold">
${getStatusText(socket.socket.status, socket.socket.device_on)}
</div>
${socket.socket.current_power ? `<div class="text-sm text-gray-600">${socket.socket.current_power}W</div>` : ''}
</div>
</div>
${socket.warnings.length > 0 ? `
<div class="bg-yellow-50 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded p-3 mb-4">
<div class="font-medium text-yellow-800 dark:text-yellow-200 mb-1">⚠️ Warnungen:</div>
${socket.warnings.map(warning => `<div class="text-sm text-yellow-700 dark:text-yellow-300">• ${warning}</div>`).join('')}
</div>
` : ''}
<div class="flex gap-2">
<button onclick="testSocketControl(${socket.printer.id}, 'on')"
class="test-button btn-test-on flex-1 ${socket.socket.device_on ? 'opacity-50' : ''}">
⚡ Einschalten
</button>
<button onclick="testSocketControl(${socket.printer.id}, 'off')"
class="test-button btn-test-off flex-1 ${!socket.socket.device_on ? 'opacity-50' : ''}">
🔌 Ausschalten
</button>
</div>
</div>
`).join('');
}
// Einzelstatus anzeigen
function displaySingleSocketStatus(data) {
const statusDiv = document.getElementById('single-socket-status');
const riskClass = getRiskClass(data.safety.warnings.length);
statusDiv.innerHTML = `
<div class="test-card p-4 ${riskClass}">
<div class="mb-4">
<h3 class="font-semibold text-lg">${data.printer.name}</h3>
<p class="text-sm text-gray-600">${data.printer.location}</p>
<div class="mt-2">
<span class="socket-${data.socket.status} font-semibold">
${getStatusText(data.socket.status, data.socket.info?.device_on)}
</span>
${data.socket.info?.current_power ? `${data.socket.info.current_power}W` : ''}
</div>
</div>
${data.safety.warnings.length > 0 ? `
<div class="warning-banner mb-4">
<div class="font-medium mb-2">⚠️ Sicherheitswarnungen:</div>
${data.safety.warnings.map(warning => `<div>• ${warning}</div>`).join('')}
</div>
` : ''}
${data.safety.recommendations.length > 0 ? `
<div class="info-banner mb-4">
<div class="font-medium mb-2">💡 Empfehlungen:</div>
${data.safety.recommendations.map(rec => `<div>• ${rec}</div>`).join('')}
</div>
` : ''}
<div class="flex gap-2">
<button onclick="testSocketControl(${data.printer.id}, 'on')"
class="test-button btn-test-on flex-1">
⚡ Test: Einschalten
</button>
<button onclick="testSocketControl(${data.printer.id}, 'off')"
class="test-button btn-test-off flex-1">
🔌 Test: Ausschalten
</button>
</div>
</div>
`;
}
// Test-Modal öffnen
function testSocketControl(printerId, action) {
const printer = printers.find(p => p.id == printerId);
if (!printer) return;
currentTestData = { printerId, action, printer };
const modal = document.getElementById('test-confirmation-modal');
const content = document.getElementById('test-modal-content');
content.innerHTML = `
<p class="mb-4">
<strong>Drucker:</strong> ${printer.name}<br>
<strong>Aktion:</strong> Steckdose ${action === 'on' ? 'einschalten' : 'ausschalten'}
</p>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Grund für den Test:</label>
<input type="text" id="test-reason" class="w-full p-2 border rounded"
placeholder="z.B. Routinetest, Wartung, etc." value="Routinetest">
</div>
<div class="mb-4">
<label class="flex items-center">
<input type="checkbox" id="force-test" class="mr-2">
<span class="text-sm">Sicherheitswarnungen überschreiben (force)</span>
</label>
</div>
`;
modal.classList.remove('hidden');
}
// Test ausführen
async function executeTest() {
if (!currentTestData) return;
const reason = document.getElementById('test-reason').value || 'Routinetest';
const force = document.getElementById('force-test').checked;
const confirmBtn = document.getElementById('confirm-test-btn');
confirmBtn.innerHTML = '<div class="loading-spinner"></div> Teste...';
confirmBtn.disabled = true;
try {
const response = await fetch(`/api/printers/test/socket/${currentTestData.printerId}/control`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name=csrf-token]').getAttribute('content')
},
body: JSON.stringify({
action: currentTestData.action,
test_reason: reason,
force: force
})
});
const data = await response.json();
if (data.success) {
showNotification(`Test erfolgreich: ${data.message}`, 'success');
loadAllSocketsStatus();
loadSingleSocketStatus();
} else {
if (data.requires_force) {
showNotification('Test blockiert: ' + data.error + ' Aktivieren Sie "force" um fortzufahren.', 'warning');
} else {
showNotification('Test fehlgeschlagen: ' + data.error, 'error');
}
}
} catch (error) {
console.error('Fehler beim Test:', error);
showNotification('Fehler beim Test: ' + error.message, 'error');
} finally {
closeTestModal();
}
}
// Modal schließen
function closeTestModal() {
const modal = document.getElementById('test-confirmation-modal');
modal.classList.add('hidden');
currentTestData = null;
const confirmBtn = document.getElementById('confirm-test-btn');
confirmBtn.innerHTML = 'Test durchführen';
confirmBtn.disabled = false;
}
// Hilfsfunktionen
function getRiskClass(warningCount) {
if (warningCount === 0) return 'risk-low';
if (warningCount <= 2) return 'risk-medium';
return 'risk-high';
}
function getStatusText(status, deviceOn) {
switch (status) {
case 'online': return deviceOn ? '🟢 Eingeschaltet' : '🔴 Ausgeschaltet';
case 'offline': return '🔴 Ausgeschaltet';
case 'error': return '⚠️ Fehler';
default: return '❓ Unbekannt';
}
}
function showNotification(message, type) {
// Einfache Benachrichtigung - kann durch Toast-System ersetzt werden
alert(message);
}
</script>
{% endblock %}