From bb5a7005a71abb6a2be15b846f73ed0f77733e65 Mon Sep 17 00:00:00 2001 From: jeanotx32 Date: Mon, 18 May 2026 23:14:26 -0400 Subject: [PATCH] bla --- .gitignore | 17 +++ vps-monitor/.pids | 4 +- .../backend/__pycache__/main.cpython-313.pyc | Bin 18270 -> 20400 bytes vps-monitor/backend/data/vps.json | 11 +- vps-monitor/backend/main.py | 107 +++++++++++++----- vps-monitor/data/.gitkeep | 0 vps-monitor/docker-compose.yml | 2 +- 7 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 .gitignore create mode 100644 vps-monitor/data/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3559644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +.env + +# Node +node_modules/ +dist/ +.DS_Store + +# Data persistante — SQLite & secrets (ne pas committer) +vps-monitor/data/*.db +vps-monitor/data/.jwt_secret + +# Anciens fichiers JSON de données +vps-monitor/backend/data/ diff --git a/vps-monitor/.pids b/vps-monitor/.pids index dbff357..f21dac2 100644 --- a/vps-monitor/.pids +++ b/vps-monitor/.pids @@ -1,2 +1,2 @@ -97732 -97787 +1456 +1540 diff --git a/vps-monitor/backend/__pycache__/main.cpython-313.pyc b/vps-monitor/backend/__pycache__/main.cpython-313.pyc index 076327c66012488cfc35724498e8a59e09b18eb4..9bf1e4b935df32e14e0dc6ecb79e5b2a899c0eaa 100644 GIT binary patch delta 7584 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 N$uaZXo{j?c{{UQkOkw~4 delta 5521 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 F{|Bw1(#rq< diff --git a/vps-monitor/backend/data/vps.json b/vps-monitor/backend/data/vps.json index 0637a08..c2f6c65 100644 --- a/vps-monitor/backend/data/vps.json +++ b/vps-monitor/backend/data/vps.json @@ -1 +1,10 @@ -[] \ No newline at end of file +[ + { + "id": "test-1", + "name": "test", + "host": "45.145.167.66", + "port": 8001, + "api_key": "1fbbcb4998f99ab74c88e5076355e99531a76eb7c2e34b82318b7c334900a848", + "description": "" + } +] \ No newline at end of file diff --git a/vps-monitor/backend/main.py b/vps-monitor/backend/main.py index 191e8fe..8b29a70 100644 --- a/vps-monitor/backend/main.py +++ b/vps-monitor/backend/main.py @@ -5,9 +5,10 @@ Agrège les données de tous les agents et expose une API REST pour le frontend. """ import asyncio -import json import os import secrets +import sqlite3 +from contextlib import contextmanager from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Annotated @@ -22,18 +23,13 @@ from pydantic import BaseModel # ─── Config ─────────────────────────────────────────────────────────────────── -CONFIG_FILE = Path(os.getenv("CONFIG_FILE", "data/vps.json")) -USERS_FILE = Path(os.getenv("USERS_FILE", "data/users.json")) +DB_FILE = Path(os.getenv("DB_FILE", "data/monitor.db")) SECRET_FILE = Path(os.getenv("SECRET_FILE", "data/.jwt_secret")) AGENT_TIMEOUT = int(os.getenv("AGENT_TIMEOUT", "5")) JWT_ALGORITHM = "HS256" JWT_EXPIRE_MIN = int(os.getenv("JWT_EXPIRE_MINUTES", "1440")) # 24 h -CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) -if not CONFIG_FILE.exists(): - CONFIG_FILE.write_text("[]") -if not USERS_FILE.exists(): - USERS_FILE.write_text("[]") +DB_FILE.parent.mkdir(parents=True, exist_ok=True) # Clé JWT : env var > fichier persisté > génération + sauvegarde def _load_jwt_secret() -> str: @@ -76,22 +72,78 @@ class LoginRequest(BaseModel): password: str +# ─── SQLite ─────────────────────────────────────────────────────────────────── + +@contextmanager +def get_db(): + conn = sqlite3.connect(DB_FILE, check_same_thread=False) + conn.row_factory = sqlite3.Row + try: + yield conn + conn.commit() + except Exception: + conn.rollback() + raise + finally: + conn.close() + + +def init_db() -> None: + with get_db() as conn: + conn.execute(""" + CREATE TABLE IF NOT EXISTS users ( + username TEXT PRIMARY KEY, + password TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'admin' + ) + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS vps ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + host TEXT NOT NULL, + port INTEGER NOT NULL DEFAULT 8001, + api_key TEXT NOT NULL, + description TEXT NOT NULL DEFAULT '' + ) + """) + + +init_db() + + # ─── Persistance ────────────────────────────────────────────────────────────── -def load_vps() -> list[dict]: - return json.loads(CONFIG_FILE.read_text()) - - -def save_vps(data: list[dict]) -> None: - CONFIG_FILE.write_text(json.dumps(data, indent=2)) - - def load_users() -> list[dict]: - return json.loads(USERS_FILE.read_text()) + with get_db() as conn: + return [dict(r) for r in conn.execute("SELECT * FROM users").fetchall()] -def save_users(data: list[dict]) -> None: - USERS_FILE.write_text(json.dumps(data, indent=2)) +def add_user(user: dict) -> None: + with get_db() as conn: + conn.execute( + "INSERT INTO users (username, password, role) VALUES (?, ?, ?)", + (user["username"], user["password"], user["role"]), + ) + + +def load_vps() -> list[dict]: + with get_db() as conn: + return [dict(r) for r in conn.execute("SELECT * FROM vps").fetchall()] + + +def insert_vps(vps: dict) -> None: + with get_db() as conn: + conn.execute( + "INSERT INTO vps (id, name, host, port, api_key, description) VALUES (?, ?, ?, ?, ?, ?)", + (vps["id"], vps["name"], vps["host"], vps["port"], vps["api_key"], vps.get("description", "")), + ) + + +def remove_vps(vps_id: str) -> bool: + with get_db() as conn: + cur = conn.execute("DELETE FROM vps WHERE id = ?", (vps_id,)) + return cur.rowcount > 0 # ─── Auth helpers ───────────────────────────────────────────────────────────── @@ -189,8 +241,7 @@ def auth_status(): @app.post("/api/auth/register", status_code=201) def register(body: RegisterRequest): """Enregistre le premier utilisateur (admin). Fermé ensuite.""" - users = load_users() - if len(users) > 0: + if len(load_users()) > 0: raise HTTPException( status_code=403, detail="L'enregistrement public est désactivé. Seul l'admin peut créer des comptes." @@ -202,8 +253,7 @@ def register(body: RegisterRequest): "password": _bcrypt.hashpw(body.password.encode(), _bcrypt.gensalt()).decode(), "role": "admin", } - users.append(user) - save_users(users) + add_user(user) token = create_token(user["username"], user["role"]) return {"access_token": token, "token_type": "bearer", "role": user["role"]} @@ -238,22 +288,17 @@ def list_vps(_: Annotated[dict, Depends(get_current_user)]): @app.post("/api/vps", status_code=201) def add_vps(vps: VpsConfig, _: Annotated[dict, Depends(get_current_user)]): """Ajoute un nouveau VPS.""" - data = load_vps() - if any(v["id"] == vps.id for v in data): + if any(v["id"] == vps.id for v in load_vps()): raise HTTPException(status_code=409, detail="Un VPS avec cet ID existe déjà") - data.append(vps.model_dump()) - save_vps(data) + insert_vps(vps.model_dump()) return {"status": "ok", "id": vps.id} @app.delete("/api/vps/{vps_id}") def delete_vps(vps_id: str, _: Annotated[dict, Depends(get_current_user)]): """Supprime un VPS de la configuration.""" - data = load_vps() - filtered = [v for v in data if v["id"] != vps_id] - if len(filtered) == len(data): + if not remove_vps(vps_id): raise HTTPException(status_code=404, detail="VPS introuvable") - save_vps(filtered) return {"status": "ok"} diff --git a/vps-monitor/data/.gitkeep b/vps-monitor/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/vps-monitor/docker-compose.yml b/vps-monitor/docker-compose.yml index 7bf1ea1..f65a791 100644 --- a/vps-monitor/docker-compose.yml +++ b/vps-monitor/docker-compose.yml @@ -4,7 +4,7 @@ services: ports: - "8000:8000" volumes: - - ./backend/data:/app/data + - ./data:/app/data env_file: ./backend/.env restart: unless-stopped