feat: add VPS Monitor backend and frontend services
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:
jeanotx32
2026-05-18 22:31:36 -04:00
parent f83f8f97fa
commit cf0b3f0acf
28 changed files with 1601 additions and 16 deletions

View File

@@ -0,0 +1,2 @@
AGENT_API_KEY=changeme-please
AGENT_PORT=8001

109
vps-monitor/agent/agent.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
VPS Monitor Agent — à déployer sur chaque VPS.
Expose une API REST utilisée par le backend central pour interroger les conteneurs Docker.
"""
import os
from datetime import datetime, timezone
import docker
from docker.errors import DockerException, NotFound
from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import APIKeyHeader
# ─── Config ───────────────────────────────────────────────────────────────────
API_KEY = os.getenv("AGENT_API_KEY", "changeme-please")
AGENT_PORT = int(os.getenv("AGENT_PORT", "8001"))
# ─── App ──────────────────────────────────────────────────────────────────────
app = FastAPI(title="VPS Monitor Agent", version="1.0.0", docs_url=None, redoc_url=None)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=True)
def require_api_key(key: str = Security(api_key_header)) -> None:
if key != API_KEY:
raise HTTPException(status_code=403, detail="Clé API invalide")
def get_docker_client():
try:
return docker.from_env()
except DockerException as e:
raise HTTPException(status_code=503, detail=f"Docker inaccessible : {e}")
# ─── Routes ───────────────────────────────────────────────────────────────────
@app.get("/health")
def health():
"""Vérification de disponibilité — sans authentification."""
return {"status": "ok", "timestamp": datetime.now(timezone.utc).isoformat()}
@app.get("/containers")
def list_containers(_: None = Depends(require_api_key)):
"""Retourne tous les conteneurs (actifs et arrêtés)."""
client = get_docker_client()
result = []
for c in client.containers.list(all=True):
image_tag = c.image.tags[0] if c.image.tags else c.image.short_id
result.append({
"id": c.short_id,
"name": c.name.lstrip("/"),
"status": c.status,
"image": image_tag,
"created": c.attrs.get("Created", ""),
"compose_project": c.labels.get("com.docker.compose.project", ""),
"compose_service": c.labels.get("com.docker.compose.service", ""),
"ports": {
host: [{"HostIp": b["HostIp"], "HostPort": b["HostPort"]} for b in bindings]
for host, bindings in (c.ports or {}).items()
if bindings
},
})
return sorted(result, key=lambda x: x["name"])
@app.get("/containers/{container_id}/logs")
def get_logs(container_id: str, lines: int = 100, _: None = Depends(require_api_key)):
"""Retourne les N dernières lignes de logs d'un conteneur."""
client = get_docker_client()
try:
c = client.containers.get(container_id)
raw = c.logs(tail=lines, timestamps=True)
return {"logs": raw.decode("utf-8", errors="replace")}
except NotFound:
raise HTTPException(status_code=404, detail="Conteneur introuvable")
@app.post("/containers/{container_id}/action")
def container_action(container_id: str, action: str, _: None = Depends(require_api_key)):
"""Effectue une action sur un conteneur : start, stop, restart."""
if action not in ("start", "stop", "restart"):
raise HTTPException(status_code=400, detail=f"Action invalide : {action}")
client = get_docker_client()
try:
c = client.containers.get(container_id)
getattr(c, action)()
return {"status": "ok", "action": action, "container": container_id}
except NotFound:
raise HTTPException(status_code=404, detail="Conteneur introuvable")
# ─── Entrée ───────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=AGENT_PORT)

View File

@@ -0,0 +1,16 @@
[Unit]
Description=VPS Monitor Agent
After=docker.service
Requires=docker.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/vps-monitor-agent
EnvironmentFile=/opt/vps-monitor-agent/.env
ExecStart=/opt/vps-monitor-agent/venv/bin/uvicorn agent:app --host 0.0.0.0 --port 8001
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,3 @@
fastapi>=0.111.0
uvicorn[standard]>=0.30.0
docker>=7.1.0