This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -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/
|
||||
@@ -1,2 +1,2 @@
|
||||
97732
|
||||
97787
|
||||
1456
|
||||
1540
|
||||
|
||||
Binary file not shown.
@@ -1 +1,10 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"id": "test-1",
|
||||
"name": "test",
|
||||
"host": "45.145.167.66",
|
||||
"port": 8001,
|
||||
"api_key": "1fbbcb4998f99ab74c88e5076355e99531a76eb7c2e34b82318b7c334900a848",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
@@ -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"}
|
||||
|
||||
|
||||
|
||||
0
vps-monitor/data/.gitkeep
Normal file
0
vps-monitor/data/.gitkeep
Normal file
@@ -4,7 +4,7 @@ services:
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./backend/data:/app/data
|
||||
- ./data:/app/data
|
||||
env_file: ./backend/.env
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
Reference in New Issue
Block a user