290 lines
13 KiB
HTML
290 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Statistiken - MYP Platform{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="space-y-8">
|
|
<!-- Header -->
|
|
<div class="relative overflow-hidden rounded-2xl p-6 stats-card">
|
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Statistiken</h1>
|
|
<p class="mt-2 text-slate-500 dark:text-slate-400">Übersicht über Systemleistung und Nutzungsstatistiken</p>
|
|
</div>
|
|
<div class="flex gap-4 mt-4 lg:mt-0">
|
|
<button onclick="refreshStats()" class="bg-black hover:bg-gray-800 dark:bg-blue-600 dark:hover:bg-blue-500 text-white px-6 py-2.5 rounded-xl transition-colors duration-300">
|
|
<svg class="h-5 w-5 mr-2 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<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>
|
|
Aktualisieren
|
|
</button>
|
|
<button onclick="exportStats()" class="bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600 text-slate-700 dark:text-white px-6 py-2.5 rounded-xl transition-colors duration-300 border border-slate-200 dark:border-slate-600">
|
|
<svg class="h-5 w-5 mr-2 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
|
|
Exportieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overview Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
|
<!-- Total Jobs -->
|
|
<div class="stats-card p-6" id="stats-total-jobs">
|
|
<div class="absolute top-5 right-5 text-slate-900 dark:text-blue-400 text-3xl">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
</svg>
|
|
</div>
|
|
<p class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400 mt-2">Gesamte Jobs</p>
|
|
<p id="total-jobs-count" class="text-3xl md:text-4xl font-semibold text-slate-900 dark:text-white">-</p>
|
|
</div>
|
|
|
|
<!-- Completed Jobs -->
|
|
<div class="stats-card p-6" id="stats-completed-jobs">
|
|
<div class="absolute top-5 right-5 text-green-600 dark:text-green-400 text-3xl">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<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>
|
|
</div>
|
|
<p class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400 mt-2">Abgeschlossene Jobs</p>
|
|
<p id="completed-jobs-count" class="text-3xl md:text-4xl font-semibold text-slate-900 dark:text-white">-</p>
|
|
</div>
|
|
|
|
<!-- Active Printers -->
|
|
<div class="stats-card p-6" id="stats-active-printers">
|
|
<div class="absolute top-5 right-5 text-purple-600 dark:text-purple-400 text-3xl">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</div>
|
|
<p class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400 mt-2">Online Drucker</p>
|
|
<p id="online-printers-count" class="text-3xl md:text-4xl font-semibold text-slate-900 dark:text-white">-</p>
|
|
</div>
|
|
|
|
<!-- Success Rate -->
|
|
<div class="stats-card p-6" id="stats-success-rate">
|
|
<div class="absolute top-5 right-5 text-amber-600 dark:text-amber-400 text-3xl">
|
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<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>
|
|
</div>
|
|
<p class="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400 mt-2">Erfolgsrate</p>
|
|
<p id="success-rate-percent" class="text-3xl md:text-4xl font-semibold text-slate-900 dark:text-white">-%</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<!-- Job Status Distribution -->
|
|
<div class="stats-card p-6">
|
|
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Job-Status Verteilung</h2>
|
|
<div class="h-64">
|
|
<canvas id="job-status-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Printer Usage -->
|
|
<div class="stats-card p-6">
|
|
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Drucker-Nutzung</h2>
|
|
<div class="h-64">
|
|
<canvas id="printer-usage-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Timeline and User Activity Charts -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<!-- Jobs Timeline -->
|
|
<div class="stats-card p-6">
|
|
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Jobs der letzten 30 Tage</h2>
|
|
<div class="h-64">
|
|
<canvas id="jobs-timeline-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Activity -->
|
|
<div class="stats-card p-6">
|
|
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Top Benutzer-Aktivität</h2>
|
|
<div class="h-64">
|
|
<canvas id="user-activity-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Performance Metrics -->
|
|
<div class="stats-card p-6">
|
|
<h2 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Systemleistung</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<!-- Active Jobs -->
|
|
<div class="text-center p-4 bg-slate-50 dark:bg-slate-700/30 rounded-xl">
|
|
<svg class="h-8 w-8 mx-auto mb-3 text-slate-900 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<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>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">Aktive Jobs</p>
|
|
<p id="active-jobs-count" class="text-xl font-bold text-slate-900 dark:text-white">-</p>
|
|
</div>
|
|
|
|
<!-- Failed Jobs -->
|
|
<div class="text-center p-4 bg-slate-50 dark:bg-slate-700/30 rounded-xl">
|
|
<svg class="h-8 w-8 mx-auto mb-3 text-red-600 dark:text-red-400" 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-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
</svg>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">Fehlgeschlagene Jobs</p>
|
|
<p id="failed-jobs-count" class="text-xl font-bold text-slate-900 dark:text-white">-</p>
|
|
</div>
|
|
|
|
<!-- Total Users -->
|
|
<div class="text-center p-4 bg-slate-50 dark:bg-slate-700/30 rounded-xl">
|
|
<svg class="h-8 w-8 mx-auto mb-3 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
|
|
</svg>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mb-1">Registrierte Benutzer</p>
|
|
<p id="total-users-count" class="text-xl font-bold text-slate-900 dark:text-white">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<!-- Chart.js CDN -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.min.js"></script>
|
|
|
|
<!-- Global Refresh Functions -->
|
|
<script src="{{ url_for('static', filename='js/global-refresh-functions.js') }}"></script>
|
|
|
|
<!-- Charts JavaScript -->
|
|
<script src="{{ url_for('static', filename='js/charts.js') }}"></script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Basis-Statistiken laden
|
|
loadBasicStats();
|
|
|
|
// Theme wechsel Event-Listener
|
|
window.addEventListener('darkModeChanged', function(e) {
|
|
if (window.updateChartsTheme) {
|
|
window.updateChartsTheme();
|
|
}
|
|
});
|
|
|
|
// Auto-refresh für Basis-Statistiken (alle 30 Sekunden)
|
|
setInterval(loadBasicStats, 30000);
|
|
});
|
|
|
|
async function loadBasicStats() {
|
|
try {
|
|
const response = await fetch('/api/stats');
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Fehler beim Laden der Statistiken');
|
|
}
|
|
|
|
// Statistiken aktualisieren mit defensiven Checks
|
|
updateStatsCounter('total-jobs-count', data.total_jobs);
|
|
updateStatsCounter('completed-jobs-count', data.completed_jobs);
|
|
updateStatsCounter('online-printers-count', data.online_printers);
|
|
updateStatsCounter('success-rate-percent', data.success_rate + '%');
|
|
updateStatsCounter('active-jobs-count', data.active_jobs);
|
|
updateStatsCounter('failed-jobs-count', data.failed_jobs);
|
|
updateStatsCounter('total-users-count', data.total_users);
|
|
|
|
console.log('✅ Basis-Statistiken erfolgreich geladen:', data);
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Basis-Statistiken:', error);
|
|
showToast('Fehler beim Laden der Statistiken', 'error');
|
|
}
|
|
}
|
|
|
|
function updateStatsCounter(elementId, value, animate = true) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) {
|
|
console.warn(`Element mit ID '${elementId}' nicht gefunden - wird übersprungen`);
|
|
return;
|
|
}
|
|
|
|
if (animate) {
|
|
// Animierte Zählung
|
|
const currentValue = parseInt(element.textContent.replace(/[^\d]/g, '')) || 0;
|
|
const targetValue = parseInt(value.toString().replace(/[^\d]/g, '')) || 0;
|
|
|
|
if (currentValue !== targetValue) {
|
|
animateCounter(element, currentValue, targetValue, value.toString());
|
|
}
|
|
} else {
|
|
element.textContent = value;
|
|
}
|
|
}
|
|
|
|
function animateCounter(element, start, end, finalText) {
|
|
const duration = 1000; // 1 Sekunde
|
|
const startTime = performance.now();
|
|
|
|
function updateCounter(currentTime) {
|
|
const elapsed = currentTime - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
// Easing-Funktion (ease-out)
|
|
const easeOut = 1 - Math.pow(1 - progress, 3);
|
|
const currentValue = Math.round(start + (end - start) * easeOut);
|
|
|
|
if (finalText.includes('%')) {
|
|
element.textContent = currentValue + '%';
|
|
} else {
|
|
element.textContent = currentValue;
|
|
}
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(updateCounter);
|
|
} else {
|
|
element.textContent = finalText;
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(updateCounter);
|
|
}
|
|
|
|
// Statistiken neu laden
|
|
function refreshStats() {
|
|
// Feedback für den Benutzer
|
|
showToast('Statistiken werden aktualisiert...', 'info');
|
|
|
|
// Basis-Statistiken laden
|
|
loadBasicStats();
|
|
|
|
// Charts aktualisieren
|
|
if (window.refreshAllCharts) {
|
|
window.refreshAllCharts();
|
|
}
|
|
|
|
// Erfolgsmeldung
|
|
setTimeout(() => {
|
|
showToast('Statistiken erfolgreich aktualisiert', 'success');
|
|
}, 1000);
|
|
}
|
|
|
|
// Statistiken exportieren
|
|
function exportStats() {
|
|
// Direkter Download vom API-Endpunkt
|
|
window.location.href = '/api/stats/export';
|
|
}
|
|
|
|
// Helper-Funktion für Toast-Benachrichtigungen
|
|
function showToast(message, type) {
|
|
if (window.showToast) {
|
|
window.showToast(message, type);
|
|
} else {
|
|
// Fallback für einfache Alert
|
|
if (type === 'error') {
|
|
alert('Fehler: ' + message);
|
|
} else {
|
|
console.log(type + ': ' + message);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %} |