{value}
@@ -234,6 +262,7 @@ export default function App() { onLogs={openLogs} onDelete={handleDeleteVps} onUpdate={handleUpdate} + onEdit={setEditVps} /> ))}diff --git a/vps-monitor/.pids b/vps-monitor/.pids
index b291492..cb8ea29 100644
--- a/vps-monitor/.pids
+++ b/vps-monitor/.pids
@@ -1,2 +1,2 @@
-2317
-2396
+5812
+5870
diff --git a/vps-monitor/backend/__pycache__/main.cpython-313.pyc b/vps-monitor/backend/__pycache__/main.cpython-313.pyc
index 0d87bb4..99e4c74 100644
Binary files a/vps-monitor/backend/__pycache__/main.cpython-313.pyc and b/vps-monitor/backend/__pycache__/main.cpython-313.pyc differ
diff --git a/vps-monitor/backend/main.py b/vps-monitor/backend/main.py
index 7674604..4fdba3c 100644
--- a/vps-monitor/backend/main.py
+++ b/vps-monitor/backend/main.py
@@ -5,6 +5,7 @@ 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
@@ -56,12 +57,22 @@ class VpsConfig(BaseModel):
port: int = 8001
api_key: str
description: str = ""
+ tags: list[str] = []
class ActionRequest(BaseModel):
action: str # start | stop | restart
+class VpsUpdateRequest(BaseModel):
+ name: str
+ host: str
+ port: int = 8001
+ api_key: str = "" # vide = conserver la clé existante
+ description: str = ""
+ tags: list[str] = []
+
+
class ComposeUpdateRequest(BaseModel):
project: str
@@ -108,9 +119,15 @@ def init_db() -> None:
host TEXT NOT NULL,
port INTEGER NOT NULL DEFAULT 8001,
api_key TEXT NOT NULL,
- description TEXT NOT NULL DEFAULT ''
+ description TEXT NOT NULL DEFAULT '',
+ tags TEXT NOT NULL DEFAULT '[]'
)
""")
+ # Migration : ajoute la colonne tags si elle n'existe pas encore
+ try:
+ conn.execute("ALTER TABLE vps ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'")
+ except Exception:
+ pass # colonne déjà présente
init_db()
@@ -133,14 +150,21 @@ def add_user(user: dict) -> None:
def load_vps() -> list[dict]:
with get_db() as conn:
- return [dict(r) for r in conn.execute("SELECT * FROM vps").fetchall()]
+ rows = [dict(r) for r in conn.execute("SELECT * FROM vps").fetchall()]
+ for row in rows:
+ try:
+ row["tags"] = json.loads(row.get("tags") or "[]")
+ except Exception:
+ row["tags"] = []
+ return rows
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", "")),
+ "INSERT INTO vps (id, name, host, port, api_key, description, tags) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ (vps["id"], vps["name"], vps["host"], vps["port"], vps["api_key"],
+ vps.get("description", ""), json.dumps(vps.get("tags", []))),
)
@@ -150,6 +174,16 @@ def remove_vps(vps_id: str) -> bool:
return cur.rowcount > 0
+def update_vps(vps_id: str, data: dict) -> bool:
+ with get_db() as conn:
+ cur = conn.execute(
+ "UPDATE vps SET name=?, host=?, port=?, api_key=?, description=?, tags=? WHERE id=?",
+ (data["name"], data["host"], data["port"], data["api_key"],
+ data["description"], json.dumps(data.get("tags", [])), vps_id),
+ )
+ return cur.rowcount > 0
+
+
# ─── Auth helpers ─────────────────────────────────────────────────────────────
def create_token(username: str, role: str) -> str:
@@ -229,6 +263,7 @@ async def fetch_vps_status(vps: dict) -> dict:
"online": True,
"containers": containers_res,
"system": system,
+ "tags": vps.get("tags", []),
}
except Exception as e:
return {
@@ -240,6 +275,7 @@ async def fetch_vps_status(vps: dict) -> dict:
"error": str(e),
"containers": [],
"system": None,
+ "tags": vps.get("tags", []),
}
@@ -293,7 +329,8 @@ def me(current_user: Annotated[dict, Depends(get_current_user)]):
def list_vps(_: Annotated[dict, Depends(get_current_user)]):
"""Liste les VPS configurés (sans les clés API)."""
return [
- {"id": v["id"], "name": v["name"], "host": v["host"], "description": v.get("description", "")}
+ {"id": v["id"], "name": v["name"], "host": v["host"],
+ "description": v.get("description", ""), "tags": v.get("tags", [])}
for v in load_vps()
]
@@ -315,6 +352,24 @@ def delete_vps(vps_id: str, _: Annotated[dict, Depends(get_current_user)]):
return {"status": "ok"}
+@app.put("/api/vps/{vps_id}")
+def edit_vps(vps_id: str, body: VpsUpdateRequest, _: Annotated[dict, Depends(get_current_user)]):
+ """Met à jour les paramètres d'un VPS (name, host, port, api_key, description)."""
+ existing = next((v for v in load_vps() if v["id"] == vps_id), None)
+ if not existing:
+ raise HTTPException(status_code=404, detail="VPS introuvable")
+ data = {
+ "name": body.name,
+ "host": body.host,
+ "port": body.port,
+ "api_key": body.api_key.strip() or existing["api_key"],
+ "description": body.description,
+ "tags": body.tags,
+ }
+ update_vps(vps_id, data)
+ return {"status": "ok"}
+
+
@app.get("/api/status")
async def all_status(_: Annotated[dict, Depends(get_current_user)]):
"""Retourne l'état de tous les VPS en parallèle."""
diff --git a/vps-monitor/frontend/src/App.jsx b/vps-monitor/frontend/src/App.jsx
index 7cfd995..e3763be 100644
--- a/vps-monitor/frontend/src/App.jsx
+++ b/vps-monitor/frontend/src/App.jsx
@@ -1,12 +1,20 @@
import { useState, useEffect, useCallback } from 'react'
-import { fetchAllStatus, containerAction, addVps, deleteVps, fetchLogs, authStatus, getToken, setToken, composeUpdate } from './api/client'
-import Header from './components/Header'
-import VpsCard from './components/VpsCard'
-import LogsModal from './components/LogsModal'
-import AddVpsModal from './components/AddVpsModal'
-import LoginPage from './components/LoginPage'
+import { fetchAllStatus, containerAction, addVps, deleteVps, fetchLogs, authStatus, getToken, setToken, composeUpdate, updateVps } from './api/client'
+import Header from './components/Header'
+import VpsCard from './components/VpsCard'
+import LogsModal from './components/LogsModal'
+import AddVpsModal from './components/AddVpsModal'
+import EditVpsModal from './components/EditVpsModal'
+import LoginPage from './components/LoginPage'
-const REFRESH_INTERVAL = 30_000
+const INTERVAL_OPTIONS = [
+ { label: '10 s', value: 10_000 },
+ { label: '30 s', value: 30_000 },
+ { label: '1 min', value: 60_000 },
+ { label: '2 min', value: 120_000 },
+ { label: '5 min', value: 300_000 },
+ { label: 'Off', value: 0 },
+]
export default function App() {
const [token, setTokenState] = useState(() => getToken())
@@ -23,6 +31,16 @@ export default function App() {
const [logsContent, setLogsContent] = useState('')
const [logsLoading, setLogsLoading] = useState(false)
const [showAddVps, setShowAddVps] = useState(false)
+ const [editVps, setEditVps] = useState(null) // objet vps à éditer
+ const [refreshInterval, setRefreshInterval] = useState(() => {
+ const stored = localStorage.getItem('refreshInterval')
+ return stored ? parseInt(stored, 10) : 30_000
+ })
+
+ const handleIntervalChange = (val) => {
+ setRefreshInterval(val)
+ localStorage.setItem('refreshInterval', val)
+ }
const [updateModal, setUpdateModal] = useState(null) // { vpsId, project }
const [updateContent, setUpdateContent] = useState('')
@@ -85,9 +103,10 @@ export default function App() {
useEffect(() => {
if (!token) return
refresh()
- const id = setInterval(() => refresh(), REFRESH_INTERVAL)
+ if (!refreshInterval) return
+ const id = setInterval(() => refresh(), refreshInterval)
return () => clearInterval(id)
- }, [refresh, token])
+ }, [refresh, token, refreshInterval])
// Extrait le username du token stocké au rechargement de page
useEffect(() => {
@@ -145,6 +164,12 @@ export default function App() {
await refresh(true)
}
+ const handleEditVps = async (vpsId, data) => {
+ await updateVps(vpsId, data)
+ setEditVps(null)
+ await refresh(true)
+ }
+
// Attente vérification auth
if (!authChecked) return null
@@ -172,6 +197,9 @@ export default function App() {
refreshing={refreshing}
username={username}
onLogout={handleLogout}
+ refreshInterval={refreshInterval}
+ onIntervalChange={handleIntervalChange}
+ intervalOptions={INTERVAL_OPTIONS}
/>
{value}
Entrée ou virgule pour valider
+