From 92eaf37c0b80573fdf98071148d6153e79df077e Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 13 Aug 2025 22:55:07 +0200 Subject: [PATCH 1/4] Add ping latency check to monitoring --- monitor.py | 43 +++++++++++++++++++++++++++++++++---------- templates/index.html | 12 +++++++++--- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/monitor.py b/monitor.py index 0044e12..c3f4a6a 100644 --- a/monitor.py +++ b/monitor.py @@ -1,4 +1,5 @@ import os, time, threading, sqlite3, datetime as dt, requests +import subprocess, urllib.parse, re from flask import Flask, jsonify, Response, render_template # ────────── Konfiguration via ENV ────────── @@ -11,6 +12,19 @@ def get_utc_timestamp(): """UTC-Timestamp zurückgeben (immer UTC, unabhängig von Container-Zeitzone)""" return int(dt.datetime.now(dt.timezone.utc).timestamp()) +def ping_host(): + """Einmaliges Ping auf das TEST_URL-Host ausführen und Laufzeit in ms zurückgeben.""" + host = urllib.parse.urlparse(TEST_URL).hostname or TEST_URL + try: + r = subprocess.run(["ping", "-c", "1", "-W", "1", host], capture_output=True, text=True) + if r.returncode == 0: + m = re.search(r"time=([0-9.]+) ms", r.stdout) + if m: + return float(m.group(1)) + except Exception: + pass + return None + # ────────── Backend ────────── app = Flask(__name__) @@ -18,20 +32,28 @@ def get_utc_timestamp(): def init_db(): """Tabelle anlegen, falls sie noch nicht existiert.""" with sqlite3.connect(DB_PATH) as c: - c.execute("CREATE TABLE IF NOT EXISTS status(ts INTEGER PRIMARY KEY, up INTEGER)") + c.execute("CREATE TABLE IF NOT EXISTS status(ts INTEGER PRIMARY KEY, up INTEGER, ping REAL)") + try: + c.execute("ALTER TABLE status ADD COLUMN ping REAL") + except sqlite3.OperationalError: + pass def write_sample(): """Einen Connectivity-Datensatz schreiben und alte Daten trimmen.""" up = 1 - try: + ping_ms = None + try: requests.get(TEST_URL, timeout=5) - except requests.RequestException: + ping_ms = ping_host() + if ping_ms is None: + up = 0 + except requests.RequestException: up = 0 now = get_utc_timestamp() # UTC-Timestamp verwenden with sqlite3.connect(DB_PATH) as c: # INSERT OR REPLACE verwenden um Duplikate zu vermeiden - c.execute("INSERT OR REPLACE INTO status VALUES(?,?)", (now, up)) + c.execute("INSERT OR REPLACE INTO status(ts,up,ping) VALUES(?,?,?)", (now, up, ping_ms)) # Alte Daten löschen - aber nur sehr alte (älter als RETENTION) cutoff = now - RETENTION*86400 deleted = c.execute("DELETE FROM status WHERE ts < ?", (cutoff,)).rowcount @@ -52,7 +74,7 @@ def index(): def data(): # Alle Daten holen (ohne since Filter) um das Problem zu debuggen with sqlite3.connect(DB_PATH) as c: - rows = c.execute("SELECT ts,up FROM status ORDER BY ts").fetchall() + rows = c.execute("SELECT ts,up,ping FROM status ORDER BY ts").fetchall() total_count = c.execute("SELECT COUNT(*) FROM status").fetchone()[0] print(f"DEBUG: Total records: {total_count}, All records: {len(rows)}") @@ -73,10 +95,11 @@ def data(): # Basis-Daten für detaillierten Verlauf - nur letzten 12 Stunden twelve_hours_ago = now - (12 * 3600) # 12 Stunden in Sekunden detail_rows = [row for row in rows if row[0] >= twelve_hours_ago] - + result = { - "labels":[t for t,_ in detail_rows], # Raw UTC timestamps der letzten 12h senden - "values":[u for _,u in detail_rows] + "labels":[t for t,_,_ in detail_rows], # Raw UTC timestamps der letzten 12h senden + "values":[u for _,u,_ in detail_rows], + "pings":[p for _,_,p in detail_rows] } # 24-Stunden-Aggregation (letzte 24h) @@ -87,7 +110,7 @@ def data(): # Von der aktuellen Stunde 23 Stunden zurückgehen hour_start = current_hour_start - (h * 3600) hour_end = hour_start + 3600 - hour_rows = [up for ts, up in rows if hour_start <= ts < hour_end] + hour_rows = [up for ts, up, _ in rows if hour_start <= ts < hour_end] if hour_rows: uptime = sum(hour_rows) / len(hour_rows) @@ -113,7 +136,7 @@ def data(): day_start = int(day_start_dt.timestamp()) day_end = int(day_end_dt.timestamp()) - day_rows = [up for ts, up in rows if day_start <= ts <= day_end] + day_rows = [up for ts, up, _ in rows if day_start <= ts <= day_end] if day_rows: uptime = sum(day_rows) / len(day_rows) diff --git a/templates/index.html b/templates/index.html index 2c99c8a..041df57 100644 --- a/templates/index.html +++ b/templates/index.html @@ -65,11 +65,12 @@

