diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..8e6db1c --- /dev/null +++ b/app/main.py @@ -0,0 +1,107 @@ +import asyncio, json, ssl, time, urllib.request +from datetime import datetime, timezone +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse +from fastapi.staticfiles import StaticFiles +import os + +app = FastAPI(title="Hermes Mission Control Dashboard") + +# ── Config ───────────────────────────────────────────────────────────────── +BR_TOKEN = "52yUWNDZzBNJo31nzwTEzYrrb1yuMlMw" +BR_URL = "http://Baserow/api/database/rows/table/1064/?user_field_names=true&size=50" +BR_HEADER = "baserow.bolbol.tn" +CTX = ssl.create_default_context() + +INSTANCES = { + "hermes-tt": {"label": "Hermes TT", "color": "#3b82f6", "container": "hermes-agent-tt"}, + "hermes-nyora": {"label": "Hermes Nyora", "color": "#22c55e", "container": "hermes-agent-nyora"}, + "hermes-perso": {"label": "Hermes Perso", "color": "#f97316", "container": "hermes-agent-perso"}, +} + +CRONS = [ + {"instance": "hermes-tt", "name": "Brief RLA", "schedule": "Lun-Ven 08h00"}, + {"instance": "hermes-nyora", "name": "Veille IA", "schedule": "Lun-Ven 12h30"}, +] + +# ── Cache ─────────────────────────────────────────────────────────────────── +_cache = {"missions": [], "ts": 0} + +def fetch_missions(): + global _cache + if time.time() - _cache["ts"] < 30: + return _cache["missions"] + try: + req = urllib.request.Request(BR_URL, headers={ + "Authorization": f"Token {BR_TOKEN}", "Host": BR_HEADER}) + with urllib.request.urlopen(req, timeout=8) as r: + data = json.loads(r.read()) + rows = data.get("results", []) + missions = [] + for row in rows: + inst_obj = row.get("Instance") or {} + stat_obj = row.get("Statut") or {} + missions.append({ + "id": row.get("id"), + "mission": row.get("Mission", ""), + "instance": inst_obj.get("value", "") if isinstance(inst_obj, dict) else "", + "statut": stat_obj.get("value", "") if isinstance(stat_obj, dict) else "", + "modele": row.get("Modele", ""), + "debut": (row.get("Debut") or "")[:16], + "fin": (row.get("Fin") or "")[:16], + "cout": row.get("Cout_DT", 0), + "resultat": (row.get("Resultat") or "")[:120], + "actif": row.get("Actif", False), + }) + _cache = {"missions": missions, "ts": time.time()} + return missions + except Exception as e: + return _cache.get("missions", []) + +def build_status(missions): + status = {} + for key, cfg in INSTANCES.items(): + label = cfg["label"] + instance_missions = [m for m in missions if m["instance"] == key] + active = [m for m in instance_missions if m["actif"]] + last = instance_missions[0] if instance_missions else None + status[key] = { + "label": label, + "color": cfg["color"], + "active_count": len(active), + "last_mission": last["mission"] if last else "—", + "last_statut": last["statut"] if last else "—", + "last_time": last["fin"] or last["debut"] if last else "—", + "last_modele": last["modele"] if last else "—", + "total": len(instance_missions), + "current": active[0]["mission"] if active else None, + } + return status + +# ── Routes ────────────────────────────────────────────────────────────────── +@app.get("/api/data") +def api_data(): + missions = fetch_missions() + return {"status": build_status(missions), "missions": missions[:20], "crons": CRONS, "ts": int(time.time())} + +@app.post("/webhook/baserow") +async def webhook(request: Request): + _cache["ts"] = 0 # invalide le cache + return {"ok": True} + +@app.get("/events") +async def sse(request: Request): + async def gen(): + while True: + if await request.is_disconnected(): + break + missions = fetch_missions() + payload = {"status": build_status(missions), "missions": missions[:20], "ts": int(time.time())} + yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n" + await asyncio.sleep(30) + return StreamingResponse(gen(), media_type="text/event-stream", + headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}) + +@app.get("/", response_class=HTMLResponse) +def index(): + return HTMLResponse(open("/app/templates/index.html").read())