feat: Einführung neuer API-Endpunkte zur Verwaltung von Benutzereinstellungen und Druckerstatus. Implementierung von Funktionen zur Überprüfung wartender Jobs und zur Aktualisierung aller Drucker. Verbesserung der Benutzeroberfläche durch optimierte Ladeanzeigen und Warnungen für Offline-Drucker. Anpassungen in den Templates zur Unterstützung neuer Funktionen und zur Verbesserung der Benutzererfahrung.
This commit is contained in:
@@ -997,6 +997,379 @@ async function createPrinter(formData) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fehlende Admin-Funktionen
|
||||
*/
|
||||
|
||||
// System-Einstellungen anzeigen
|
||||
function showSystemSettings() {
|
||||
const modal = createModal('⚙️ System-Einstellungen', `
|
||||
<form id="settings-form" class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Server-Konfiguration</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Host</label>
|
||||
<input type="text" name="host" value="0.0.0.0"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
dark:bg-gray-700 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Port</label>
|
||||
<input type="number" name="port" value="443" min="1" max="65535"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
dark:bg-gray-700 dark:text-white">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="ssl_enabled" id="ssl-enabled" checked
|
||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<label for="ssl-enabled" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">
|
||||
SSL aktiviert
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Scheduler-Einstellungen</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Intervall (Sekunden)</label>
|
||||
<input type="number" name="scheduler_interval" value="60" min="10" max="3600"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
dark:bg-gray-700 dark:text-white">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="scheduler_enabled" id="scheduler-enabled" checked
|
||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||
<label for="scheduler-enabled" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">
|
||||
Scheduler aktiviert
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<button type="button" onclick="closeModal()"
|
||||
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
|
||||
Einstellungen speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
|
||||
// Form-Handler
|
||||
const form = document.getElementById('settings-form');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
await saveSystemSettings(new FormData(e.target));
|
||||
});
|
||||
}
|
||||
|
||||
// System-Einstellungen speichern
|
||||
async function saveSystemSettings(formData) {
|
||||
try {
|
||||
showLoadingOverlay();
|
||||
|
||||
const settings = {
|
||||
server: {
|
||||
host: formData.get('host'),
|
||||
port: parseInt(formData.get('port')),
|
||||
ssl_enabled: formData.get('ssl_enabled') === 'on'
|
||||
},
|
||||
scheduler: {
|
||||
interval_seconds: parseInt(formData.get('scheduler_interval')),
|
||||
enabled: formData.get('scheduler_enabled') === 'on'
|
||||
}
|
||||
};
|
||||
|
||||
const url = `${API_BASE_URL}/api/admin/settings`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showNotification('✅ Einstellungen erfolgreich gespeichert!', 'success');
|
||||
closeModal();
|
||||
} else {
|
||||
showNotification('❌ Fehler beim Speichern: ' + result.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Settings save error:', error);
|
||||
showNotification('❌ Fehler beim Speichern der Einstellungen', 'error');
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// Alle Drucker aktualisieren
|
||||
async function updateAllPrinters() {
|
||||
if (!confirm('🔄 Möchten Sie den Status aller Drucker aktualisieren?')) return;
|
||||
|
||||
showLoadingOverlay();
|
||||
|
||||
try {
|
||||
const url = `${API_BASE_URL}/api/admin/printers/update-all`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showNotification(`✅ ${result.message}`, 'success');
|
||||
// Seite nach 2 Sekunden neu laden
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
showNotification('❌ Fehler beim Aktualisieren: ' + result.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Printer update error:', error);
|
||||
showNotification('❌ Fehler beim Aktualisieren der Drucker', 'error');
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// System neustarten
|
||||
async function restartSystem() {
|
||||
if (!confirm('🔄 Möchten Sie das System wirklich neu starten? Dies kann einige Minuten dauern.')) return;
|
||||
|
||||
showLoadingOverlay();
|
||||
|
||||
try {
|
||||
const url = `${API_BASE_URL}/api/admin/system/restart`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showNotification('🔄 System wird neu gestartet...', 'info');
|
||||
// Nach 5 Sekunden versuchen, die Seite neu zu laden
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 5000);
|
||||
} else {
|
||||
showNotification('❌ Fehler beim Neustart: ' + result.message, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('System restart error:', error);
|
||||
showNotification('❌ Fehler beim System-Neustart', 'error');
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// Drucker verwalten
|
||||
function managePrinter(printerId) {
|
||||
window.location.href = `/admin/printers/${printerId}/manage`;
|
||||
}
|
||||
|
||||
// Drucker-Einstellungen anzeigen
|
||||
function showPrinterSettings(printerId) {
|
||||
window.location.href = `/admin/printers/${printerId}/settings`;
|
||||
}
|
||||
|
||||
// Benutzer bearbeiten
|
||||
function editUser(userId) {
|
||||
window.location.href = `/admin/users/${userId}/edit`;
|
||||
}
|
||||
|
||||
// Benutzer löschen
|
||||
async function deleteUser(userId, userName) {
|
||||
if (!confirm(`⚠️ Möchten Sie den Benutzer "${userName}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.`)) return;
|
||||
|
||||
showLoadingOverlay();
|
||||
|
||||
try {
|
||||
const url = `${API_BASE_URL}/api/admin/users/${userId}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showNotification(`✅ Benutzer "${userName}" erfolgreich gelöscht`, 'success');
|
||||
// Seite nach 2 Sekunden neu laden
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
showNotification('❌ Fehler beim Löschen: ' + result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('User delete error:', error);
|
||||
showNotification('❌ Fehler beim Löschen des Benutzers', 'error');
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// Job-Aktionen verarbeiten
|
||||
async function handleJobAction(action, jobId) {
|
||||
let confirmMessage = '';
|
||||
let url = '';
|
||||
let method = 'POST';
|
||||
|
||||
switch (action) {
|
||||
case 'cancel':
|
||||
confirmMessage = 'Möchten Sie diesen Job wirklich abbrechen?';
|
||||
url = `${API_BASE_URL}/api/jobs/${jobId}/cancel`;
|
||||
break;
|
||||
case 'delete':
|
||||
confirmMessage = 'Möchten Sie diesen Job wirklich löschen?';
|
||||
url = `${API_BASE_URL}/api/jobs/${jobId}`;
|
||||
method = 'DELETE';
|
||||
break;
|
||||
case 'finish':
|
||||
confirmMessage = 'Möchten Sie diesen Job als beendet markieren?';
|
||||
url = `${API_BASE_URL}/api/jobs/${jobId}/finish`;
|
||||
break;
|
||||
default:
|
||||
showNotification('❌ Unbekannte Aktion', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(confirmMessage)) return;
|
||||
|
||||
showLoadingOverlay();
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCSRFToken()
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showNotification('✅ Aktion erfolgreich ausgeführt', 'success');
|
||||
// Seite nach 2 Sekunden neu laden
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
showNotification('❌ Fehler: ' + result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Job action error:', error);
|
||||
showNotification('❌ Fehler beim Ausführen der Aktion', 'error');
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// Such- und Filter-Funktionen
|
||||
function filterUsers(searchTerm) {
|
||||
const userRows = document.querySelectorAll('tbody tr');
|
||||
|
||||
userRows.forEach(row => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
const matches = text.includes(searchTerm.toLowerCase());
|
||||
row.style.display = matches ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function filterJobs(status) {
|
||||
const jobRows = document.querySelectorAll('.job-row, [data-job-status]');
|
||||
|
||||
jobRows.forEach(row => {
|
||||
if (status === 'all') {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
const jobStatus = row.dataset.jobStatus || row.querySelector('.status')?.textContent?.toLowerCase();
|
||||
const matches = jobStatus === status.toLowerCase();
|
||||
row.style.display = matches ? '' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Daten-Export
|
||||
function exportData(type) {
|
||||
const url = `${API_BASE_URL}/api/admin/export/${type}`;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
// Analytics-Daten laden
|
||||
async function loadAnalyticsData() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/stats/live`);
|
||||
const data = await response.json();
|
||||
|
||||
// Charts mit Chart.js erstellen (falls verfügbar)
|
||||
if (typeof Chart !== 'undefined') {
|
||||
createPrinterUsageChart(data);
|
||||
createSuccessRateChart(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Analytics data error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Live-Analytics starten
|
||||
function startLiveAnalytics() {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/stats/live`);
|
||||
const data = await response.json();
|
||||
|
||||
// Live-Werte aktualisieren
|
||||
document.getElementById('live-jobs').textContent = data.jobs?.active || 0;
|
||||
document.getElementById('live-printers').textContent = data.printers?.online || 0;
|
||||
document.getElementById('live-queue').textContent = data.jobs?.queued || 0;
|
||||
document.getElementById('live-success').textContent = (data.jobs?.success_rate || 0) + '%';
|
||||
} catch (error) {
|
||||
console.error('Live analytics error:', error);
|
||||
}
|
||||
}, 5000); // Alle 5 Sekunden
|
||||
}
|
||||
|
||||
// System-Status aktualisieren
|
||||
async function updateSystemStatus() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/system/status`);
|
||||
const data = await response.json();
|
||||
|
||||
// Status-Indikatoren aktualisieren
|
||||
const indicators = document.querySelectorAll('.status-indicator');
|
||||
indicators.forEach(indicator => {
|
||||
// Aktualisiere basierend auf data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('System status update error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drucker aktualisieren
|
||||
*/
|
||||
|
Reference in New Issue
Block a user