Implementiere Uptime-Überwachung für Steckdosen mit Dashboard-Anzeige
This commit is contained in:
		
							
								
								
									
										255
									
								
								backend/app.py
									
									
									
									
									
								
							
							
						
						
									
										255
									
								
								backend/app.py
									
									
									
									
									
								
							@@ -90,7 +90,9 @@ def init_db():
 | 
			
		||||
        name TEXT NOT NULL,
 | 
			
		||||
        description TEXT NOT NULL,
 | 
			
		||||
        status INTEGER DEFAULT 0,
 | 
			
		||||
        ip_address TEXT
 | 
			
		||||
        ip_address TEXT,
 | 
			
		||||
        last_seen TIMESTAMP,
 | 
			
		||||
        connection_status TEXT DEFAULT 'unknown'
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    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 (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()
 | 
			
		||||
@@ -356,11 +367,44 @@ def socket_to_dict(socket):
 | 
			
		||||
    latest_job = get_latest_job_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 {
 | 
			
		||||
        'id': socket['id'],
 | 
			
		||||
        'name': socket['name'],
 | 
			
		||||
        'description': socket['description'],
 | 
			
		||||
        '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,
 | 
			
		||||
        '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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
def get_socket_device(ip_address):
 | 
			
		||||
    try:
 | 
			
		||||
@@ -1072,6 +1197,11 @@ def stats():
 | 
			
		||||
    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']
 | 
			
		||||
    
 | 
			
		||||
    # 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
 | 
			
		||||
    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 = 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({
 | 
			
		||||
        'printers': {
 | 
			
		||||
            'total': total_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': {
 | 
			
		||||
            'total': total_jobs,
 | 
			
		||||
@@ -1109,9 +1258,62 @@ def stats():
 | 
			
		||||
        },
 | 
			
		||||
        '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
 | 
			
		||||
def check_jobs():
 | 
			
		||||
    """Ü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.")
 | 
			
		||||
 | 
			
		||||
# 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():
 | 
			
		||||
    """Hintergrund-Thread, der regelmäßig abgelaufene Jobs überprüft."""
 | 
			
		||||
    app.logger.info("Starte Hintergrund-Thread für Job-Überprüfung")
 | 
			
		||||
    """Hintergrund-Thread, der regelmäßig abgelaufene Jobs und Steckdosenverbindungen überprüft."""
 | 
			
		||||
    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:
 | 
			
		||||
        try:
 | 
			
		||||
            # Überprüfe Jobs bei jedem Durchlauf
 | 
			
		||||
            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:
 | 
			
		||||
            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
 | 
			
		||||
        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")
 | 
			
		||||
def cli_check_jobs():
 | 
			
		||||
    """CLI-Befehl zur manuellen Überprüfung abgelaufener 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'])
 | 
			
		||||
def job_status(job_id):
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,47 @@
 | 
			
		||||
                    <!-- Wird dynamisch gefüllt -->
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="mt-4">
 | 
			
		||||
                    <h6>API-Antwort:</h6>
 | 
			
		||||
                    <pre class="api-response" id="statsResponse"></pre>
 | 
			
		||||
                <!-- 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>
 | 
			
		||||
@@ -29,23 +67,48 @@
 | 
			
		||||
{% 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 observer = new MutationObserver(function(mutations) {
 | 
			
		||||
        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);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        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) {
 | 
			
		||||
@@ -73,12 +136,31 @@
 | 
			
		||||
                        <span>Auslastung:</span>
 | 
			
		||||
                        <span>${Math.round(stats.printers.utilization_rate * 100)}%</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="progress mt-3">
 | 
			
		||||
                    <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>
 | 
			
		||||
        `;
 | 
			
		||||
@@ -112,19 +194,28 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
        
 | 
			
		||||
        // Benutzer-Statistiken
 | 
			
		||||
        // 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">Benutzer</h5>
 | 
			
		||||
                    <h5 class="mb-0">System</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-flex justify-content-between mb-2">
 | 
			
		||||
                        <span>Gesamt:</span>
 | 
			
		||||
                        <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>
 | 
			
		||||
        `;
 | 
			
		||||
@@ -133,5 +224,172 @@
 | 
			
		||||
        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 %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user