init: FastAPI SSE Baserow polling
This commit is contained in:
parent
8e3d4ae5eb
commit
c638f8eada
|
|
@ -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())
|
||||
Loading…
Reference in New Issue