108 lines
4.9 KiB
Python
108 lines
4.9 KiB
Python
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())
|