395 lines
16 KiB
HTML
395 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Statistiken - MYP API Tester{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-md-12 mb-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="mb-0">Systemstatistiken</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<form class="api-form mb-3" data-url="/api/stats" data-method="GET" data-response="statsResponse">
|
|
<button type="submit" class="btn btn-primary">Statistiken aktualisieren</button>
|
|
</form>
|
|
|
|
<div class="row" id="statsContainer">
|
|
<!-- Wird dynamisch gefüllt -->
|
|
</div>
|
|
|
|
<!-- Problem-Drucker-Bereich -->
|
|
<div class="row mt-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-warning text-dark">
|
|
<h5 class="mb-0">Drucker mit Verbindungsproblemen</h5>
|
|
</div>
|
|
<div class="card-body" id="problemPrintersContainer">
|
|
<div class="alert alert-info">Keine Verbindungsprobleme festgestellt.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Uptime-Grafik -->
|
|
<div class="row mt-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-dark text-white">
|
|
<h5 class="mb-0">Steckdosen-Verfügbarkeit</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form class="api-form mb-3" data-url="/api/uptime" data-method="GET" data-response="uptimeResponse">
|
|
<button type="submit" class="btn btn-primary">Uptime-Daten laden</button>
|
|
</form>
|
|
<canvas id="uptimeChart" width="100%" height="300"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- API-Antworten -->
|
|
<div class="row mt-4">
|
|
<div class="col-md-6">
|
|
<h6>Stats API-Antwort:</h6>
|
|
<pre class="api-response" id="statsResponse"></pre>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Uptime API-Antwort:</h6>
|
|
<pre class="api-response" id="uptimeResponse"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<!-- Chart.js für Diagramme -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
<script>
|
|
let uptimeChart;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Statistiken laden
|
|
document.querySelector('form[data-url="/api/stats"]').dispatchEvent(new Event('submit'));
|
|
document.querySelector('form[data-url="/api/uptime"]').dispatchEvent(new Event('submit'));
|
|
|
|
// Statistiken aktualisieren, wenn API-Antwort geladen wird
|
|
const statsResponse = document.getElementById('statsResponse');
|
|
const statsObserver = new MutationObserver(function(mutations) {
|
|
try {
|
|
const stats = JSON.parse(statsResponse.textContent);
|
|
updateStatsDisplay(stats);
|
|
updateProblemPrinters(stats);
|
|
} catch (e) {
|
|
console.error('Fehler beim Parsen der Statistik-Daten:', e);
|
|
}
|
|
});
|
|
|
|
statsObserver.observe(statsResponse, { childList: true, characterData: true, subtree: true });
|
|
|
|
// Uptime-Daten aktualisieren, wenn API-Antwort geladen wird
|
|
const uptimeResponse = document.getElementById('uptimeResponse');
|
|
const uptimeObserver = new MutationObserver(function(mutations) {
|
|
try {
|
|
const uptime = JSON.parse(uptimeResponse.textContent);
|
|
updateUptimeChart(uptime);
|
|
} catch (e) {
|
|
console.error('Fehler beim Parsen der Uptime-Daten:', e);
|
|
}
|
|
});
|
|
|
|
uptimeObserver.observe(uptimeResponse, { childList: true, characterData: true, subtree: true });
|
|
|
|
// Periodische Aktualisierung
|
|
setInterval(function() {
|
|
document.querySelector('form[data-url="/api/stats"]').dispatchEvent(new Event('submit'));
|
|
document.querySelector('form[data-url="/api/uptime"]').dispatchEvent(new Event('submit'));
|
|
}, 60000); // Alle 60 Sekunden aktualisieren
|
|
});
|
|
|
|
function updateStatsDisplay(stats) {
|
|
const container = document.getElementById('statsContainer');
|
|
container.innerHTML = '';
|
|
|
|
// Drucker-Statistiken
|
|
const printerStats = document.createElement('div');
|
|
printerStats.className = 'col-md-4 mb-3';
|
|
printerStats.innerHTML = `
|
|
<div class="card h-100">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0">Drucker</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Gesamt:</span>
|
|
<span>${stats.printers.total}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Verfügbar:</span>
|
|
<span>${stats.printers.available}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Auslastung:</span>
|
|
<span>${Math.round(stats.printers.utilization_rate * 100)}%</span>
|
|
</div>
|
|
<div class="progress mt-3 mb-3">
|
|
<div class="progress-bar" role="progressbar"
|
|
style="width: ${Math.round(stats.printers.utilization_rate * 100)}%">
|
|
${Math.round(stats.printers.utilization_rate * 100)}%
|
|
</div>
|
|
</div>
|
|
<hr />
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Online:</span>
|
|
<span>${stats.printers.online}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Offline:</span>
|
|
<span>${stats.printers.offline}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Verbindungsrate:</span>
|
|
<span>${Math.round(stats.printers.connectivity_rate * 100)}%</span>
|
|
</div>
|
|
<div class="progress mt-3">
|
|
<div class="progress-bar bg-success" role="progressbar"
|
|
style="width: ${Math.round(stats.printers.connectivity_rate * 100)}%">
|
|
${Math.round(stats.printers.connectivity_rate * 100)}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Job-Statistiken
|
|
const jobStats = document.createElement('div');
|
|
jobStats.className = 'col-md-4 mb-3';
|
|
jobStats.innerHTML = `
|
|
<div class="card h-100">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0">Druckaufträge</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Gesamt:</span>
|
|
<span>${stats.jobs.total}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Aktiv:</span>
|
|
<span>${stats.jobs.active}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Abgeschlossen:</span>
|
|
<span>${stats.jobs.completed}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Durchschnittliche Dauer:</span>
|
|
<span>${stats.jobs.avg_duration} Minuten</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Benutzer- und Uptime-Statistiken
|
|
const userStats = document.createElement('div');
|
|
userStats.className = 'col-md-4 mb-3';
|
|
userStats.innerHTML = `
|
|
<div class="card h-100">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0">System</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Benutzer:</span>
|
|
<span>${stats.users.total}</span>
|
|
</div>
|
|
<hr />
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Verbindungsausfälle (7 Tage):</span>
|
|
<span>${stats.uptime.outages_last_7_days}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span>Aktuelle Probleme:</span>
|
|
<span>${stats.uptime.problem_printers.length}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(printerStats);
|
|
container.appendChild(jobStats);
|
|
container.appendChild(userStats);
|
|
}
|
|
|
|
function updateProblemPrinters(stats) {
|
|
const container = document.getElementById('problemPrintersContainer');
|
|
const problemPrinters = stats.uptime.problem_printers;
|
|
|
|
if (problemPrinters.length === 0) {
|
|
container.innerHTML = '<div class="alert alert-info">Keine Verbindungsprobleme festgestellt.</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="table-responsive"><table class="table table-striped">';
|
|
html += '<thead><tr><th>Drucker</th><th>Status</th><th>Offline seit</th><th>Dauer</th></tr></thead>';
|
|
html += '<tbody>';
|
|
|
|
problemPrinters.forEach(printer => {
|
|
let offlineSince = 'Unbekannt';
|
|
let duration = 'Unbekannt';
|
|
|
|
if (printer.last_seen) {
|
|
try {
|
|
const lastSeen = new Date(printer.last_seen);
|
|
const now = new Date();
|
|
const diffSeconds = Math.floor((now - lastSeen) / 1000);
|
|
const hours = Math.floor(diffSeconds / 3600);
|
|
const minutes = Math.floor((diffSeconds % 3600) / 60);
|
|
|
|
offlineSince = lastSeen.toLocaleString();
|
|
duration = `${hours}h ${minutes}m`;
|
|
} catch (e) {
|
|
console.error('Fehler beim Berechnen der Offline-Zeit:', e);
|
|
}
|
|
}
|
|
|
|
html += `<tr>
|
|
<td>${printer.name}</td>
|
|
<td><span class="badge bg-danger">Offline</span></td>
|
|
<td>${offlineSince}</td>
|
|
<td>${duration}</td>
|
|
</tr>`;
|
|
});
|
|
|
|
html += '</tbody></table></div>';
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function updateUptimeChart(uptimeData) {
|
|
// Wenn keine Daten vorhanden sind, nichts tun
|
|
if (!uptimeData || !uptimeData.sockets || uptimeData.sockets.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Daten für das Diagramm vorbereiten
|
|
const socketNames = [];
|
|
const datasets = [];
|
|
const colors = {
|
|
online: 'rgba(40, 167, 69, 0.7)',
|
|
offline: 'rgba(220, 53, 69, 0.7)',
|
|
unknown: 'rgba(108, 117, 125, 0.7)'
|
|
};
|
|
|
|
// Zeitraum für das Diagramm (letzten 7 Tage)
|
|
const endDate = new Date();
|
|
const startDate = new Date();
|
|
startDate.setDate(startDate.getDate() - 7);
|
|
|
|
// Für jede Steckdose
|
|
uptimeData.sockets.forEach(socket => {
|
|
socketNames.push(socket.name);
|
|
|
|
// Sortiere Ereignisse nach Zeitstempel
|
|
if (socket.events) {
|
|
socket.events.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
|
|
// Erstelle einen Datensatz für diese Steckdose
|
|
const data = [];
|
|
|
|
// Füge Ereignisse zum Datensatz hinzu
|
|
socket.events.forEach(event => {
|
|
data.push({
|
|
x: new Date(event.timestamp),
|
|
y: event.status === 'online' ? 1 : 0,
|
|
status: event.status,
|
|
duration: event.duration_seconds ?
|
|
formatDuration(event.duration_seconds) : null
|
|
});
|
|
});
|
|
|
|
// Füge aktuellen Status hinzu
|
|
if (socket.current_status) {
|
|
data.push({
|
|
x: new Date(),
|
|
y: socket.current_status.connection_status === 'online' ? 1 : 0,
|
|
status: socket.current_status.connection_status,
|
|
duration: null
|
|
});
|
|
}
|
|
|
|
datasets.push({
|
|
label: socket.name,
|
|
data: data,
|
|
stepped: true,
|
|
borderColor: colors[socket.current_status?.connection_status || 'unknown'],
|
|
backgroundColor: colors[socket.current_status?.connection_status || 'unknown'],
|
|
fill: false
|
|
});
|
|
}
|
|
});
|
|
|
|
// Chart.js Konfiguration
|
|
const ctx = document.getElementById('uptimeChart').getContext('2d');
|
|
|
|
// Wenn Chart bereits existiert, zerstöre ihn
|
|
if (uptimeChart) {
|
|
uptimeChart.destroy();
|
|
}
|
|
|
|
// Erstelle neuen Chart
|
|
uptimeChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
datasets: datasets
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
const point = context.raw;
|
|
let label = context.dataset.label || '';
|
|
label += ': ' + (point.status === 'online' ? 'Online' : 'Offline');
|
|
if (point.duration) {
|
|
label += ' (Dauer: ' + point.duration + ')';
|
|
}
|
|
return label;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
type: 'time',
|
|
time: {
|
|
unit: 'day'
|
|
},
|
|
min: startDate,
|
|
max: endDate
|
|
},
|
|
y: {
|
|
min: -0.1,
|
|
max: 1.1,
|
|
ticks: {
|
|
callback: function(value) {
|
|
return value === 0 ? 'Offline' : value === 1 ? 'Online' : '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function formatDuration(seconds) {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
</script>
|
|
{% endblock %} |