From eff906f298cd2b06081af59342f02ee79a2d6a99 Mon Sep 17 00:00:00 2001 From: ginnoir Date: Thu, 11 Jun 2026 20:14:07 -0500 Subject: [PATCH] feat: add uptime checking script for scheduled reports --- scripts/check_uptime.py | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 scripts/check_uptime.py diff --git a/scripts/check_uptime.py b/scripts/check_uptime.py new file mode 100644 index 0000000..6bf5c6b --- /dev/null +++ b/scripts/check_uptime.py @@ -0,0 +1,88 @@ +import urllib.request +import re +import json + +def check_uptime(): + try: + # Fetch status page to extract monitor IDs and names + req = urllib.request.Request( + "https://uptime.ginnoir.com/status/default", + headers={"User-Agent": "Mozilla/5.0"} + ) + with urllib.request.urlopen(req) as response: + html = response.read().decode('utf-8') + + # Regex to find monitor mapping: {'id':29,'name':'auth.ginnoir.com','sendUrl':0,'type':'http'} + monitors = {} + matches = re.findall(r"\{'id':(\d+),'name':'([^']+)','sendUrl':\d+,'type':'[^']+'\}", html) + for mon_id, mon_name in matches: + monitors[int(mon_id)] = mon_name + + if not monitors: + print("No monitors found in status page HTML.") + return + + # Fetch heartbeats + req_hb = urllib.request.Request( + "https://uptime.ginnoir.com/api/status-page/heartbeat/default", + headers={"User-Agent": "Mozilla/5.0"} + ) + with urllib.request.urlopen(req_hb) as response: + hb_data = json.loads(response.read().decode('utf-8')) + + hb_list = hb_data.get("heartbeatList", {}) + + up_count = 0 + down_count = 0 + other_count = 0 + + status_summary = [] + down_services = [] + + for mon_id, name in sorted(monitors.items(), key=lambda x: x[1]): + # Get heartbeats for this monitor + beats = hb_list.get(str(mon_id), []) + if not beats: + status_summary.append(f"- {name}: NO_DATA") + other_count += 1 + continue + + # Get latest beat + latest = beats[-1] + status_val = latest.get("status") + ping = latest.get("ping") + msg = latest.get("msg", "") + + if status_val == 1: + status_str = "UP" + up_count += 1 + ping_str = f" ({ping}ms)" if ping is not None else "" + status_summary.append(f"- {name}: {status_str}{ping_str}") + elif status_val == 0: + status_str = "DOWN" + down_count += 1 + err_msg = f" - {msg}" if msg else "" + status_summary.append(f"- {name}: {status_str}{err_msg}") + down_services.append(name) + else: + status_str = "DEGRADED" + other_count += 1 + err_msg = f" - {msg}" if msg else "" + status_summary.append(f"- {name}: {status_str}{err_msg}") + down_services.append(name) + + print("=== MONITOR STATUS REPORT ===") + if down_services: + print(f"ALERT: {len(down_services)} service(s) down or degraded!") + print(f"DOWN SERVICES: {', '.join(down_services)}") + else: + print("STATUS: OK") + print(f"STATS: UP={up_count}, DOWN={down_count}, OTHER={other_count}") + print("\nDETAILS:") + print("\n".join(status_summary)) + + except Exception as e: + print(f"Error checking status: {e}") + +if __name__ == "__main__": + check_uptime()