Internet Connection Monitor

async function reloadData(){ const r = await fetch('/data'); - const {labels,values,hourly,daily} = await r.json(); + const {labels,values,pings,hourly,daily} = await r.json(); // Set status text + const lastPing = pings.length ? pings.at(-1) : null; const statusTxt = values.length - ? (values.at(-1) ? '🟢 Connection up' : '🔴 No connection') + ? (values.at(-1) ? `🟢 Connection up${lastPing!==null?` (ping ${lastPing} ms)`:''}` : '🔴 No connection') : 'No data yet'; document.getElementById('status').textContent = statusTxt; @@ -80,15 +81,20 @@

Internet Connection Monitor

chart = new Chart(document.getElementById('c'),{ type:'line', data:{labels:localLabels, - datasets:[{label:'Online Status',data:values,stepped:true,fill:true}]}, + datasets:[ + {label:'Online Status',data:values,stepped:true,fill:true,yAxisID:'y'}, + {label:'Ping (ms)',data:pings,borderColor:'#3b82f6',yAxisID:'y1'} + ]}, options:{scales:{ y:{min:0,max:1,ticks:{stepSize:1,callback:v=>v?'Online':'Offline'}}, + y1:{position:'right',beginAtZero:true}, x:{type:'time',time:{unit:'hour'}} }} }); }else{ chart.data.labels = localLabels; chart.data.datasets[0].data = values; + chart.data.datasets[1].data = pings; chart.update(); } From ab8ab6ce31474df1900b8d59e80e4c89da7766f3 Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 13 Aug 2025 23:15:14 +0200 Subject: [PATCH 2/4] docs: document ping monitoring --- DOCKER_README.md | 7 ++++--- README.md | 49 +++++++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index b583478..8ede053 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -4,7 +4,7 @@ ![Docker Image Size](https://img.shields.io/docker/image-size/syntaxsorcerer7/internet-monitor) ![Docker Stars](https://img.shields.io/docker/stars/syntaxsorcerer7/internet-monitor) -A containerized internet monitoring tool that continuously tracks your internet connection availability and visualizes it in a clear web interface with professional charts. +A containerized internet monitoring tool that continuously tracks your internet connection availability and ping latency and visualizes it in a clear web interface with professional charts. ## 🚀 Quick Start @@ -22,8 +22,8 @@ open http://localhost:8000 ## 📊 Features ### 📈 Three Monitoring Levels -- **📍 Detailed History**: Minute-by-minute recording of all connectivity tests -- **⏰ 24-Hour Overview**: Hourly aggregation with availability percentages +- **📍 Detailed History**: Minute-by-minute recording of all connectivity tests including ping latency +- **⏰ 24-Hour Overview**: Hourly aggregation with availability percentages - **📅 30-Day Overview**: Daily long-term trends for SLA monitoring ### 🎯 Professional Availability Levels @@ -38,6 +38,7 @@ open http://localhost:8000 - **Persistent Storage**: SQLite database with automatic cleanup - **Multi-Platform**: Supports AMD64 and ARM64 (Raspberry Pi, Apple Silicon) - **High Performance**: Minimal resource usage through Alpine Linux +- **Ping Latency Measurement**: ICMP ping to capture latency and display in dashboard ## ⚙️ Configuration diff --git a/README.md b/README.md index 85e0cea..258b410 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Internet-Verbindungsmonitor 🌐 -Ein containerisiertes Internet-Monitoring-Tool, das die Verfügbarkeit Ihrer Internetverbindung kontinuierlich überwacht und in einer übersichtlichen Web-Oberfläche mit professionellen Diagrammen visualisiert. +Ein containerisiertes Internet-Monitoring-Tool, das die Verfügbarkeit Ihrer Internetverbindung sowie die Ping-Latenz kontinuierlich überwacht und in einer übersichtlichen Web-Oberfläche mit professionellen Diagrammen visualisiert. ![Internet Monitor Dashboard](https://img.shields.io/badge/Status-Production%20Ready-green) ![Container](https://img.shields.io/badge/Container-Podman%2FDocker-blue) @@ -38,7 +38,7 @@ cd internet-check ### 📈 Drei Monitoring-Ebenen -- **📍 Detaillierter Verlauf**: Minutengenaue Aufzeichnung aller Connectivity-Tests +- **📍 Detaillierter Verlauf**: Minutengenaue Aufzeichnung aller Connectivity-Tests inklusive Ping-Latenz - **⏰ 24-Stunden-Übersicht**: Stündliche Aggregation mit Verfügbarkeitsprozenten - **📅 30-Tage-Übersicht**: Tägliche Langzeittrends für SLA-Monitoring @@ -56,6 +56,7 @@ cd internet-check - **Persistente Speicherung**: SQLite-Datenbank mit automatischem Cleanup - **Container-Ready**: Podman/Docker-basiert ohne komplexe Abhängigkeiten - **Hochperformant**: Minimaler Ressourcenverbrauch durch Alpine Linux +- **Ping-Latenzmessung**: ICMP-Ping zur Erfassung der aktuellen Latenz und Anzeige im Dashboard ## ⚙️ Konfiguration @@ -161,25 +162,29 @@ Das Projekt ist jetzt modular strukturiert: ### 📊 Datenmodell +Die Status-Tabelle speichert neben dem Onlinestatus auch die gemessene Ping-Latenz: + ```sql CREATE TABLE status ( - ts INTEGER PRIMARY KEY, -- Unix-Timestamp (Sekunden seit 1970) - up INTEGER -- 1=online, 0=offline + ts INTEGER PRIMARY KEY, -- Unix-Timestamp (Sekunden seit 1970) + up INTEGER, -- 1 = online, 0 = offline + ping REAL -- gemessene Ping-Latenz in Millisekunden ); ``` ### 🔗 API-Endpunkte - `GET /` - Responsive Web-Dashboard -- `GET /data` - JSON-API mit Roh- und Aggregationsdaten +- `GET /data` - JSON-API mit Roh- und Aggregationsdaten inklusive Ping-Latenzen (`pings`) -### � Monitoring-Algorithmus +### 🛰️ Monitoring-Algorithmus 1. **HTTP-Test**: GET-Request an konfigurierte URL -2. **Timeout-Handling**: 5 Sekunden maximale Wartezeit -3. **Bewertung**: HTTP 2xx = Online | Timeout/Error = Offline -4. **Speicherung**: Timestamp + Status in SQLite -5. **Retention**: Automatisches Cleanup alter Daten +2. **Ping-Messung**: ICMP-Ping zur Ermittlung der Latenz +3. **Timeout-Handling**: 5 Sekunden maximale Wartezeit +4. **Bewertung**: HTTP 2xx = Online | Timeout/Error oder fehlender Ping = Offline +5. **Speicherung**: Timestamp + Status + Ping in SQLite +6. **Retention**: Automatisches Cleanup alter Daten ## 💡 Anwendungsfälle @@ -214,19 +219,21 @@ podman exec monitor-primary cp /app/data.db /tmp/ podman cp monitor-primary:/tmp/data.db ./backup_$(date +%Y%m%d).db # CSV-Export für Excel-Analyse -podman exec monitor-primary sqlite3 /app/data.db \ - "SELECT datetime(ts, 'unixepoch') as timestamp, - CASE up WHEN 1 THEN 'Online' ELSE 'Offline' END as status - FROM status ORDER BY ts;" \ - -header -csv > connectivity_report.csv + podman exec monitor-primary sqlite3 /app/data.db \ + "SELECT datetime(ts, 'unixepoch') as timestamp, + CASE up WHEN 1 THEN 'Online' ELSE 'Offline' END as status, + ping + FROM status ORDER BY ts;" \ + -header -csv > connectivity_report.csv # Statistiken abrufen -podman exec monitor-primary sqlite3 /app/data.db \ - "SELECT - COUNT(*) as total_checks, - SUM(up) as successful_checks, - ROUND(SUM(up) * 100.0 / COUNT(*), 2) as uptime_percentage - FROM status WHERE ts >= strftime('%s', 'now', '-24 hours');" + podman exec monitor-primary sqlite3 /app/data.db \ + "SELECT + COUNT(*) as total_checks, + SUM(up) as successful_checks, + ROUND(SUM(up) * 100.0 / COUNT(*), 2) as uptime_percentage, + ROUND(AVG(ping), 2) as avg_ping_ms + FROM status WHERE ts >= strftime('%s', 'now', '-24 hours');" ``` ## 📊 SLA-Referenztabelle From 2364d6d5fcb5ce7458ad9449b99322524827d43b Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 13 Aug 2025 23:15:19 +0200 Subject: [PATCH 3/4] Measure ping in loop and show aggregated latency --- DOCKER_README.md | 4 +- README.md | 10 ++--- monitor.py | 53 ++++++++++++++++--------- templates/index.html | 94 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 121 insertions(+), 40 deletions(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index 8ede053..3520d5c 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -23,8 +23,8 @@ open http://localhost:8000 ### 📈 Three Monitoring Levels - **📍 Detailed History**: Minute-by-minute recording of all connectivity tests including ping latency -- **⏰ 24-Hour Overview**: Hourly aggregation with availability percentages -- **📅 30-Day Overview**: Daily long-term trends for SLA monitoring +- **⏰ 24-Hour Overview**: Hourly aggregation with availability percentages plus average and 99th percentile ping +- **📅 30-Day Overview**: Daily long-term trends including average and 99th percentile ping for SLA monitoring ### 🎯 Professional Availability Levels - 🟢 **Excellent** (≥99.9%): Enterprise-grade availability diff --git a/README.md b/README.md index 258b410..7842e86 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ cd internet-check ### 📈 Drei Monitoring-Ebenen - **📍 Detaillierter Verlauf**: Minutengenaue Aufzeichnung aller Connectivity-Tests inklusive Ping-Latenz -- **⏰ 24-Stunden-Übersicht**: Stündliche Aggregation mit Verfügbarkeitsprozenten -- **📅 30-Tage-Übersicht**: Tägliche Langzeittrends für SLA-Monitoring +- **⏰ 24-Stunden-Übersicht**: Stündliche Aggregation mit Verfügbarkeitsprozenten sowie durchschnittlicher und 99%-Perzentil-Ping +- **📅 30-Tage-Übersicht**: Tägliche Langzeittrends inklusive durchschnittlicher und 99%-Perzentil-Ping-Latenz für SLA-Monitoring ### 🎯 Professionelle Verfügbarkeitsstufen @@ -179,10 +179,10 @@ CREATE TABLE status ( ### 🛰️ Monitoring-Algorithmus -1. **HTTP-Test**: GET-Request an konfigurierte URL -2. **Ping-Messung**: ICMP-Ping zur Ermittlung der Latenz +1. **Ping-Messung**: ICMP-Ping zur Ermittlung der Latenz +2. **HTTP-Test**: GET-Request an die konfigurierte URL 3. **Timeout-Handling**: 5 Sekunden maximale Wartezeit -4. **Bewertung**: HTTP 2xx = Online | Timeout/Error oder fehlender Ping = Offline +4. **Bewertung**: HTTP 2xx = Online, sonst Offline 5. **Speicherung**: Timestamp + Status + Ping in SQLite 6. **Retention**: Automatisches Cleanup alter Daten diff --git a/monitor.py b/monitor.py index c3f4a6a..2cd7eb5 100644 --- a/monitor.py +++ b/monitor.py @@ -1,5 +1,5 @@ import os, time, threading, sqlite3, datetime as dt, requests -import subprocess, urllib.parse, re +import subprocess, urllib.parse, re, math from flask import Flask, jsonify, Response, render_template # ────────── Konfiguration via ENV ────────── @@ -40,13 +40,10 @@ def init_db(): def write_sample(): """Einen Connectivity-Datensatz schreiben und alte Daten trimmen.""" + ping_ms = ping_host() # Ping immer messen, unabhängig vom HTTP-Test up = 1 - ping_ms = None try: requests.get(TEST_URL, timeout=5) - ping_ms = ping_host() - if ping_ms is None: - up = 0 except requests.RequestException: up = 0 @@ -105,21 +102,30 @@ def data(): # 24-Stunden-Aggregation (letzte 24h) current_hour_start = (now // 3600) * 3600 # Aktuelle Stunde, auf den Stundenanfang gerundet hourly_data = [] - + for h in range(24): # Von der aktuellen Stunde 23 Stunden zurückgehen hour_start = current_hour_start - (h * 3600) hour_end = hour_start + 3600 - hour_rows = [up for ts, up, _ in rows if hour_start <= ts < hour_end] - + hour_rows = [(up, ping) for ts, up, ping in rows if hour_start <= ts < hour_end] + if hour_rows: - uptime = sum(hour_rows) / len(hour_rows) + ups = [u for u, _ in hour_rows] + pings = [p for _, p in hour_rows if p is not None] + uptime = sum(ups) / len(ups) + ping_avg = sum(pings) / len(pings) if pings else None + if pings: + k = max(0, min(len(pings)-1, math.ceil(len(pings) * 0.99) - 1)) + ping_p99 = sorted(pings)[k] + else: + ping_p99 = None else: uptime = -1 # Keine Daten verfügbar - + ping_avg = ping_p99 = None + # Nur die Stunde des Tages (0-23) senden hour_of_day = dt.datetime.fromtimestamp(hour_start, dt.timezone.utc).hour - hourly_data.insert(0, {"hour": hour_of_day, "uptime": uptime}) + hourly_data.insert(0, {"hour": hour_of_day, "uptime": uptime, "ping_avg": ping_avg, "ping_p99": ping_p99}) # Tages-Aggregation (letzte 30 Tage) - korrekte Kalendertage verwenden daily_data = [] @@ -128,23 +134,32 @@ def data(): for d in range(30): # Kalendertag berechnen (d Tage zurück vom heutigen Tag) target_date = current_utc_date - dt.timedelta(days=d) - + # Start und Ende des Kalendertages in UTC day_start_dt = dt.datetime.combine(target_date, dt.time.min).replace(tzinfo=dt.timezone.utc) day_end_dt = dt.datetime.combine(target_date, dt.time.max).replace(tzinfo=dt.timezone.utc) - + day_start = int(day_start_dt.timestamp()) day_end = int(day_end_dt.timestamp()) - - day_rows = [up for ts, up, _ in rows if day_start <= ts <= day_end] - + + day_rows = [(up, ping) for ts, up, ping in rows if day_start <= ts <= day_end] + if day_rows: - uptime = sum(day_rows) / len(day_rows) + ups = [u for u, _ in day_rows] + pings = [p for _, p in day_rows if p is not None] + uptime = sum(ups) / len(ups) + ping_avg = sum(pings) / len(pings) if pings else None + if pings: + k = max(0, min(len(pings)-1, math.ceil(len(pings) * 0.99) - 1)) + ping_p99 = sorted(pings)[k] + else: + ping_p99 = None else: uptime = -1 # Keine Daten verfügbar - + ping_avg = ping_p99 = None + # UTC-Timestamp für Tagesbeginn senden - daily_data.insert(0, {"date": day_start, "uptime": uptime}) + daily_data.insert(0, {"date": day_start, "uptime": uptime, "ping_avg": ping_avg, "ping_p99": ping_p99}) result["hourly"] = hourly_data result["daily"] = daily_data diff --git a/templates/index.html b/templates/index.html index 041df57..5cf1d8f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -105,17 +105,40 @@

Internet Connection Monitor

type:'bar', data:{ labels:hourly.map(h=>h.uptime<0 ? formatHourLabel(h.hour)+' ?' : formatHourLabel(h.hour)), - datasets:[{ - label:'Hourly Status', - data:hourly.map(h=>h.uptime<0?0.5:h.uptime), - backgroundColor:hourly.map(h=>h.uptime<0?'#3b82f6':(h.uptime>=0.999?'#4ade80':(h.uptime>=0.98?'#eab308':'#ef4444'))), - borderWidth:0 - }] + datasets:[ + { + label:'Hourly Status', + data:hourly.map(h=>h.uptime<0?0.5:h.uptime), + backgroundColor:hourly.map(h=>h.uptime<0?'#3b82f6':(h.uptime>=0.999?'#4ade80':(h.uptime>=0.98?'#eab308':'#ef4444'))), + borderWidth:0, + yAxisID:'y' + }, + { + label:'Avg Ping (ms)', + data:hourly.map(h=>h.ping_avg), + type:'line', + borderColor:'#3b82f6', + backgroundColor:'transparent', + yAxisID:'y1', + tension:0.4 + }, + { + label:'99th % Ping (ms)', + data:hourly.map(h=>h.ping_p99), + type:'line', + borderColor:'#9333ea', + backgroundColor:'transparent', + borderDash:[5,5], + yAxisID:'y1', + tension:0.4 + } + ] }, options:{ responsive:true, scales:{ y:{min:0,max:1,ticks:{stepSize:0.2,callback:v=>(v*100).toFixed(0)+'%'}}, + y1:{position:'right',beginAtZero:true,title:{display:true,text:'Ping (ms)'}}, x:{title:{display:true,text:'Hour (local time)'}} }, plugins:{ @@ -123,7 +146,15 @@

Internet Connection Monitor

callbacks:{ label:ctx=>{ const hourData = hourly[ctx.dataIndex]; - return hourData.uptime<0 ? 'No data available' : `Availability: ${(ctx.parsed.y*100).toFixed(1)}%`; + if(ctx.dataset.label==='Hourly Status'){ + return hourData.uptime<0 ? 'No data available' : `Availability: ${(ctx.parsed.y*100).toFixed(1)}%`; + } + if(ctx.dataset.label==='Avg Ping (ms)'){ + return hourData.ping_avg==null ? 'Avg ping: n/a' : `Avg ping: ${hourData.ping_avg.toFixed(1)} ms`; + } + if(ctx.dataset.label==='99th % Ping (ms)'){ + return hourData.ping_p99==null ? '99th % ping: n/a' : `99th % ping: ${hourData.ping_p99.toFixed(1)} ms`; + } } } } @@ -134,6 +165,8 @@

Internet Connection Monitor

hourlyChart.data.labels = hourly.map(h=>h.uptime<0 ? formatHourLabel(h.hour)+' ?' : formatHourLabel(h.hour)); hourlyChart.data.datasets[0].data = hourly.map(h=>h.uptime<0?0.5:h.uptime); hourlyChart.data.datasets[0].backgroundColor = hourly.map(h=>h.uptime<0?'#3b82f6':(h.uptime>=0.999?'#4ade80':(h.uptime>=0.98?'#eab308':'#ef4444'))); + hourlyChart.data.datasets[1].data = hourly.map(h=>h.ping_avg); + hourlyChart.data.datasets[2].data = hourly.map(h=>h.ping_p99); hourlyChart.update(); } } @@ -145,17 +178,40 @@

