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
|
1456
|
||||||
97787
|
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 asyncio
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
import sqlite3
|
||||||
|
from contextlib import contextmanager
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
@@ -22,18 +23,13 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
# ─── Config ───────────────────────────────────────────────────────────────────
|
# ─── Config ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
CONFIG_FILE = Path(os.getenv("CONFIG_FILE", "data/vps.json"))
|
DB_FILE = Path(os.getenv("DB_FILE", "data/monitor.db"))
|
||||||
USERS_FILE = Path(os.getenv("USERS_FILE", "data/users.json"))
|
|
||||||
SECRET_FILE = Path(os.getenv("SECRET_FILE", "data/.jwt_secret"))
|
SECRET_FILE = Path(os.getenv("SECRET_FILE", "data/.jwt_secret"))
|
||||||
AGENT_TIMEOUT = int(os.getenv("AGENT_TIMEOUT", "5"))
|
AGENT_TIMEOUT = int(os.getenv("AGENT_TIMEOUT", "5"))
|
||||||
JWT_ALGORITHM = "HS256"
|
JWT_ALGORITHM = "HS256"
|
||||||
JWT_EXPIRE_MIN = int(os.getenv("JWT_EXPIRE_MINUTES", "1440")) # 24 h
|
JWT_EXPIRE_MIN = int(os.getenv("JWT_EXPIRE_MINUTES", "1440")) # 24 h
|
||||||
|
|
||||||
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
DB_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("[]")
|
|
||||||
|
|
||||||
# Clé JWT : env var > fichier persisté > génération + sauvegarde
|
# Clé JWT : env var > fichier persisté > génération + sauvegarde
|
||||||
def _load_jwt_secret() -> str:
|
def _load_jwt_secret() -> str:
|
||||||
@@ -76,22 +72,78 @@ class LoginRequest(BaseModel):
|
|||||||
password: str
|
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 ──────────────────────────────────────────────────────────────
|
# ─── 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]:
|
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:
|
def add_user(user: dict) -> None:
|
||||||
USERS_FILE.write_text(json.dumps(data, indent=2))
|
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 ─────────────────────────────────────────────────────────────
|
# ─── Auth helpers ─────────────────────────────────────────────────────────────
|
||||||
@@ -189,8 +241,7 @@ def auth_status():
|
|||||||
@app.post("/api/auth/register", status_code=201)
|
@app.post("/api/auth/register", status_code=201)
|
||||||
def register(body: RegisterRequest):
|
def register(body: RegisterRequest):
|
||||||
"""Enregistre le premier utilisateur (admin). Fermé ensuite."""
|
"""Enregistre le premier utilisateur (admin). Fermé ensuite."""
|
||||||
users = load_users()
|
if len(load_users()) > 0:
|
||||||
if len(users) > 0:
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=403,
|
status_code=403,
|
||||||
detail="L'enregistrement public est désactivé. Seul l'admin peut créer des comptes."
|
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(),
|
"password": _bcrypt.hashpw(body.password.encode(), _bcrypt.gensalt()).decode(),
|
||||||
"role": "admin",
|
"role": "admin",
|
||||||
}
|
}
|
||||||
users.append(user)
|
add_user(user)
|
||||||
save_users(users)
|
|
||||||
token = create_token(user["username"], user["role"])
|
token = create_token(user["username"], user["role"])
|
||||||
return {"access_token": token, "token_type": "bearer", "role": 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)
|
@app.post("/api/vps", status_code=201)
|
||||||
def add_vps(vps: VpsConfig, _: Annotated[dict, Depends(get_current_user)]):
|
def add_vps(vps: VpsConfig, _: Annotated[dict, Depends(get_current_user)]):
|
||||||
"""Ajoute un nouveau VPS."""
|
"""Ajoute un nouveau VPS."""
|
||||||
data = load_vps()
|
if any(v["id"] == vps.id for v in load_vps()):
|
||||||
if any(v["id"] == vps.id for v in data):
|
|
||||||
raise HTTPException(status_code=409, detail="Un VPS avec cet ID existe déjà")
|
raise HTTPException(status_code=409, detail="Un VPS avec cet ID existe déjà")
|
||||||
data.append(vps.model_dump())
|
insert_vps(vps.model_dump())
|
||||||
save_vps(data)
|
|
||||||
return {"status": "ok", "id": vps.id}
|
return {"status": "ok", "id": vps.id}
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/vps/{vps_id}")
|
@app.delete("/api/vps/{vps_id}")
|
||||||
def delete_vps(vps_id: str, _: Annotated[dict, Depends(get_current_user)]):
|
def delete_vps(vps_id: str, _: Annotated[dict, Depends(get_current_user)]):
|
||||||
"""Supprime un VPS de la configuration."""
|
"""Supprime un VPS de la configuration."""
|
||||||
data = load_vps()
|
if not remove_vps(vps_id):
|
||||||
filtered = [v for v in data if v["id"] != vps_id]
|
|
||||||
if len(filtered) == len(data):
|
|
||||||
raise HTTPException(status_code=404, detail="VPS introuvable")
|
raise HTTPException(status_code=404, detail="VPS introuvable")
|
||||||
save_vps(filtered)
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
0
vps-monitor/data/.gitkeep
Normal file
0
vps-monitor/data/.gitkeep
Normal file
@@ -4,7 +4,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend/data:/app/data
|
- ./data:/app/data
|
||||||
env_file: ./backend/.env
|
env_file: ./backend/.env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user