🎉 Refactor and optimize database files, enhance error handling with new utility scripts 📚, and update documentation on fault tolerance and unattended operation. 🚀
This commit is contained in:
@@ -524,6 +524,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">System-Steuerung</h3>
|
||||
<div class="space-y-3">
|
||||
<button id="kiosk-restart-btn" class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<span>Kiosk neustarten</span>
|
||||
</button>
|
||||
<button id="restart-system-btn" class="w-full px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
|
||||
<svg class="w-4 h-4" 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>
|
||||
<span>System neustarten</span>
|
||||
</button>
|
||||
<button id="shutdown-system-btn" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18.364 5.636M5.636 18.364l12.728-12.728"/>
|
||||
</svg>
|
||||
<span>System herunterfahren</span>
|
||||
</button>
|
||||
<button id="system-status-btn" class="w-full px-4 py-2 bg-slate-500 text-white rounded-lg hover:bg-slate-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
|
||||
<svg class="w-4 h-4" 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"/>
|
||||
</svg>
|
||||
<span>System-Status</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Konfiguration</h3>
|
||||
<div class="space-y-3">
|
||||
@@ -533,8 +563,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<button id="update-printers-btn" class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium">
|
||||
Drucker aktualisieren
|
||||
</button>
|
||||
<button id="restart-system-btn" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium">
|
||||
System neustarten
|
||||
<button id="error-recovery-toggle-btn" class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<span>Fehlerresilienz</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -976,5 +1009,532 @@ function getCsrfToken() {
|
||||
const token = document.querySelector('meta[name="csrf-token"]');
|
||||
return token ? token.getAttribute('content') : '';
|
||||
}
|
||||
|
||||
// ===== ERWEITERTE SYSTEM-CONTROL-FUNKTIONALITÄT =====
|
||||
|
||||
class SystemControlManager {
|
||||
constructor() {
|
||||
this.pendingOperations = new Map();
|
||||
this.lastStatusUpdate = null;
|
||||
this.isInitialized = false;
|
||||
|
||||
this.initializeEventListeners();
|
||||
this.startStatusPolling();
|
||||
this.isInitialized = true;
|
||||
|
||||
console.log('🔧 System-Control-Manager initialisiert');
|
||||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
// Kiosk-Neustart
|
||||
const kioskRestartBtn = document.getElementById('kiosk-restart-btn');
|
||||
if (kioskRestartBtn) {
|
||||
kioskRestartBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleKioskRestart();
|
||||
});
|
||||
}
|
||||
|
||||
// System-Neustart
|
||||
const restartBtn = document.getElementById('restart-system-btn');
|
||||
if (restartBtn) {
|
||||
restartBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleSystemRestart();
|
||||
});
|
||||
}
|
||||
|
||||
// System-Shutdown
|
||||
const shutdownBtn = document.getElementById('shutdown-system-btn');
|
||||
if (shutdownBtn) {
|
||||
shutdownBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.handleSystemShutdown();
|
||||
});
|
||||
}
|
||||
|
||||
// System-Status
|
||||
const statusBtn = document.getElementById('system-status-btn');
|
||||
if (statusBtn) {
|
||||
statusBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.showSystemStatus();
|
||||
});
|
||||
}
|
||||
|
||||
// Error-Recovery Toggle
|
||||
const errorRecoveryBtn = document.getElementById('error-recovery-toggle-btn');
|
||||
if (errorRecoveryBtn) {
|
||||
errorRecoveryBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.toggleErrorRecovery();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleKioskRestart() {
|
||||
const confirmed = await this.confirmOperation(
|
||||
'Kiosk-Neustart',
|
||||
'Möchten Sie das Kiosk-Display neustarten? Dies dauert ca. 10-30 Sekunden.',
|
||||
'Der Kiosk wird neugestartet...'
|
||||
);
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/kiosk/restart', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
delay_seconds: 10,
|
||||
reason: 'Manueller Kiosk-Neustart über Admin-Panel'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
showNotification('Kiosk-Neustart geplant', 'success');
|
||||
this.trackOperation(result.operation_id, 'kiosk_restart');
|
||||
} else {
|
||||
showNotification(result.error || 'Fehler beim Kiosk-Neustart', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Kiosk-Neustart Fehler:', error);
|
||||
showNotification('Fehler beim Kiosk-Neustart: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async handleSystemRestart() {
|
||||
const confirmed = await this.confirmOperation(
|
||||
'System-Neustart',
|
||||
'WARNUNG: Das gesamte System wird neu gestartet! Dies dauert ca. 2-5 Minuten.',
|
||||
'Das System wird neu gestartet...',
|
||||
true
|
||||
);
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
// Zusätzliche Verzögerungsabfrage
|
||||
const delayInput = prompt('Verzögerung in Sekunden (10-3600):', '60');
|
||||
if (!delayInput) return;
|
||||
|
||||
const delay = Math.max(10, Math.min(3600, parseInt(delayInput) || 60));
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/system/restart', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
delay_seconds: delay,
|
||||
reason: 'Manueller System-Neustart über Admin-Panel'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
showNotification(`System-Neustart in ${delay} Sekunden geplant`, 'success');
|
||||
this.trackOperation(result.operation_id, 'system_restart');
|
||||
this.showCountdown(delay, 'System-Neustart');
|
||||
} else {
|
||||
showNotification(result.error || 'Fehler beim System-Neustart', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('System-Neustart Fehler:', error);
|
||||
showNotification('Fehler beim System-Neustart: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async handleSystemShutdown() {
|
||||
const confirmed = await this.confirmOperation(
|
||||
'System-Shutdown',
|
||||
'KRITISCHE WARNUNG: Das System wird komplett heruntergefahren!',
|
||||
'Das System wird heruntergefahren...',
|
||||
true
|
||||
);
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
// Doppelte Bestätigung
|
||||
const doubleConfirm = confirm('LETZTE WARNUNG: System wirklich herunterfahren?\nDas System muss danach manuell neu gestartet werden!');
|
||||
if (!doubleConfirm) return;
|
||||
|
||||
// Verzögerungsabfrage
|
||||
const delayInput = prompt('Verzögerung in Sekunden (10-3600):', '30');
|
||||
if (!delayInput) return;
|
||||
|
||||
const delay = Math.max(10, Math.min(3600, parseInt(delayInput) || 30));
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/system/shutdown', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
delay_seconds: delay,
|
||||
reason: 'Manueller System-Shutdown über Admin-Panel'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
showNotification(`System-Shutdown in ${delay} Sekunden geplant`, 'error');
|
||||
this.trackOperation(result.operation_id, 'system_shutdown');
|
||||
this.showCountdown(delay, 'System-Shutdown');
|
||||
} else {
|
||||
showNotification(result.error || 'Fehler beim System-Shutdown', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('System-Shutdown Fehler:', error);
|
||||
showNotification('Fehler beim System-Shutdown: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async showSystemStatus() {
|
||||
try {
|
||||
showNotification('System-Status wird geladen...', 'info');
|
||||
|
||||
const response = await fetch('/api/admin/system/status', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
});
|
||||
|
||||
const status = await response.json();
|
||||
|
||||
if (response.ok && status.success) {
|
||||
this.displaySystemStatusModal(status);
|
||||
} else {
|
||||
showNotification('Fehler beim Laden des System-Status', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('System-Status Fehler:', error);
|
||||
showNotification('Fehler beim System-Status: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async toggleErrorRecovery() {
|
||||
try {
|
||||
// Aktuellen Status abrufen
|
||||
const statusResponse = await fetch('/api/admin/error-recovery/status', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
});
|
||||
|
||||
const statusData = await statusResponse.json();
|
||||
const currentlyActive = statusData?.statistics?.monitoring_active || false;
|
||||
|
||||
// Toggle-Operation
|
||||
const response = await fetch('/api/admin/error-recovery/toggle', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
enable: !currentlyActive
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.success) {
|
||||
showNotification(result.message, 'success');
|
||||
this.updateErrorRecoveryButton(result.monitoring_active);
|
||||
} else {
|
||||
showNotification(result.error || 'Fehler beim Error-Recovery Toggle', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error-Recovery Toggle Fehler:', error);
|
||||
showNotification('Fehler beim Error-Recovery Toggle: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
updateErrorRecoveryButton(isActive) {
|
||||
const btn = document.getElementById('error-recovery-toggle-btn');
|
||||
if (btn) {
|
||||
if (isActive) {
|
||||
btn.classList.remove('bg-green-500', 'hover:bg-green-600');
|
||||
btn.classList.add('bg-yellow-500', 'hover:bg-yellow-600');
|
||||
btn.querySelector('span').textContent = 'Fehlerresilienz AN';
|
||||
} else {
|
||||
btn.classList.remove('bg-yellow-500', 'hover:bg-yellow-600');
|
||||
btn.classList.add('bg-green-500', 'hover:bg-green-600');
|
||||
btn.querySelector('span').textContent = 'Fehlerresilienz AUS';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async confirmOperation(title, message, processingMessage, isDestructive = false) {
|
||||
const bgColor = isDestructive ? 'bg-red-100 dark:bg-red-900' : 'bg-blue-100 dark:bg-blue-900';
|
||||
const iconColor = isDestructive ? 'text-red-600 dark:text-red-400' : 'text-blue-600 dark:text-blue-400';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-2xl max-w-md w-full">
|
||||
<div class="text-center mb-6">
|
||||
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full ${bgColor} mb-4">
|
||||
<svg class="h-6 w-6 ${iconColor}" 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 15.5c-.77.833.192 2.5 1.732 2.5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">${title}</h3>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">${message}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-3">
|
||||
<button id="modal-cancel" class="flex-1 px-4 py-2 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-300 dark:hover:bg-slate-600 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button id="modal-confirm" class="flex-1 px-4 py-2 ${isDestructive ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-500 hover:bg-blue-600'} text-white rounded-xl transition-colors">
|
||||
Bestätigen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const cancelBtn = modal.querySelector('#modal-cancel');
|
||||
const confirmBtn = modal.querySelector('#modal-confirm');
|
||||
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
// ESC-Key
|
||||
const handleEsc = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.body.removeChild(modal);
|
||||
document.removeEventListener('keydown', handleEsc);
|
||||
resolve(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEsc);
|
||||
});
|
||||
}
|
||||
|
||||
showCountdown(seconds, operationType) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black/75 backdrop-blur-sm z-50 flex items-center justify-center p-4';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl max-w-sm w-full text-center">
|
||||
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-orange-100 dark:bg-orange-900 mb-6">
|
||||
<svg class="h-8 w-8 text-orange-600 dark:text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">${operationType}</h3>
|
||||
<p class="text-slate-600 dark:text-slate-400 mb-6">Wird ausgeführt in:</p>
|
||||
<div id="countdown-timer" class="text-4xl font-bold text-orange-600 dark:text-orange-400 mb-6">${seconds}</div>
|
||||
<button id="countdown-cancel" class="px-6 py-2 bg-red-500 text-white rounded-xl hover:bg-red-600 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const timerEl = modal.querySelector('#countdown-timer');
|
||||
const cancelBtn = modal.querySelector('#countdown-cancel');
|
||||
|
||||
let remainingSeconds = seconds;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
remainingSeconds--;
|
||||
timerEl.textContent = remainingSeconds;
|
||||
|
||||
if (remainingSeconds <= 0) {
|
||||
clearInterval(interval);
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
cancelBtn.addEventListener('click', async () => {
|
||||
clearInterval(interval);
|
||||
document.body.removeChild(modal);
|
||||
|
||||
// Versuche Operation zu stornieren
|
||||
try {
|
||||
// Hier könnte der API-Call zum Stornieren implementiert werden
|
||||
showNotification('Operation wird storniert...', 'info');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Stornieren:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
displaySystemStatusModal(status) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
|
||||
|
||||
const servicesHtml = Object.entries(status.services || {}).map(([name, state]) => {
|
||||
const isActive = state === 'active';
|
||||
return `
|
||||
<div class="flex items-center justify-between py-2 px-3 rounded-lg ${isActive ? 'bg-green-50 dark:bg-green-900/20' : 'bg-red-50 dark:bg-red-900/20'}">
|
||||
<span class="font-medium">${name}</span>
|
||||
<span class="px-2 py-1 rounded-full text-xs ${isActive ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100'}">${state}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
const metrics = status.system_metrics || {};
|
||||
const errorRecovery = status.error_recovery || {};
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-2xl max-w-2xl w-full max-h-90vh overflow-y-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-xl font-bold text-slate-900 dark:text-white">System-Status</h3>
|
||||
<button id="close-status-modal" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-300">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Services -->
|
||||
<div>
|
||||
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">Services</h4>
|
||||
<div class="space-y-2">
|
||||
${servicesHtml}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System-Metriken -->
|
||||
<div>
|
||||
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">System-Metriken</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between">
|
||||
<span>Speicher:</span>
|
||||
<span class="font-medium ${metrics.memory_percent > 80 ? 'text-red-600' : 'text-green-600'}">${metrics.memory_percent?.toFixed(1) || 0}%</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Festplatte:</span>
|
||||
<span class="font-medium ${metrics.disk_percent > 90 ? 'text-red-600' : 'text-green-600'}">${metrics.disk_percent?.toFixed(1) || 0}%</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>System-Last:</span>
|
||||
<span class="font-medium">${metrics.load_average?.toFixed(2) || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fehlerresilienz -->
|
||||
<div class="md:col-span-2">
|
||||
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">Fehlerresilienz</h4>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||
<div class="text-2xl font-bold text-blue-600">${errorRecovery.total_errors || 0}</div>
|
||||
<div class="text-xs text-slate-600 dark:text-slate-400">Gesamt-Fehler</div>
|
||||
</div>
|
||||
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||
<div class="text-2xl font-bold text-orange-600">${errorRecovery.errors_last_24h || 0}</div>
|
||||
<div class="text-xs text-slate-600 dark:text-slate-400">Letzten 24h</div>
|
||||
</div>
|
||||
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||
<div class="text-2xl font-bold text-green-600">${errorRecovery.recovery_success_rate || 0}%</div>
|
||||
<div class="text-xs text-slate-600 dark:text-slate-400">Erfolgsrate</div>
|
||||
</div>
|
||||
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||
<div class="text-2xl font-bold ${status.is_safe ? 'text-green-600' : 'text-red-600'}">${status.is_safe ? 'SICHER' : 'UNSICHER'}</div>
|
||||
<div class="text-xs text-slate-600 dark:text-slate-400">Status</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-4 border-t border-slate-200 dark:border-slate-600">
|
||||
<div class="text-sm text-slate-500 dark:text-slate-400">
|
||||
Letztes Update: ${new Date().toLocaleString('de-DE')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const closeBtn = modal.querySelector('#close-status-modal');
|
||||
closeBtn.addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
});
|
||||
|
||||
// ESC zum Schließen
|
||||
const handleEsc = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.body.removeChild(modal);
|
||||
document.removeEventListener('keydown', handleEsc);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEsc);
|
||||
}
|
||||
|
||||
trackOperation(operationId, type) {
|
||||
this.pendingOperations.set(operationId, {
|
||||
id: operationId,
|
||||
type: type,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
async startStatusPolling() {
|
||||
// Überwache Error-Recovery-Status
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/admin/error-recovery/status', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.updateErrorRecoveryButton(data?.statistics?.monitoring_active || false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug('Status-Polling Fehler:', error);
|
||||
}
|
||||
}, 30000); // Alle 30 Sekunden
|
||||
}
|
||||
}
|
||||
|
||||
// Globaler System-Control-Manager
|
||||
let systemControlManager = null;
|
||||
|
||||
// Initialisierung beim DOM-Laden
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!systemControlManager) {
|
||||
systemControlManager = new SystemControlManager();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
Reference in New Issue
Block a user