Internet Connection Monitor

type:'bar', data:{ labels:daily.map(d=>d.uptime<0 ? formatDayLabel(d.date)+' ?' : formatDayLabel(d.date)), - datasets:[{ - label:'Daily Status', - data:daily.map(d=>d.uptime<0?0.5:d.uptime), - backgroundColor:daily.map(d=>d.uptime<0?'#3b82f6':(d.uptime>=0.999?'#4ade80':(d.uptime>=0.98?'#eab308':'#ef4444'))), - borderWidth:0 - }] + datasets:[ + { + label:'Daily Status', + data:daily.map(d=>d.uptime<0?0.5:d.uptime), + backgroundColor:daily.map(d=>d.uptime<0?'#3b82f6':(d.uptime>=0.999?'#4ade80':(d.uptime>=0.98?'#eab308':'#ef4444'))), + borderWidth:0, + yAxisID:'y' + }, + { + label:'Avg Ping (ms)', + data:daily.map(d=>d.ping_avg), + type:'line', + borderColor:'#3b82f6', + backgroundColor:'transparent', + yAxisID:'y1', + tension:0.4 + }, + { + label:'99th % Ping (ms)', + data:daily.map(d=>d.ping_p99), + type:'line', + borderColor:'#9333ea', + backgroundColor:'transparent', + borderDash:[5,5], + yAxisID:'y1', + tension:0.4 + } + ] }, options:{ responsive:true, scales:{ y:{min:0,max:1,ticks:{stepSize:0.2,callback:v=>(v*100).toFixed(0)+'%'}}, + y1:{position:'right',beginAtZero:true,title:{display:true,text:'Ping (ms)'}}, x:{title:{display:true,text:'Day (local time)'}} }, plugins:{ @@ -163,7 +219,15 @@

Internet Connection Monitor

callbacks:{ label:ctx=>{ const dayData = daily[ctx.dataIndex]; - return dayData.uptime<0 ? 'No data available' : `Availability: ${(ctx.parsed.y*100).toFixed(1)}%`; + if(ctx.dataset.label==='Daily Status'){ + return dayData.uptime<0 ? 'No data available' : `Availability: ${(ctx.parsed.y*100).toFixed(1)}%`; + } + if(ctx.dataset.label==='Avg Ping (ms)'){ + return dayData.ping_avg==null ? 'Avg ping: n/a' : `Avg ping: ${dayData.ping_avg.toFixed(1)} ms`; + } + if(ctx.dataset.label==='99th % Ping (ms)'){ + return dayData.ping_p99==null ? '99th % ping: n/a' : `99th % ping: ${dayData.ping_p99.toFixed(1)} ms`; + } } } } @@ -174,6 +238,8 @@

Internet Connection Monitor

dailyChart.data.labels = daily.map(d=>d.uptime<0 ? formatDayLabel(d.date)+' ?' : formatDayLabel(d.date)); dailyChart.data.datasets[0].data = daily.map(d=>d.uptime<0?0.5:d.uptime); dailyChart.data.datasets[0].backgroundColor = daily.map(d=>d.uptime<0?'#3b82f6':(d.uptime>=0.999?'#4ade80':(d.uptime>=0.98?'#eab308':'#ef4444'))); + dailyChart.data.datasets[1].data = daily.map(d=>d.ping_avg); + dailyChart.data.datasets[2].data = daily.map(d=>d.ping_p99); dailyChart.update(); } } From 75d412579be9ab53fb50512a62df85eb10dd5d2a Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 14 Aug 2025 09:45:01 +0200 Subject: [PATCH 4/4] Separate availability and ping charts --- DOCKER_README.md | 1 + README.md | 1 + templates/index.html | 285 ++++++++++++++++++++----------------------- 3 files changed, 135 insertions(+), 152 deletions(-) diff --git a/DOCKER_README.md b/DOCKER_README.md index 3520d5c..674511d 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -35,6 +35,7 @@ open http://localhost:8000 ### ⚡ Technical Highlights - **Real-time Updates**: Automatic chart refresh every 60 seconds - **Responsive Design**: Optimized for desktop, tablet, and mobile +- **Toggleable Charts**: Switch between availability and ping views - **Persistent Storage**: SQLite database with automatic cleanup - **Multi-Platform**: Supports AMD64 and ARM64 (Raspberry Pi, Apple Silicon) - **High Performance**: Minimal resource usage through Alpine Linux diff --git a/README.md b/README.md index 7842e86..0cc3d4f 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ cd internet-check - **Echtzeit-Updates**: Automatische Diagramm-Aktualisierung alle 60 Sekunden - **Responsive Design**: Optimiert für Desktop, Tablet und Mobile +- **Umschaltbare Diagramme**: Verfügbarkeit und Ping lassen sich getrennt anzeigen - **Persistente Speicherung**: SQLite-Datenbank mit automatischem Cleanup - **Container-Ready**: Podman/Docker-basiert ohne komplexe Abhängigkeiten - **Hochperformant**: Minimaler Ressourcenverbrauch durch Alpine Linux diff --git a/templates/index.html b/templates/index.html index 5cf1d8f..0d9fc5a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,12 +10,13 @@ .legend{background:#f5f5f5;padding:1rem;border-radius:8px;margin-bottom:2rem} .legend-item{display:inline-block;margin-right:2rem;margin-bottom:0.5rem} .legend-color{display:inline-block;width:20px;height:15px;margin-right:8px;vertical-align:middle;border-radius:3px} + .toggle-btn{margin-bottom:0.5rem}

Internet Connection Monitor

Starting…

- +
Availability Levels:
Excellent (≥99.9%)
@@ -23,24 +24,54 @@

Internet Connection Monitor

Problematic (<98%)
No Data
- +
Detailed Timeline
- + + +
- +
24-Hour Overview (last 24h)
- + + +
- +
Daily Overview (last 30 days)
- + + +