410 lines
18 KiB
HTML
410 lines
18 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Job-Status - {{ job.name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
|
|
|
<!-- Header Section -->
|
|
<div class="relative overflow-hidden">
|
|
<div class="absolute inset-0 bg-gradient-to-r from-blue-600/10 to-purple-600/10"></div>
|
|
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 text-center">
|
|
<h1 class="text-4xl lg:text-6xl font-bold text-slate-900 dark:text-white mb-6">
|
|
<span class="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
|
Job-Status
|
|
</span>
|
|
</h1>
|
|
<p class="text-xl text-slate-600 dark:text-slate-400 max-w-3xl mx-auto leading-relaxed">
|
|
Überwachen Sie den aktuellen Status Ihres Druckauftrags in Echtzeit.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10">
|
|
|
|
<!-- Status Container -->
|
|
<div class="form-container professional-shadow p-8 lg:p-12">
|
|
|
|
<!-- Job-Header -->
|
|
<div class="text-center mb-8">
|
|
<div class="w-20 h-20 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<svg class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
</svg>
|
|
</div>
|
|
<h2 class="text-3xl font-bold text-slate-900 dark:text-white mb-4">
|
|
{{ job.name }}
|
|
</h2>
|
|
<div id="statusBadge" class="inline-flex items-center px-4 py-2 rounded-full text-sm font-medium">
|
|
<!-- Wird von JavaScript aktualisiert -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Live-Status-Updates -->
|
|
<div id="liveStatusContainer" class="mb-8">
|
|
<!-- Wird von JavaScript gefüllt -->
|
|
</div>
|
|
|
|
<!-- Fortschrittsbalken -->
|
|
<div class="mb-8">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Fortschritt</span>
|
|
<span id="progressText" class="text-sm text-slate-500 dark:text-slate-400">0%</span>
|
|
</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-3">
|
|
<div id="progressBar" class="bg-gradient-to-r from-blue-500 to-indigo-500 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Job-Details -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
|
<div class="space-y-4">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Job-Details</h3>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Job-ID:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">#{{ job.id }}</span>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Geplante Dauer:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ job.duration_minutes }} Minuten</span>
|
|
</div>
|
|
|
|
{% if job.start_at %}
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Gestartet:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ job.start_at|format_datetime('%H:%M') }}</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if job.end_at %}
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Geplantes Ende:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ job.end_at|format_datetime('%H:%M') }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Drucker-Details</h3>
|
|
|
|
{% if job.printer %}
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Drucker:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ job.printer.name }}</span>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Standort:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ job.printer.location or 'Unbekannt' }}</span>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-sm text-slate-500 dark:text-slate-400">Kein Drucker zugewiesen</p>
|
|
{% endif %}
|
|
|
|
{% if guest_request %}
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-500 dark:text-slate-400">Antragsteller:</span>
|
|
<span class="text-sm font-medium text-slate-900 dark:text-white">{{ guest_request.name }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Zeitstatus -->
|
|
<div class="bg-slate-50 dark:bg-slate-800 rounded-xl p-6 mb-8">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-center">
|
|
<div>
|
|
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400" id="elapsedTime">--</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Verstrichene Zeit</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-2xl font-bold text-green-600 dark:text-green-400" id="remainingTime">--</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Verbleibende Zeit</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-2xl font-bold text-purple-600 dark:text-purple-400" id="currentTime">--</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Aktuelle Zeit</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Aktionen -->
|
|
<div class="text-center">
|
|
<div class="space-y-4 sm:space-y-0 sm:space-x-4 sm:flex sm:justify-center">
|
|
<button onclick="refreshStatus()"
|
|
class="w-full sm:w-auto px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-colors duration-200">
|
|
<svg class="w-5 h-5 inline mr-2" 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>
|
|
Status aktualisieren
|
|
</button>
|
|
|
|
<a href="{{ url_for('guest.guest_request_form') }}"
|
|
class="w-full sm:w-auto inline-flex items-center justify-center px-6 py-3 bg-gray-600 text-white rounded-xl hover:bg-gray-700 transition-colors duration-200">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
</svg>
|
|
Neue Anfrage
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.form-container {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.dark .form-container {
|
|
background: rgba(30, 41, 59, 0.95);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.professional-shadow {
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.status-scheduled {
|
|
background-color: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.status-running {
|
|
background-color: #d1fae5;
|
|
color: #065f46;
|
|
}
|
|
|
|
.status-completed, .status-finished {
|
|
background-color: #dbeafe;
|
|
color: #1e40af;
|
|
}
|
|
|
|
.status-failed, .status-cancelled {
|
|
background-color: #fee2e2;
|
|
color: #dc2626;
|
|
}
|
|
|
|
.dark .status-scheduled {
|
|
background-color: rgba(251, 191, 36, 0.2);
|
|
color: #fbbf24;
|
|
}
|
|
|
|
.dark .status-running {
|
|
background-color: rgba(34, 197, 94, 0.2);
|
|
color: #22c55e;
|
|
}
|
|
|
|
.dark .status-completed, .dark .status-finished {
|
|
background-color: rgba(59, 130, 246, 0.2);
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.dark .status-failed, .dark .status-cancelled {
|
|
background-color: rgba(239, 68, 68, 0.2);
|
|
color: #ef4444;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
var jobId = parseInt('{{ job.id }}');
|
|
var refreshInterval;
|
|
var jobData = {};
|
|
|
|
// Initial load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
refreshStatus();
|
|
startAutoRefresh();
|
|
updateCurrentTime();
|
|
setInterval(updateCurrentTime, 1000);
|
|
});
|
|
|
|
async function refreshStatus() {
|
|
try {
|
|
const response = await fetch(`/api/guest/job/${jobId}/status`);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
jobData = result.job;
|
|
updateDisplay(jobData);
|
|
} else {
|
|
console.error('Fehler beim Laden des Job-Status:', result.error);
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Status-Update:', error);
|
|
}
|
|
}
|
|
|
|
function updateDisplay(job) {
|
|
// Status-Badge aktualisieren
|
|
const statusBadge = document.getElementById('statusBadge');
|
|
statusBadge.className = `inline-flex items-center px-4 py-2 rounded-full text-sm font-medium status-${job.status}`;
|
|
statusBadge.innerHTML = getStatusText(job.status);
|
|
|
|
// Fortschrittsbalken
|
|
const progressBar = document.getElementById('progressBar');
|
|
const progressText = document.getElementById('progressText');
|
|
progressBar.style.width = job.progress_percent + '%';
|
|
progressText.textContent = job.progress_percent + '%';
|
|
|
|
// Fortschrittsbalken-Farbe je nach Status
|
|
if (job.status === 'completed' || job.status === 'finished') {
|
|
progressBar.className = 'bg-gradient-to-r from-green-500 to-emerald-500 h-3 rounded-full transition-all duration-300';
|
|
} else if (job.status === 'running') {
|
|
progressBar.className = 'bg-gradient-to-r from-blue-500 to-indigo-500 h-3 rounded-full transition-all duration-300';
|
|
} else if (job.status === 'failed' || job.status === 'cancelled') {
|
|
progressBar.className = 'bg-gradient-to-r from-red-500 to-rose-500 h-3 rounded-full transition-all duration-300';
|
|
}
|
|
|
|
// Zeit-Updates
|
|
updateTimeDisplays(job);
|
|
|
|
// Live-Status-Container
|
|
updateLiveStatus(job);
|
|
}
|
|
|
|
function updateTimeDisplays(job) {
|
|
const now = new Date();
|
|
|
|
if (job.start_at && job.status === 'running') {
|
|
const startTime = new Date(job.start_at);
|
|
const elapsed = Math.floor((now - startTime) / 1000 / 60);
|
|
document.getElementById('elapsedTime').textContent = elapsed + ' Min';
|
|
|
|
if (job.remaining_minutes !== undefined) {
|
|
document.getElementById('remainingTime').textContent = job.remaining_minutes + ' Min';
|
|
}
|
|
} else {
|
|
document.getElementById('elapsedTime').textContent = '--';
|
|
document.getElementById('remainingTime').textContent = '--';
|
|
}
|
|
}
|
|
|
|
function updateCurrentTime() {
|
|
const now = new Date();
|
|
document.getElementById('currentTime').textContent = now.toLocaleTimeString('de-DE', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
});
|
|
}
|
|
|
|
function updateLiveStatus(job) {
|
|
const container = document.getElementById('liveStatusContainer');
|
|
let statusHtml = '';
|
|
|
|
if (job.status === 'scheduled') {
|
|
statusHtml = `
|
|
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-xl p-4">
|
|
<div class="flex items-center">
|
|
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center mr-4">
|
|
<svg class="w-5 h-5 text-white" 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>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-yellow-800 dark:text-yellow-200">Job ist geplant</h3>
|
|
<p class="text-yellow-700 dark:text-yellow-300">Der Job wartet auf den Start.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else if (job.status === 'running') {
|
|
statusHtml = `
|
|
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-700 rounded-xl p-4">
|
|
<div class="flex items-center">
|
|
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center mr-4">
|
|
<svg class="w-5 h-5 text-white animate-spin" 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>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-green-800 dark:text-green-200">Job läuft</h3>
|
|
<p class="text-green-700 dark:text-green-300">Ihr Druckauftrag wird gerade ausgeführt.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else if (job.status === 'completed' || job.status === 'finished') {
|
|
statusHtml = `
|
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-700 rounded-xl p-4">
|
|
<div class="flex items-center">
|
|
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center mr-4">
|
|
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-blue-800 dark:text-blue-200">Job abgeschlossen</h3>
|
|
<p class="text-blue-700 dark:text-blue-300">Ihr Druckauftrag wurde erfolgreich abgeschlossen.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else if (job.status === 'failed' || job.status === 'cancelled') {
|
|
const statusText = job.status === 'failed' ? 'fehlgeschlagen' : 'abgebrochen';
|
|
statusHtml = `
|
|
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700 rounded-xl p-4">
|
|
<div class="flex items-center">
|
|
<div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center mr-4">
|
|
<svg class="w-5 h-5 text-white" 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>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-red-800 dark:text-red-200">Job ${statusText}</h3>
|
|
<p class="text-red-700 dark:text-red-300">Der Druckauftrag konnte nicht abgeschlossen werden.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
container.innerHTML = statusHtml;
|
|
}
|
|
|
|
function getStatusText(status) {
|
|
const statusTexts = {
|
|
'scheduled': 'Geplant',
|
|
'running': 'Läuft',
|
|
'completed': 'Abgeschlossen',
|
|
'finished': 'Beendet',
|
|
'failed': 'Fehlgeschlagen',
|
|
'cancelled': 'Abgebrochen'
|
|
};
|
|
return statusTexts[status] || status;
|
|
}
|
|
|
|
function startAutoRefresh() {
|
|
// Nur bei aktiven Jobs automatisch aktualisieren
|
|
if (jobData.is_active) {
|
|
refreshInterval = setInterval(refreshStatus, 5000); // Alle 5 Sekunden
|
|
} else if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
}
|
|
|
|
// Auto-Refresh stoppen wenn Job abgeschlossen ist
|
|
function checkAutoRefresh(job) {
|
|
if (!job.is_active && refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
refreshInterval = null;
|
|
} else if (job.is_active && !refreshInterval) {
|
|
refreshInterval = setInterval(refreshStatus, 5000);
|
|
}
|
|
}
|
|
|
|
// Cleanup beim Verlassen der Seite
|
|
window.addEventListener('beforeunload', function() {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |