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:
@@ -9,6 +9,16 @@
|
||||
<script src="{{ url_for('static', filename='js/admin.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/admin-system.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/admin-live.js') }}" defer></script>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="loading-overlay" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center hidden">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
<span class="text-lg font-medium text-gray-900 dark:text-white">Wird geladen...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@@ -35,6 +35,17 @@
|
||||
<option value="">Drucker auswählen...</option>
|
||||
<!-- Wird durch JavaScript gefüllt -->
|
||||
</select>
|
||||
<div id="printer-status-warning" class="mt-2 hidden">
|
||||
<div class="flex items-center p-3 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg">
|
||||
<svg class="w-5 h-5 text-orange-500 mr-2 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<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-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-orange-800 dark:text-orange-200">Offline-Drucker ausgewählt</p>
|
||||
<p class="text-orange-700 dark:text-orange-300">Der Job wird erstellt, startet aber erst, wenn der Drucker online geht.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gewünschte Startzeit -->
|
||||
@@ -202,12 +213,31 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
loadPrinters();
|
||||
loadActiveJobs();
|
||||
|
||||
// Event-Listener für Drucker-Auswahl (Offline-Warnung)
|
||||
const printerSelect = document.getElementById('printer_id');
|
||||
const statusWarning = document.getElementById('printer-status-warning');
|
||||
|
||||
if (printerSelect && statusWarning) {
|
||||
printerSelect.addEventListener('change', function() {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
|
||||
if (selectedOption && selectedOption.getAttribute('data-offline') === 'true') {
|
||||
statusWarning.classList.remove('hidden');
|
||||
} else {
|
||||
statusWarning.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Formulare initialisieren
|
||||
initNewJobForm();
|
||||
initExtendJobForm();
|
||||
|
||||
// Timer für automatische Aktualisierung der Jobs (alle 60 Sekunden)
|
||||
setInterval(loadActiveJobs, 60000);
|
||||
|
||||
// Timer für Überprüfung wartender Jobs (alle 30 Sekunden)
|
||||
setInterval(checkWaitingJobs, 30000);
|
||||
});
|
||||
|
||||
// Hilfsfunktion zum Formatieren des Datums für Datetime-Input
|
||||
@@ -329,7 +359,8 @@ function populatePrinterSelect(printers, onlineOnly = false) {
|
||||
statusIcon = '🔴';
|
||||
statusText = 'Offline';
|
||||
option.style.color = '#dc2626'; // Rot für offline
|
||||
option.disabled = true; // Offline-Drucker deaktivieren
|
||||
// Offline-Drucker NICHT deaktivieren, aber kennzeichnen
|
||||
option.setAttribute('data-offline', 'true');
|
||||
}
|
||||
|
||||
// Letzter Check-Zeitstempel
|
||||
@@ -474,6 +505,35 @@ function loadActiveJobs() {
|
||||
});
|
||||
}
|
||||
|
||||
// Überprüfung wartender Jobs
|
||||
function checkWaitingJobs() {
|
||||
fetch('/api/jobs/check-waiting', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.updated_jobs && data.updated_jobs.length > 0) {
|
||||
// Benachrichtigung für aktivierte Jobs
|
||||
data.updated_jobs.forEach(job => {
|
||||
showNotification(
|
||||
`🎉 Gute Nachrichten! Drucker "${job.printer_name}" ist online. Ihr Job "${job.name}" wurde aktiviert und startet bald.`,
|
||||
'success'
|
||||
);
|
||||
});
|
||||
|
||||
// Jobs neu laden, um aktualisierte Status anzuzeigen
|
||||
loadActiveJobs();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Überprüfen wartender Jobs:', error);
|
||||
// Stille Fehler - nicht den Benutzer stören
|
||||
});
|
||||
}
|
||||
|
||||
// Initialisierung des Formulars für neue Jobs
|
||||
function initNewJobForm() {
|
||||
const form = document.getElementById('newJobForm');
|
||||
@@ -493,6 +553,28 @@ function initNewJobForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfen, ob ein Offline-Drucker ausgewählt wurde
|
||||
const selectedOption = document.querySelector(`#printer_id option[value="${printer_id}"]`);
|
||||
if (selectedOption && selectedOption.getAttribute('data-offline') === 'true') {
|
||||
const printerName = selectedOption.textContent.split(' (')[0].replace('🔴 ', '');
|
||||
const confirmOffline = confirm(
|
||||
`⚠️ WARNUNG: Offline-Drucker ausgewählt!\n\n` +
|
||||
`Der Drucker "${printerName}" ist derzeit OFFLINE.\n\n` +
|
||||
`Wenn Sie fortfahren:\n` +
|
||||
`• Der Job wird als "Wartend auf Drucker" markiert\n` +
|
||||
`• Sie erhalten eine Benachrichtigung, wenn der Drucker online geht\n` +
|
||||
`• Der Job startet automatisch, sobald der Drucker verfügbar ist\n\n` +
|
||||
`Möchten Sie trotzdem fortfahren?`
|
||||
);
|
||||
|
||||
if (!confirmOffline) {
|
||||
showNotification('Job-Erstellung abgebrochen. Bitte wählen Sie einen Online-Drucker oder warten Sie, bis der gewünschte Drucker verfügbar ist.', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
showNotification(`Job für Offline-Drucker "${printerName}" wird erstellt. Sie werden benachrichtigt, wenn der Drucker online geht.`, 'warning');
|
||||
}
|
||||
|
||||
// Startzeit in ISO-Format konvertieren
|
||||
const start_date = new Date(start_time);
|
||||
|
||||
@@ -761,10 +843,28 @@ function renderJobCard(job) {
|
||||
</span>
|
||||
`;
|
||||
} else if (job.status === 'scheduled') {
|
||||
// Prüfe, ob der Drucker online ist
|
||||
const printerOnline = job.printer?.status === 'available' || job.printer?.active;
|
||||
if (printerOnline) {
|
||||
statusBadge = `
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-400">
|
||||
<span class="w-2 h-2 mr-1 bg-blue-500 rounded-full"></span>
|
||||
Geplant
|
||||
</span>
|
||||
`;
|
||||
} else {
|
||||
statusBadge = `
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-800 dark:text-orange-400">
|
||||
<span class="w-2 h-2 mr-1 bg-orange-500 rounded-full animate-pulse"></span>
|
||||
Wartend auf Drucker
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
} else if (job.status === 'waiting_for_printer') {
|
||||
statusBadge = `
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-400">
|
||||
<span class="w-2 h-2 mr-1 bg-blue-500 rounded-full"></span>
|
||||
Geplant
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-400">
|
||||
<span class="w-2 h-2 mr-1 bg-yellow-500 rounded-full animate-pulse"></span>
|
||||
Drucker offline
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
@@ -2,6 +2,12 @@
|
||||
|
||||
{% block title %}Einstellungen - MYP Platform{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<!-- CSRF Token für AJAX-Anfragen -->
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Header -->
|
||||
@@ -431,15 +437,21 @@
|
||||
};
|
||||
|
||||
// Einstellungen an den Server senden
|
||||
const response = await apiCall('/user/update-settings', {
|
||||
const response = await fetch('/user/update-settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showFlashMessage('Alle Einstellungen wurden erfolgreich gespeichert', 'success');
|
||||
} else {
|
||||
throw new Error(response.message || 'Unbekannter Fehler');
|
||||
throw new Error(result.error || 'Unbekannter Fehler');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern der Einstellungen:', error);
|
||||
@@ -447,6 +459,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Einstellungen beim Laden der Seite abrufen
|
||||
async function loadUserSettings() {
|
||||
try {
|
||||
const response = await fetch('/api/user/settings');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const settings = result.settings;
|
||||
|
||||
// Theme-Einstellungen anwenden
|
||||
if (settings.theme === 'dark') {
|
||||
localStorage.setItem(STORAGE_KEY, 'true');
|
||||
document.documentElement.classList.add('dark');
|
||||
setActiveThemeButton(darkThemeBtn);
|
||||
} else if (settings.theme === 'light') {
|
||||
localStorage.setItem(STORAGE_KEY, 'false');
|
||||
document.documentElement.classList.remove('dark');
|
||||
setActiveThemeButton(lightThemeBtn);
|
||||
} else {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
setActiveThemeButton(systemThemeBtn);
|
||||
}
|
||||
|
||||
// Weitere Einstellungen anwenden
|
||||
document.getElementById('reduced-motion').checked = settings.reduced_motion;
|
||||
document.getElementById('notify-new-jobs').checked = settings.notifications.new_jobs;
|
||||
document.getElementById('notify-job-updates').checked = settings.notifications.job_updates;
|
||||
document.getElementById('notify-system').checked = settings.notifications.system;
|
||||
document.getElementById('notify-email').checked = settings.notifications.email;
|
||||
document.getElementById('activity-logs').checked = settings.privacy.activity_logs;
|
||||
document.getElementById('two-factor').checked = settings.privacy.two_factor;
|
||||
document.getElementById('auto-logout').value = settings.privacy.auto_logout;
|
||||
|
||||
// Kontrast-Einstellungen
|
||||
if (settings.contrast === 'high') {
|
||||
localStorage.setItem('myp-contrast', 'high');
|
||||
document.documentElement.classList.add('high-contrast');
|
||||
setActiveContrastButton(highContrastBtn);
|
||||
} else {
|
||||
localStorage.setItem('myp-contrast', 'normal');
|
||||
document.documentElement.classList.remove('high-contrast');
|
||||
setActiveContrastButton(normalContrastBtn);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Einstellungen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Einstellungen beim Laden der Seite abrufen
|
||||
loadUserSettings();
|
||||
|
||||
// Helper function to show flash messages
|
||||
function showFlashMessage(message, type = 'info') {
|
||||
// Use the global toast manager if available
|
||||
|
Reference in New Issue
Block a user