Implementiere Uptime-Überwachung für Steckdosen mit Dashboard-Anzeige
This commit is contained in:
parent
63b04c4dea
commit
52a336a788
255
backend/app.py
255
backend/app.py
@ -90,7 +90,9 @@ def init_db():
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
description TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
status INTEGER DEFAULT 0,
|
status INTEGER DEFAULT 0,
|
||||||
ip_address TEXT
|
ip_address TEXT,
|
||||||
|
last_seen TIMESTAMP,
|
||||||
|
connection_status TEXT DEFAULT 'unknown'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS job (
|
CREATE TABLE IF NOT EXISTS job (
|
||||||
@ -106,6 +108,15 @@ def init_db():
|
|||||||
FOREIGN KEY (socket_id) REFERENCES socket (id) ON DELETE CASCADE,
|
FOREIGN KEY (socket_id) REFERENCES socket (id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS socket_uptime (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
socket_id TEXT NOT NULL,
|
||||||
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
duration_seconds INTEGER,
|
||||||
|
FOREIGN KEY (socket_id) REFERENCES socket (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
''')
|
''')
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
@ -356,11 +367,44 @@ def socket_to_dict(socket):
|
|||||||
latest_job = get_latest_job_for_socket(socket['id'])
|
latest_job = get_latest_job_for_socket(socket['id'])
|
||||||
waiting_jobs = get_waiting_jobs_for_socket(socket['id'])
|
waiting_jobs = get_waiting_jobs_for_socket(socket['id'])
|
||||||
|
|
||||||
|
# Verbindungsstatus-Informationen
|
||||||
|
connection_status = socket.get('connection_status', 'unknown')
|
||||||
|
last_seen = socket.get('last_seen')
|
||||||
|
uptime_info = None
|
||||||
|
|
||||||
|
if last_seen and connection_status == 'offline':
|
||||||
|
# Berechne wie lange die Steckdose offline ist
|
||||||
|
try:
|
||||||
|
last_seen_dt = datetime.datetime.fromisoformat(last_seen)
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
offline_duration = int((now - last_seen_dt).total_seconds())
|
||||||
|
|
||||||
|
# Formatiere die Offline-Zeit benutzerfreundlich
|
||||||
|
hours, remainder = divmod(offline_duration, 3600)
|
||||||
|
minutes, seconds = divmod(remainder, 60)
|
||||||
|
|
||||||
|
uptime_info = {
|
||||||
|
'offline_since': last_seen,
|
||||||
|
'offline_duration': offline_duration,
|
||||||
|
'offline_duration_formatted': f"{hours}h {minutes}m {seconds}s"
|
||||||
|
}
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
# Wenn das Datum nicht geparst werden kann
|
||||||
|
uptime_info = {
|
||||||
|
'offline_since': last_seen,
|
||||||
|
'offline_duration': None,
|
||||||
|
'offline_duration_formatted': "Unbekannt"
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': socket['id'],
|
'id': socket['id'],
|
||||||
'name': socket['name'],
|
'name': socket['name'],
|
||||||
'description': socket['description'],
|
'description': socket['description'],
|
||||||
'status': socket['status'],
|
'status': socket['status'],
|
||||||
|
'ipAddress': socket.get('ip_address'),
|
||||||
|
'connectionStatus': connection_status,
|
||||||
|
'lastSeen': last_seen,
|
||||||
|
'uptimeInfo': uptime_info,
|
||||||
'latestJob': job_to_dict(latest_job) if latest_job else None,
|
'latestJob': job_to_dict(latest_job) if latest_job else None,
|
||||||
'waitingJobs': [job_to_dict(job) for job in waiting_jobs] if waiting_jobs else []
|
'waitingJobs': [job_to_dict(job) for job in waiting_jobs] if waiting_jobs else []
|
||||||
}
|
}
|
||||||
@ -510,6 +554,87 @@ def job_to_dict(job):
|
|||||||
'remainingMinutes': calculate_remaining_time(job)
|
'remainingMinutes': calculate_remaining_time(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Socket Uptime-Überwachung
|
||||||
|
def log_socket_connection_event(socket_id, status, duration_seconds=None):
|
||||||
|
"""Speichert ein Ereignis zum Verbindungsstatus einer Steckdose"""
|
||||||
|
event_id = str(uuid.uuid4())
|
||||||
|
timestamp = datetime.datetime.utcnow().isoformat()
|
||||||
|
|
||||||
|
db = get_db()
|
||||||
|
db.execute(
|
||||||
|
'INSERT INTO socket_uptime (id, socket_id, timestamp, status, duration_seconds) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
(event_id, socket_id, timestamp, status, duration_seconds)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
app.logger.info(f"Verbindungsstatus für Steckdose {socket_id} geändert: {status}")
|
||||||
|
|
||||||
|
# Aktualisiere auch den Verbindungsstatus in der socket-Tabelle
|
||||||
|
db.execute(
|
||||||
|
'UPDATE socket SET connection_status = ?, last_seen = ? WHERE id = ?',
|
||||||
|
(status, timestamp if status == 'online' else None, socket_id)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return event_id
|
||||||
|
|
||||||
|
def get_socket_uptime_events(socket_id=None, limit=100):
|
||||||
|
"""Ruft Verbindungsereignisse für eine oder alle Steckdosen ab"""
|
||||||
|
db = get_db()
|
||||||
|
|
||||||
|
if socket_id:
|
||||||
|
rows = db.execute('''
|
||||||
|
SELECT su.*, s.name, s.ip_address FROM socket_uptime su
|
||||||
|
JOIN socket s ON su.socket_id = s.id
|
||||||
|
WHERE su.socket_id = ?
|
||||||
|
ORDER BY su.timestamp DESC
|
||||||
|
LIMIT ?
|
||||||
|
''', (socket_id, limit)).fetchall()
|
||||||
|
else:
|
||||||
|
rows = db.execute('''
|
||||||
|
SELECT su.*, s.name, s.ip_address FROM socket_uptime su
|
||||||
|
JOIN socket s ON su.socket_id = s.id
|
||||||
|
ORDER BY su.timestamp DESC
|
||||||
|
LIMIT ?
|
||||||
|
''', (limit,)).fetchall()
|
||||||
|
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
def check_socket_connection(socket_id):
|
||||||
|
"""Überprüft die Verbindung zu einer Steckdose und aktualisiert den Status"""
|
||||||
|
socket = get_socket_by_id(socket_id)
|
||||||
|
if not socket or not socket['ip_address']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
previous_status = socket.get('connection_status', 'unknown')
|
||||||
|
last_seen = socket.get('last_seen')
|
||||||
|
|
||||||
|
try:
|
||||||
|
device = get_socket_device(socket['ip_address'])
|
||||||
|
if device:
|
||||||
|
# Verbindung erfolgreich
|
||||||
|
if previous_status != 'online':
|
||||||
|
# Status hat sich von offline/unknown auf online geändert
|
||||||
|
duration = None
|
||||||
|
if previous_status == 'offline' and last_seen:
|
||||||
|
# Berechne die Dauer des Ausfalls
|
||||||
|
offline_since = datetime.datetime.fromisoformat(last_seen)
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
duration = int((now - offline_since).total_seconds())
|
||||||
|
|
||||||
|
log_socket_connection_event(socket_id, 'online', duration)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Keine Verbindung möglich
|
||||||
|
if previous_status != 'offline':
|
||||||
|
# Status hat sich von online/unknown auf offline geändert
|
||||||
|
log_socket_connection_event(socket_id, 'offline')
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error(f"Fehler bei der Überprüfung der Steckdose {socket['ip_address']}: {e}")
|
||||||
|
if previous_status != 'offline':
|
||||||
|
log_socket_connection_event(socket_id, 'offline')
|
||||||
|
return False
|
||||||
|
|
||||||
# Steckdosen-Steuerung mit PyP100
|
# Steckdosen-Steuerung mit PyP100
|
||||||
def get_socket_device(ip_address):
|
def get_socket_device(ip_address):
|
||||||
try:
|
try:
|
||||||
@ -1072,6 +1197,11 @@ def stats():
|
|||||||
total_sockets = db.execute('SELECT COUNT(*) as count FROM socket').fetchone()['count']
|
total_sockets = db.execute('SELECT COUNT(*) as count FROM socket').fetchone()['count']
|
||||||
available_sockets = db.execute('SELECT COUNT(*) as count FROM socket WHERE status = 0').fetchone()['count']
|
available_sockets = db.execute('SELECT COUNT(*) as count FROM socket WHERE status = 0').fetchone()['count']
|
||||||
|
|
||||||
|
# Verbindungsstatistiken
|
||||||
|
online_sockets = db.execute("SELECT COUNT(*) as count FROM socket WHERE connection_status = 'online'").fetchone()['count']
|
||||||
|
offline_sockets = db.execute("SELECT COUNT(*) as count FROM socket WHERE connection_status = 'offline'").fetchone()['count']
|
||||||
|
unknown_sockets = db.execute("SELECT COUNT(*) as count FROM socket WHERE connection_status = 'unknown' OR connection_status IS NULL").fetchone()['count']
|
||||||
|
|
||||||
# Job-Statistiken
|
# Job-Statistiken
|
||||||
total_jobs = db.execute('SELECT COUNT(*) as count FROM job').fetchone()['count']
|
total_jobs = db.execute('SELECT COUNT(*) as count FROM job').fetchone()['count']
|
||||||
|
|
||||||
@ -1095,11 +1225,30 @@ def stats():
|
|||||||
avg_duration_result = db.execute('SELECT AVG(duration_in_minutes) as avg FROM job').fetchone()
|
avg_duration_result = db.execute('SELECT AVG(duration_in_minutes) as avg FROM job').fetchone()
|
||||||
avg_duration = int(avg_duration_result['avg']) if avg_duration_result['avg'] else 0
|
avg_duration = int(avg_duration_result['avg']) if avg_duration_result['avg'] else 0
|
||||||
|
|
||||||
|
# Steckdosen-Fehlerstatistiken (letzten 7 Tage)
|
||||||
|
seven_days_ago = (datetime.datetime.utcnow() - timedelta(days=7)).isoformat()
|
||||||
|
outages = db.execute('''
|
||||||
|
SELECT COUNT(*) as count FROM socket_uptime
|
||||||
|
WHERE status = 'offline'
|
||||||
|
AND timestamp > ?
|
||||||
|
''', (seven_days_ago,)).fetchone()['count']
|
||||||
|
|
||||||
|
# Steckdosen mit aktuellen Problemen
|
||||||
|
problem_sockets = db.execute('''
|
||||||
|
SELECT s.name, s.connection_status, s.last_seen
|
||||||
|
FROM socket s
|
||||||
|
WHERE s.connection_status = 'offline'
|
||||||
|
''').fetchall()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'printers': {
|
'printers': {
|
||||||
'total': total_sockets,
|
'total': total_sockets,
|
||||||
'available': available_sockets,
|
'available': available_sockets,
|
||||||
'utilization_rate': (total_sockets - available_sockets) / total_sockets if total_sockets > 0 else 0
|
'utilization_rate': (total_sockets - available_sockets) / total_sockets if total_sockets > 0 else 0,
|
||||||
|
'online': online_sockets,
|
||||||
|
'offline': offline_sockets,
|
||||||
|
'unknown': unknown_sockets,
|
||||||
|
'connectivity_rate': online_sockets / total_sockets if total_sockets > 0 else 0
|
||||||
},
|
},
|
||||||
'jobs': {
|
'jobs': {
|
||||||
'total': total_jobs,
|
'total': total_jobs,
|
||||||
@ -1109,9 +1258,62 @@ def stats():
|
|||||||
},
|
},
|
||||||
'users': {
|
'users': {
|
||||||
'total': total_users
|
'total': total_users
|
||||||
|
},
|
||||||
|
'uptime': {
|
||||||
|
'outages_last_7_days': outages,
|
||||||
|
'problem_printers': [{'name': row['name'], 'status': row['connection_status'], 'last_seen': row['last_seen']} for row in problem_sockets]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/uptime', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def uptime_stats():
|
||||||
|
"""Liefert detaillierte Uptime-Statistiken für das Dashboard."""
|
||||||
|
socket_id = request.args.get('socket_id')
|
||||||
|
limit = int(request.args.get('limit', 100))
|
||||||
|
|
||||||
|
# Rufe die letzten Uptime-Ereignisse ab
|
||||||
|
events = get_socket_uptime_events(socket_id, limit)
|
||||||
|
|
||||||
|
# Gruppiere Ereignisse nach Steckdose
|
||||||
|
sockets = {}
|
||||||
|
for event in events:
|
||||||
|
socket_id = event['socket_id']
|
||||||
|
if socket_id not in sockets:
|
||||||
|
sockets[socket_id] = {
|
||||||
|
'id': socket_id,
|
||||||
|
'name': event['name'],
|
||||||
|
'ip_address': event['ip_address'],
|
||||||
|
'events': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Füge Ereignis zur Steckdosenliste hinzu
|
||||||
|
sockets[socket_id]['events'].append({
|
||||||
|
'id': event['id'],
|
||||||
|
'timestamp': event['timestamp'],
|
||||||
|
'status': event['status'],
|
||||||
|
'duration_seconds': event['duration_seconds']
|
||||||
|
})
|
||||||
|
|
||||||
|
# Hole den aktuellen Status aller Steckdosen
|
||||||
|
all_sockets = get_all_sockets()
|
||||||
|
current_status = {}
|
||||||
|
for socket in all_sockets:
|
||||||
|
current_status[socket['id']] = {
|
||||||
|
'connection_status': socket.get('connection_status', 'unknown'),
|
||||||
|
'last_seen': socket.get('last_seen')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Füge den aktuellen Status zu den Socket-Informationen hinzu
|
||||||
|
for socket_id, socket_data in sockets.items():
|
||||||
|
if socket_id in current_status:
|
||||||
|
socket_data['current_status'] = current_status[socket_id]
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'sockets': list(sockets.values())
|
||||||
|
})
|
||||||
|
|
||||||
# Regelmäßige Überprüfung der Jobs und automatische Abschaltung der Steckdosen
|
# Regelmäßige Überprüfung der Jobs und automatische Abschaltung der Steckdosen
|
||||||
def check_jobs():
|
def check_jobs():
|
||||||
"""Überprüft abgelaufene Jobs und schaltet Steckdosen automatisch aus."""
|
"""Überprüft abgelaufene Jobs und schaltet Steckdosen automatisch aus."""
|
||||||
@ -1147,24 +1349,63 @@ def check_jobs():
|
|||||||
|
|
||||||
app.logger.info(f"{len(expired_jobs)} abgelaufene Jobs überprüft, {handled_jobs} Steckdosen aktualisiert.")
|
app.logger.info(f"{len(expired_jobs)} abgelaufene Jobs überprüft, {handled_jobs} Steckdosen aktualisiert.")
|
||||||
|
|
||||||
# Hintergrund-Thread für das Job-Polling
|
def check_socket_connections():
|
||||||
|
"""Überprüft periodisch die Verbindung zu allen Steckdosen."""
|
||||||
|
with app.app_context():
|
||||||
|
sockets = get_all_sockets()
|
||||||
|
app.logger.info(f"Überprüfe Verbindungsstatus von {len(sockets)} Steckdosen")
|
||||||
|
|
||||||
|
online_count = 0
|
||||||
|
offline_count = 0
|
||||||
|
|
||||||
|
for socket in sockets:
|
||||||
|
if not socket['ip_address']:
|
||||||
|
continue # Überspringe Steckdosen ohne IP-Adresse
|
||||||
|
|
||||||
|
is_online = check_socket_connection(socket['id'])
|
||||||
|
if is_online:
|
||||||
|
online_count += 1
|
||||||
|
else:
|
||||||
|
offline_count += 1
|
||||||
|
|
||||||
|
app.logger.info(f"Verbindungsüberprüfung abgeschlossen: {online_count} online, {offline_count} offline")
|
||||||
|
|
||||||
|
# Hintergrund-Thread für das Job-Polling und Steckdosen-Monitoring
|
||||||
def background_job_checker():
|
def background_job_checker():
|
||||||
"""Hintergrund-Thread, der regelmäßig abgelaufene Jobs überprüft."""
|
"""Hintergrund-Thread, der regelmäßig abgelaufene Jobs und Steckdosenverbindungen überprüft."""
|
||||||
app.logger.info("Starte Hintergrund-Thread für Job-Überprüfung")
|
app.logger.info("Starte Hintergrund-Thread für Job-Überprüfung und Steckdosen-Monitoring")
|
||||||
|
|
||||||
|
# Standardintervall für Socket-Überprüfungen (5 Minuten)
|
||||||
|
socket_check_interval = int(os.environ.get('SOCKET_CHECK_INTERVAL', '300'))
|
||||||
|
last_socket_check = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
# Überprüfe Jobs bei jedem Durchlauf
|
||||||
check_jobs()
|
check_jobs()
|
||||||
|
|
||||||
|
# Überprüfe Steckdosen nur in längeren Intervallen
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_socket_check >= socket_check_interval:
|
||||||
|
check_socket_connections()
|
||||||
|
last_socket_check = current_time
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.error(f"Fehler im Hintergrund-Thread für Job-Überprüfung: {e}")
|
app.logger.error(f"Fehler im Hintergrund-Thread: {e}")
|
||||||
|
|
||||||
# Pause zwischen den Überprüfungen
|
# Pause zwischen den Überprüfungen
|
||||||
time.sleep(app.config['JOB_CHECK_INTERVAL'])
|
time.sleep(app.config['JOB_CHECK_INTERVAL'])
|
||||||
|
|
||||||
# CLI-Befehl für manuelle Ausführung
|
# CLI-Befehle für manuelle Ausführung
|
||||||
@app.cli.command("check-jobs")
|
@app.cli.command("check-jobs")
|
||||||
def cli_check_jobs():
|
def cli_check_jobs():
|
||||||
"""CLI-Befehl zur manuellen Überprüfung abgelaufener Jobs."""
|
"""CLI-Befehl zur manuellen Überprüfung abgelaufener Jobs."""
|
||||||
check_jobs()
|
check_jobs()
|
||||||
|
|
||||||
|
@app.cli.command("check-sockets")
|
||||||
|
def cli_check_sockets():
|
||||||
|
"""CLI-Befehl zur manuellen Überprüfung aller Steckdosenverbindungen."""
|
||||||
|
check_socket_connections()
|
||||||
|
|
||||||
@app.route('/api/job/<job_id>/status', methods=['GET'])
|
@app.route('/api/job/<job_id>/status', methods=['GET'])
|
||||||
def job_status(job_id):
|
def job_status(job_id):
|
||||||
|
@ -18,9 +18,47 @@
|
|||||||
<!-- Wird dynamisch gefüllt -->
|
<!-- Wird dynamisch gefüllt -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<!-- Problem-Drucker-Bereich -->
|
||||||
<h6>API-Antwort:</h6>
|
<div class="row mt-4">
|
||||||
<pre class="api-response" id="statsResponse"></pre>
|
<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>
|
</div>
|
||||||
@ -29,23 +67,48 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% 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>
|
<script>
|
||||||
|
let uptimeChart;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Statistiken laden
|
// Statistiken laden
|
||||||
document.querySelector('form[data-url="/api/stats"]').dispatchEvent(new Event('submit'));
|
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
|
// Statistiken aktualisieren, wenn API-Antwort geladen wird
|
||||||
const statsResponse = document.getElementById('statsResponse');
|
const statsResponse = document.getElementById('statsResponse');
|
||||||
const observer = new MutationObserver(function(mutations) {
|
const statsObserver = new MutationObserver(function(mutations) {
|
||||||
try {
|
try {
|
||||||
const stats = JSON.parse(statsResponse.textContent);
|
const stats = JSON.parse(statsResponse.textContent);
|
||||||
updateStatsDisplay(stats);
|
updateStatsDisplay(stats);
|
||||||
|
updateProblemPrinters(stats);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Fehler beim Parsen der Statistik-Daten:', e);
|
console.error('Fehler beim Parsen der Statistik-Daten:', e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(statsResponse, { childList: true, characterData: true, subtree: true });
|
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) {
|
function updateStatsDisplay(stats) {
|
||||||
@ -73,12 +136,31 @@
|
|||||||
<span>Auslastung:</span>
|
<span>Auslastung:</span>
|
||||||
<span>${Math.round(stats.printers.utilization_rate * 100)}%</span>
|
<span>${Math.round(stats.printers.utilization_rate * 100)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress mt-3">
|
<div class="progress mt-3 mb-3">
|
||||||
<div class="progress-bar" role="progressbar"
|
<div class="progress-bar" role="progressbar"
|
||||||
style="width: ${Math.round(stats.printers.utilization_rate * 100)}%">
|
style="width: ${Math.round(stats.printers.utilization_rate * 100)}%">
|
||||||
${Math.round(stats.printers.utilization_rate * 100)}%
|
${Math.round(stats.printers.utilization_rate * 100)}%
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -112,19 +194,28 @@
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Benutzer-Statistiken
|
// Benutzer- und Uptime-Statistiken
|
||||||
const userStats = document.createElement('div');
|
const userStats = document.createElement('div');
|
||||||
userStats.className = 'col-md-4 mb-3';
|
userStats.className = 'col-md-4 mb-3';
|
||||||
userStats.innerHTML = `
|
userStats.innerHTML = `
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header bg-info text-white">
|
<div class="card-header bg-info text-white">
|
||||||
<h5 class="mb-0">Benutzer</h5>
|
<h5 class="mb-0">System</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<span>Gesamt:</span>
|
<span>Benutzer:</span>
|
||||||
<span>${stats.users.total}</span>
|
<span>${stats.users.total}</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -133,5 +224,172 @@
|
|||||||
container.appendChild(jobStats);
|
container.appendChild(jobStats);
|
||||||
container.appendChild(userStats);
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user