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:
2
vps-monitor/agent/.env.example
Normal file
2
vps-monitor/agent/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
AGENT_API_KEY=changeme-please
|
||||
AGENT_PORT=8001
|
||||
109
vps-monitor/agent/agent.py
Normal file
109
vps-monitor/agent/agent.py
Normal 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)
|
||||
16
vps-monitor/agent/agent.service
Normal file
16
vps-monitor/agent/agent.service
Normal 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
|
||||
3
vps-monitor/agent/requirements.txt
Normal file
3
vps-monitor/agent/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
fastapi>=0.111.0
|
||||
uvicorn[standard]>=0.30.0
|
||||
docker>=7.1.0
|
||||
Reference in New Issue
Block a user