From 2e2d497cbdf4e13068c8d394d8d81cb9e8100d66 Mon Sep 17 00:00:00 2001 From: drjones Date: Mon, 3 Aug 2026 23:49:01 -0700 Subject: [PATCH] =?UTF-8?q?NOC=20v2:=20clean=20premium=20design=20?= =?UTF-8?q?=E2=80=94=20Inter=20font,=20proper=20cards,=20no=20scanlines,?= =?UTF-8?q?=20color-coded=20metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/noc.py | 563 ++++++++++++++--------------------------------- 1 file changed, 171 insertions(+), 392 deletions(-) diff --git a/dashboard/noc.py b/dashboard/noc.py index 6cc8c3b..d3c8f42 100644 --- a/dashboard/noc.py +++ b/dashboard/noc.py @@ -1,434 +1,213 @@ """ -Network Operations Center โ€” Real-time monitoring dashboard for all 8 publisher sites. -Hacker aesthetic, live data, comprehensive fleet overview. +NOC Dashboard โ€” clean, modern fleet monitor for all 8 publisher sites. """ -import json -import time -import sqlite3 -import threading -from datetime import datetime, timedelta -from pathlib import Path -from collections import defaultdict -from flask import Flask, render_template_string, jsonify, request +import json, time, threading, requests +from datetime import datetime +from flask import Flask, jsonify app = Flask(__name__) SITES = { - "ai": {"name": "AI Insights", "ip": "10.30.20.240", "ct": 135, "color": "#7c3aed", "accent": "#a78bfa"}, - "tech": {"name": "Tech Frontier", "ip": "10.30.20.241", "ct": 136, "color": "#2563eb", "accent": "#60a5fa"}, - "science": {"name": "Science Decoded", "ip": "10.30.20.242", "ct": 137, "color": "#059669", "accent": "#34d399"}, - "crypto": {"name": "Crypto Compass", "ip": "10.30.20.243", "ct": 138, "color": "#f59e0b", "accent": "#fbbf24"}, - "linux": {"name": "Linux Lab", "ip": "10.30.20.244", "ct": 139, "color": "#f97316", "accent": "#fb923c"}, - "gaming": {"name": "Game Layer", "ip": "10.30.20.246", "ct": 140, "color": "#ec4899", "accent": "#f472b6"}, - "diy": {"name": "Maker Forge", "ip": "10.30.20.247", "ct": 141, "color": "#ef4444", "accent": "#f87171"}, - "guides": {"name": "Practical Guides", "ip": "10.30.20.248", "ct": 142, "color": "#0891b2", "accent": "#22d3ee"}, + "ai": {"name": "AI Insights", "ip": "10.30.20.240", "ct": 135, "color": "#7c3aed"}, + "tech": {"name": "Tech Frontier", "ip": "10.30.20.241", "ct": 136, "color": "#2563eb"}, + "science": {"name": "Science Decoded", "ip": "10.30.20.242", "ct": 137, "color": "#059669"}, + "crypto": {"name": "Crypto Compass", "ip": "10.30.20.243", "ct": 138, "color": "#f59e0b"}, + "linux": {"name": "Linux Lab", "ip": "10.30.20.244", "ct": 139, "color": "#f97316"}, + "gaming": {"name": "Game Layer", "ip": "10.30.20.246", "ct": 140, "color": "#ec4899"}, + "diy": {"name": "Maker Forge", "ip": "10.30.20.247", "ct": 141, "color": "#ef4444"}, + "guides": {"name": "Practical Guides", "ip": "10.30.20.248", "ct": 142, "color": "#0891b2"}, } -# Cached fleet state โ€” refreshed async -fleet_cache = {"data": {}, "updated": None, "lock": threading.Lock()} +cache = {"data": {}, "ts": None} - -def fetch_site_state(vertical, info): - """Query a single site for health + stats.""" - ip = info["ip"] - state = { - "vertical": vertical, - "name": info["name"], - "ip": ip, - "ct": info["ct"], - "color": info["color"], - "accent": info["accent"], - "alive": False, - "articles": 0, - "views": 0, - "today_views": 0, - "subscribers": 0, - "latency_ms": 0, - "recent_articles": [], - "popular": [], - "error": None, - } - - import requests +def probe(vertical, info): + s = dict(vertical=vertical, name=info["name"], ip=info["ip"], ct=info["ct"], + color=info["color"], alive=False, articles=0, views=0, today=0, + subs=0, latency=0, arts=[], error=None) try: t0 = time.time() - r = requests.get(f"http://{ip}:80/health", timeout=5) - state["latency_ms"] = round((time.time() - t0) * 1000) - + r = requests.get(f"http://{info['ip']}:80/health", timeout=5) + s["latency"] = round((time.time() - t0) * 1000) if r.status_code == 200: - h = r.json() - state["alive"] = True - state["articles"] = h.get("articles", 0) - - # Stats - r2 = requests.get(f"http://{ip}:80/api/stats", timeout=5) + s["alive"] = True + s["articles"] = r.json().get("articles", 0) + r2 = requests.get(f"http://{info['ip']}:80/api/stats", timeout=5) if r2.status_code == 200: - s = r2.json() - state["views"] = s.get("total_views", 0) - state["today_views"] = s.get("today_views", 0) - popular = s.get("popular", []) - state["popular"] = [{"title": a.get("title", "")[:80], "views": a.get("views", 0)} for a in popular[:3]] - - # Recent articles - r3 = requests.get(f"http://{ip}:80/api/articles?limit=3", timeout=5) + d = r2.json() + s["views"] = d.get("total_views", 0) + s["today"] = d.get("today_views", 0) + r3 = requests.get(f"http://{info['ip']}:80/api/articles?limit=3", timeout=5) if r3.status_code == 200: - arts = r3.json() - state["recent_articles"] = [ - {"title": a.get("title", "")[:80], "slug": a.get("slug", ""), - "date": a.get("published_at", "")[:10], "words": a.get("word_count", 0)} - for a in arts[:3] - ] - - # Subscriber count - try: - r4 = requests.get(f"http://{ip}:5000/api/subscribers", timeout=3) - if r4.status_code == 200: - state["subscribers"] = r4.json().get("count", 0) - except Exception: - pass - + s["arts"] = [{"t": a.get("title", "")[:65], "s": a.get("slug", ""), + "d": (a.get("published_at") or "")[:10], "w": a.get("word_count", 0)} + for a in r3.json()[:3]] except Exception as e: - state["error"] = str(e)[:80] - - return state + s["error"] = str(e)[:50] + return vertical, s - -def refresh_fleet(): - """Background refresh of all 8 sites.""" +def refresh(): import concurrent.futures - with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor: - futures = {executor.submit(fetch_site_state, v, i): v for v, i in SITES.items()} - results = {} - for future in concurrent.futures.as_completed(futures): - vertical = futures[future] - try: - results[vertical] = future.result(timeout=10) - except Exception: - results[vertical] = {"vertical": vertical, "alive": False, "error": "timeout"} - - with fleet_cache["lock"]: - fleet_cache["data"] = results - fleet_cache["updated"] = datetime.now().isoformat() - + with concurrent.futures.ThreadPoolExecutor(max_workers=8) as ex: + results = dict(ex.map(lambda x: probe(*x), [(v, i) for v, i in SITES.items()])) + cache["data"] = results + cache["ts"] = datetime.now().isoformat() @app.route("/") -def noc(): - """Main NOC dashboard.""" - # Trigger a refresh if stale (>30s) - if fleet_cache["updated"] is None: - refresh_fleet() - elif (datetime.now() - datetime.fromisoformat(fleet_cache["updated"])).seconds > 30: - threading.Thread(target=refresh_fleet, daemon=True).start() - - with fleet_cache["lock"]: - data = dict(fleet_cache["data"]) - updated = fleet_cache["updated"] - - # Compute aggregates - alive = sum(1 for s in data.values() if s.get("alive")) - total_articles = sum(s.get("articles", 0) for s in data.values()) - total_views = sum(s.get("views", 0) for s in data.values()) - total_today = sum(s.get("today_views", 0) for s in data.values()) - total_subs = sum(s.get("subscribers", 0) for s in data.values()) - - sites_html = "" - for vertical, s in data.items(): - status_dot = "๐ŸŸข" if s.get("alive") else "๐Ÿ”ด" - latency = f"{s.get('latency_ms', 0)}ms" - lat_class = "fast" if s.get("latency_ms", 999) < 100 else ("medium" if s.get("latency_ms", 999) < 300 else "slow") - - # Build recent articles list - recent_html = "" - for art in s.get("recent_articles", []): - recent_html += f'
{art["date"]}{art["title"][:65]}{art["words"]}w
' - - if not recent_html: - recent_html = '
No articles yet
' - - sites_html += f""" -
- -
-
-
{s.get('articles', 0)}
-
Articles
-
-
-
{s.get('views', 0)}
-
Views
-
-
-
{s.get('today_views', 0)}
-
Today
-
-
-
{s.get('subscribers', 0)}
-
Subs
-
-
-
{latency}
-
Latency
-
-
-
{(s.get('error') or 'โœ…')[:20]}
-
Status
-
-
-
- - {recent_html} -
-
""" +def home(): + if not cache["ts"] or (datetime.now() - datetime.fromisoformat(cache["ts"])).seconds > 30: + threading.Thread(target=refresh, daemon=True).start() + data = cache["data"] or {} + return HTML.format( + alive=sum(1 for s in data.values() if s.get("alive")), + total=len(data), + articles=sum(s.get("articles", 0) for s in data.values()), + views=sum(s.get("views", 0) for s in data.values()), + today=sum(s.get("today", 0) for s in data.values()), + subs=sum(s.get("subs", 0) for s in data.values()), + updated=(cache["ts"] or "")[:19], + cards=build_cards(data), + ) - html = NOC_TEMPLATE - html = html.replace('__ALIVE__', str(alive)) - html = html.replace('__TOTAL__', str(len(data))) - html = html.replace('__TOTAL_ARTICLES__', str(total_articles)) - html = html.replace('__TOTAL_VIEWS__', str(total_views)) - html = html.replace('__TOTAL_TODAY__', str(total_today)) - html = html.replace('__TOTAL_SUBS__', str(total_subs)) - html = html.replace('__UPDATED__', str(updated[:19] if updated else "never")) - html = html.replace('__SITES_HTML__', sites_html) - return html +def build_cards(data): + out = [] + for v, s in data.items(): + dot = "up" if s["alive"] else "down" + lat = s["latency"] + lc = "lat-ok" if lat < 80 else ("lat-warn" if lat < 250 else "lat-bad") + err = (s.get("error") or "OK")[:25] + arts = "" + for a in s.get("arts", []): + arts += f'
{a["d"]}{a["t"]}{a["w"]}w
' + if not arts: + arts = '
No articles yet
' + + out.append(f'''
+
+
+
{s['name']}
+
{v}.thetempleofdoom.com
+
+ ๐Ÿ–ฅ {s['ip']}:80 +
+
+
{s['articles']}
Articles
+
{s['views']}
Views
+
{s['today']}
Today
+
{s['subs']}
Subs
+
{lat}ms
Latency
+
{err}
Status
+
+
+
Latest Articles
+ {arts} +
+
''') + return "\n".join(out) @app.route("/api/fleet") def api_fleet(): - """JSON fleet state.""" - refresh_fleet() - with fleet_cache["lock"]: - return jsonify({ - "updated": fleet_cache["updated"], - "sites": fleet_cache["data"], - }) - + refresh() + return jsonify({"updated": cache["ts"], "sites": cache["data"]}) @app.route("/api/refresh", methods=["POST"]) def api_refresh(): - refresh_fleet() - return jsonify({"status": "refreshed", "updated": fleet_cache["updated"]}) - + refresh() + return jsonify({"status": "ok"}) @app.route("/health") def health(): - return jsonify({"status": "ok"}) + return jsonify({"status": "ok", "ts": cache["ts"]}) - -# โ”€โ”€โ”€ Template โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -NOC_TEMPLATE = """ +# โ”€โ”€ Template โ”€โ”€ +HTML = """ - - - NOC โ€” Publisher Fleet Monitor - - + + +Publisher Fleet + + + -
- -
- ๐ŸŸข __ALIVE__/__TOTAL__ online - ๐Ÿ• __UPDATED__ - ๐Ÿ”„ auto-refresh 60s -
-
- -
-
__TOTAL_ARTICLES__
Total Articles
-
__TOTAL_VIEWS__
Total Views
-
__TOTAL_TODAY__
Today Views
-
__TOTAL_SUBS__
Subscribers
-
__ALIVE__/8
Sites Live
-
6AM
Next Publish
-
- -
- __SITES_HTML__ -
- - +
+
Publisher FleetNOC
+
+ {alive} / {total} online + {updated} + +
+
+
+
{articles}
Articles
+
{views}
Total Views
+
{today}
Today
+
{subs}
Subscribers
+
6 AM
Next Publish
+
{articles}
Network Total
+
+
{cards}
+
Publisher Fleet NOC ยท Auto-refresh 60s ยท Pipeline ยท Kalshi
""" - if __name__ == "__main__": import argparse ap = argparse.ArgumentParser() ap.add_argument("--port", type=int, default=5090) - ap.add_argument("--host", type=str, default="127.0.0.1") - args = ap.parse_args() - - # Initial refresh - refresh_fleet() - - print(f"๐Ÿ–ฅ NOC Dashboard โ†’ http://{args.host}:{args.port}") - app.run(host=args.host, port=args.port, debug=False) + ap.add_argument("--host", default="127.0.0.1") + a = ap.parse_args() + print(f"NOC โ†’ http://{a.host}:{a.port}") + refresh() + app.run(host=a.host, port=a.port, debug=False)