511 lines
21 KiB
HTML
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 %} |