feat: add VPS Monitor backend and frontend services
Some checks failed
Build and Push Docker Images / docker (push) Failing after 5s
Some checks failed
Build and Push Docker Images / docker (push) Failing after 5s
- Create systemd service for VPS Monitor agent. - Add FastAPI backend with endpoints for managing VPS configurations and statuses. - Implement Dockerfile for backend service with required dependencies. - Create frontend using React with Vite and Tailwind CSS for styling. - Add API client for communicating with the backend. - Implement components for displaying VPS information and logs. - Set up Docker Compose for orchestrating backend and frontend services. - Add environment configuration files for backend and agent. - Implement CORS support in the backend for frontend communication.
This commit is contained in:
3
vps-monitor/backend/.env.example
Normal file
3
vps-monitor/backend/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
CONFIG_FILE=data/vps.json
|
||||
AGENT_TIMEOUT=5
|
||||
CORS_ORIGINS=http://localhost:5173,http://localhost:3000
|
||||
6
vps-monitor/backend/Dockerfile
Normal file
6
vps-monitor/backend/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
179
vps-monitor/backend/main.py
Normal file
179
vps-monitor/backend/main.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
VPS Monitor Backend — serveur central.
|
||||
Agrège les données de tous les agents et expose une API REST pour le frontend.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
|
||||
# ─── Config ───────────────────────────────────────────────────────────────────
|
||||
|
||||
CONFIG_FILE = Path(os.getenv("CONFIG_FILE", "data/vps.json"))
|
||||
AGENT_TIMEOUT = int(os.getenv("AGENT_TIMEOUT", "5"))
|
||||
|
||||
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not CONFIG_FILE.exists():
|
||||
CONFIG_FILE.write_text("[]")
|
||||
|
||||
# ─── Modèles ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class VpsConfig(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
host: str
|
||||
port: int = 8001
|
||||
api_key: str
|
||||
description: str = ""
|
||||
|
||||
|
||||
class ActionRequest(BaseModel):
|
||||
action: str # start | stop | restart
|
||||
|
||||
|
||||
# ─── 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))
|
||||
|
||||
|
||||
# ─── App ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
app = FastAPI(title="VPS Monitor Backend", version="1.0.0")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=os.getenv("CORS_ORIGINS", "*").split(","),
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# ─── Helpers HTTP ─────────────────────────────────────────────────────────────
|
||||
|
||||
async def agent_get(vps: dict, path: str):
|
||||
url = f"http://{vps['host']}:{vps['port']}{path}"
|
||||
headers = {"X-API-Key": vps["api_key"]}
|
||||
timeout = aiohttp.ClientTimeout(total=AGENT_TIMEOUT)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers, timeout=timeout) as r:
|
||||
r.raise_for_status()
|
||||
return await r.json()
|
||||
|
||||
|
||||
async def agent_post(vps: dict, path: str, payload: dict | None = None):
|
||||
url = f"http://{vps['host']}:{vps['port']}{path}"
|
||||
headers = {"X-API-Key": vps["api_key"]}
|
||||
timeout = aiohttp.ClientTimeout(total=AGENT_TIMEOUT)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, headers=headers, json=payload, timeout=timeout) as r:
|
||||
r.raise_for_status()
|
||||
return await r.json()
|
||||
|
||||
|
||||
async def fetch_vps_status(vps: dict) -> dict:
|
||||
"""Interroge un agent et retourne son état complet."""
|
||||
try:
|
||||
containers = await agent_get(vps, "/containers")
|
||||
return {
|
||||
"id": vps["id"],
|
||||
"name": vps["name"],
|
||||
"host": vps["host"],
|
||||
"description": vps.get("description", ""),
|
||||
"online": True,
|
||||
"containers": containers,
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"id": vps["id"],
|
||||
"name": vps["name"],
|
||||
"host": vps["host"],
|
||||
"description": vps.get("description", ""),
|
||||
"online": False,
|
||||
"error": str(e),
|
||||
"containers": [],
|
||||
}
|
||||
|
||||
|
||||
# ─── Routes VPS ───────────────────────────────────────────────────────────────
|
||||
|
||||
@app.get("/api/vps")
|
||||
def list_vps():
|
||||
"""Liste les VPS configurés (sans les clés API)."""
|
||||
return [
|
||||
{"id": v["id"], "name": v["name"], "host": v["host"], "description": v.get("description", "")}
|
||||
for v in load_vps()
|
||||
]
|
||||
|
||||
|
||||
@app.post("/api/vps", status_code=201)
|
||||
def add_vps(vps: VpsConfig):
|
||||
"""Ajoute un nouveau VPS."""
|
||||
data = 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à")
|
||||
data.append(vps.model_dump())
|
||||
save_vps(data)
|
||||
return {"status": "ok", "id": vps.id}
|
||||
|
||||
|
||||
@app.delete("/api/vps/{vps_id}")
|
||||
def delete_vps(vps_id: str):
|
||||
"""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):
|
||||
raise HTTPException(status_code=404, detail="VPS introuvable")
|
||||
save_vps(filtered)
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/api/status")
|
||||
async def all_status():
|
||||
"""Retourne l'état de tous les VPS en parallèle."""
|
||||
vps_list = load_vps()
|
||||
results = await asyncio.gather(*[fetch_vps_status(v) for v in vps_list])
|
||||
return list(results)
|
||||
|
||||
|
||||
@app.get("/api/vps/{vps_id}/status")
|
||||
async def vps_status(vps_id: str):
|
||||
"""Retourne l'état d'un VPS spécifique."""
|
||||
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")
|
||||
return await fetch_vps_status(vps)
|
||||
|
||||
|
||||
@app.get("/api/vps/{vps_id}/containers/{container_id}/logs")
|
||||
async def container_logs(vps_id: str, container_id: str, lines: int = 100):
|
||||
"""Récupère les logs d'un conteneur via l'agent."""
|
||||
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_get(vps, f"/containers/{container_id}/logs?lines={lines}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/api/vps/{vps_id}/containers/{container_id}/action")
|
||||
async def container_action(vps_id: str, container_id: str, body: ActionRequest):
|
||||
"""Effectue une action sur un conteneur via l'agent."""
|
||||
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, f"/containers/{container_id}/action?action={body.action}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
4
vps-monitor/backend/requirements.txt
Normal file
4
vps-monitor/backend/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fastapi>=0.111.0
|
||||
uvicorn[standard]>=0.30.0
|
||||
aiohttp>=3.9.0
|
||||
pydantic>=2.0.0
|
||||
Reference in New Issue
Block a user