""" NOC Dashboard โ€” clean, modern fleet monitor for all 8 publisher sites. """ import json, time, threading, requests, sys, os from datetime import datetime from flask import Flask, jsonify # Evolution log sys.path.insert(0, os.path.expanduser("~/auto-publisher/core")) from evolution_log import get_recent_evolutions, get_evolution_stats, log_evolution app = Flask(__name__) SITES = { "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"}, } cache = {"data": {}, "ts": None} 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: # Pre-flush ARP for known problematic IPs if info["ip"] in ("10.30.20.242", "10.30.20.243"): os.system(f"sudo arp -d {info['ip']} 2>/dev/null") t0 = time.time() r = requests.get(f"http://{info['ip']}:80/health", timeout=5) s["latency"] = round((time.time() - t0) * 1000) if r.status_code == 200: s["alive"] = True s["articles"] = r.json().get("articles", 0) except Exception as e: s["error"] = str(e)[:50] # Stale ARP auto-fix for problematic CTs if info["ip"] in ("10.30.20.242", "10.30.20.243"): os.system(f"sudo arp -d {info['ip']} 2>/dev/null") return vertical, s try: r2 = requests.get(f"http://{info['ip']}:80/api/stats", timeout=5) if r2.status_code == 200: d = r2.json() s["views"] = d.get("total_views", 0) s["today"] = d.get("today_views", 0) except Exception: pass try: r3 = requests.get(f"http://{info['ip']}:80/api/articles?limit=3", timeout=5) if r3.status_code == 200: 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: pass return vertical, s def refresh(): import concurrent.futures 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 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 {} evo_stats = get_evolution_stats() evo_recent = get_recent_evolutions(6) evo_html = build_evo_html(evo_recent) 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], evo_total=evo_stats["total"], evo_html=evo_html, cards=build_cards(data), ) 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(): refresh() return jsonify({"updated": cache["ts"], "sites": cache["data"]}) @app.route("/api/refresh", methods=["POST"]) def api_refresh(): refresh() return jsonify({"status": "ok"}) @app.route("/api/evolution") def api_evolution(): return jsonify({ "stats": get_evolution_stats(), "recent": get_recent_evolutions(20), }) @app.route("/health") def health(): return jsonify({"status": "ok", "ts": cache["ts"]}) def build_evo_html(events): if not events: return '
No evolution events yet. First sweep runs at midnight.
' out = [] for e in events: ts = e["timestamp"][:16].replace("T", " ") out.append(f'
{ts}{e["vertical"]}{e["feature"][:80]}{e["status"]}
') return "\n".join(out) # โ”€โ”€ Template โ”€โ”€ HTML = """ Publisher Fleet
Publisher FleetNOC
{alive} / {total} online {updated}
{articles}
Articles
{views}
Total Views
{today}
Today
{subs}
Subscribers
6 AM
Next Publish
{articles}
Network Total
{cards}
๐Ÿ”„ Site Evolution ({evo_total} improvements)
{evo_html}
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", 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)