Feat : Lot of stuff
All checks were successful
Build and Push Docker Images / docker (push) Successful in 51s

This commit is contained in:
jeanotx32
2026-06-02 18:55:11 -04:00
parent daf68d98fa
commit f2e5a24b37
9 changed files with 655 additions and 151 deletions

View File

@@ -26,7 +26,8 @@ from pydantic import BaseModel
# ─── Config ───────────────────────────────────────────────────────────────────
DB_FILE = Path(os.getenv("DB_FILE", "data/monitor.db"))
DB_FILE = Path(os.getenv("DB_FILE", "data/monitor.db"))
EXPECTED_AGENT_VERSION = os.getenv("EXPECTED_AGENT_VERSION", "1.1.0")
# ─── Ring buffer de stats (en mémoire) ───────────────────────────────────────
_STATS_MAX_POINTS = 120 # 10 min à 5 s d'intervalle
@@ -102,6 +103,13 @@ class SettingUpdateRequest(BaseModel):
value: str
class PurgeRequest(BaseModel):
table: str # 'vps_stats' | 'login_logs' | 'all'
period: str # 'last_24h' | 'last_7d' | 'last_30d' | 'all' | 'custom'
from_ts: int | None = None # epoch seconds, utilisé si period == 'custom'
to_ts: int | None = None # epoch seconds, utilisé si period == 'custom'
# ─── SQLite ───────────────────────────────────────────────────────────────────
@contextmanager
@@ -360,27 +368,40 @@ async def fetch_vps_status(vps: dict) -> dict:
except Exception:
system = None
# Version de l'agent (endpoint sans auth)
try:
version_res = await agent_get(vps, "/version")
agent_version = version_res.get("version", "unknown")
except Exception:
agent_version = "unknown"
return {
"id": vps["id"],
"name": vps["name"],
"host": vps["host"],
"description": vps.get("description", ""),
"online": True,
"containers": containers_res,
"system": system,
"tags": vps.get("tags", []),
"id": vps["id"],
"name": vps["name"],
"host": vps["host"],
"description": vps.get("description", ""),
"online": True,
"containers": containers_res,
"system": system,
"tags": vps.get("tags", []),
"agent_version": agent_version,
"expected_agent_version": EXPECTED_AGENT_VERSION,
"agent_up_to_date": agent_version == EXPECTED_AGENT_VERSION,
}
except Exception as e:
return {
"id": vps["id"],
"name": vps["name"],
"host": vps["host"],
"description": vps.get("description", ""),
"online": False,
"error": str(e),
"containers": [],
"system": None,
"tags": vps.get("tags", []),
"id": vps["id"],
"name": vps["name"],
"host": vps["host"],
"description": vps.get("description", ""),
"online": False,
"error": str(e),
"containers": [],
"system": None,
"tags": vps.get("tags", []),
"agent_version": None,
"expected_agent_version": EXPECTED_AGENT_VERSION,
"agent_up_to_date": False,
}
@@ -591,6 +612,74 @@ def admin_list_users(_: Annotated[dict, Depends(require_admin)]):
]
@app.get("/api/admin/db/info")
def admin_db_info(_: Annotated[dict, Depends(require_admin)]):
"""Retourne des informations sur le contenu de la base de données."""
with get_db() as conn:
def _table_info(table: str) -> dict:
row = conn.execute(
f"SELECT COUNT(*) AS cnt, MIN(ts) AS oldest, MAX(ts) AS newest FROM {table}"
).fetchone()
return {
"count": row["cnt"],
"oldest_ts": row["oldest"],
"newest_ts": row["newest"],
}
return {
"vps_stats": _table_info("vps_stats"),
"login_logs": _table_info("login_logs"),
}
_ALLOWED_TABLES = frozenset({"vps_stats", "login_logs"})
_ALLOWED_PERIODS = frozenset({"last_24h", "last_7d", "last_30d", "all", "custom"})
@app.delete("/api/admin/db/purge")
def admin_db_purge(body: PurgeRequest, _: Annotated[dict, Depends(require_admin)]):
"""Supprime des entrées de la base de données selon la table et la période choisies."""
if body.table not in _ALLOWED_TABLES and body.table != "all":
raise HTTPException(status_code=400, detail="Table inconnue")
if body.period not in _ALLOWED_PERIODS:
raise HTTPException(status_code=400, detail="Période inconnue")
tables = list(_ALLOWED_TABLES) if body.table == "all" else [body.table]
now = int(time.time())
period_cutoff = {
"last_24h": now - 24 * 3600,
"last_7d": now - 7 * 24 * 3600,
"last_30d": now - 30 * 24 * 3600,
}
deleted: dict[str, int] = {}
with get_db() as conn:
for tbl in tables:
if body.period == "all":
cur = conn.execute(f"DELETE FROM {tbl}") # nosec tbl validated above
elif body.period == "custom":
if body.from_ts is None or body.to_ts is None:
raise HTTPException(
status_code=400,
detail="Période personnalisée : from_ts et to_ts sont requis",
)
if body.from_ts > body.to_ts:
raise HTTPException(status_code=400, detail="from_ts doit être ≤ to_ts")
cur = conn.execute(
f"DELETE FROM {tbl} WHERE ts >= ? AND ts <= ?", # nosec
(body.from_ts, body.to_ts),
)
else:
cutoff = period_cutoff[body.period]
cur = conn.execute(
f"DELETE FROM {tbl} WHERE ts >= ?", # nosec
(cutoff,),
)
deleted[tbl] = cur.rowcount
return {"deleted": deleted, "status": "ok"}
# ─── Routes VPS ───────────────────────────────────────────────────────────────
@app.get("/api/vps")
@@ -756,3 +845,18 @@ async def compose_update(
return await r.json()
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
@app.post("/api/vps/{vps_id}/agent/update")
async def agent_self_update(
vps_id: str,
_: Annotated[dict, Depends(get_current_user)] = None,
):
"""Déclenche la mise à jour de l'agent sur le VPS."""
vps = next((v for v in load_vps() if v["id"] == vps_id), None)
if not vps:
raise HTTPException(status_code=404, detail="VPS introuvable")
try:
return await agent_post(vps, "/self-update")
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))