From c7cc18101a738bbc09867c242df2ca8540820eb0 Mon Sep 17 00:00:00 2001 From: jeanotx32 Date: Mon, 18 May 2026 23:48:50 -0400 Subject: [PATCH] Feat : Various features --- vps-monitor/.pids | 4 +- .../backend/__pycache__/main.cpython-313.pyc | Bin 22890 -> 26091 bytes vps-monitor/backend/main.py | 65 ++++++++++- vps-monitor/frontend/src/App.jsx | 58 +++++++-- vps-monitor/frontend/src/api/client.js | 9 ++ .../frontend/src/components/AddVpsModal.jsx | 9 +- .../frontend/src/components/EditVpsModal.jsx | 110 ++++++++++++++++++ .../frontend/src/components/Header.jsx | 19 ++- .../frontend/src/components/TagInput.jsx | 73 ++++++++++++ .../frontend/src/components/VpsCard.jsx | 27 ++++- vps-monitor/start.sh | 1 + 11 files changed, 353 insertions(+), 22 deletions(-) create mode 100644 vps-monitor/frontend/src/components/EditVpsModal.jsx create mode 100644 vps-monitor/frontend/src/components/TagInput.jsx 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 0d87bb46cf548ae79d22f941e845fb5405866705..99e4c741d7696efa9aa697df0c2bda71c460eb18 100644 GIT binary patch delta 8032 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 delta 5488 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 G$o~P1UeRj+ 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} />
@@ -189,7 +217,7 @@ export default function App() { {[ { label: 'VPS en ligne', value: `${totalOnline}/${vpsList.length}`, color: 'text-emerald-400' }, { label: 'Conteneurs actifs', value: `${totalRunning}/${totalContainers}`, color: 'text-indigo-400' }, - { label: 'Actualisation auto', value: '30s', color: 'text-gray-400' }, + { label: 'Actualisation auto', value: INTERVAL_OPTIONS.find(o => o.value === refreshInterval)?.label ?? 'Off', color: 'text-gray-400' }, ].map(({ label, value, color }) => (

{value}

@@ -234,6 +262,7 @@ export default function App() { onLogs={openLogs} onDelete={handleDeleteVps} onUpdate={handleUpdate} + onEdit={setEditVps} /> ))}
@@ -267,6 +296,15 @@ export default function App() { onClose={() => setShowAddVps(false)} /> )} + + {/* Modal édition VPS */} + {editVps && ( + setEditVps(null)} + /> + )} ) } diff --git a/vps-monitor/frontend/src/api/client.js b/vps-monitor/frontend/src/api/client.js index ffcc613..a56ef78 100644 --- a/vps-monitor/frontend/src/api/client.js +++ b/vps-monitor/frontend/src/api/client.js @@ -99,3 +99,12 @@ export async function composeUpdate(vpsId, project) { }) return handleResponse(res) } + +export async function updateVps(vpsId, data) { + const res = await fetch(`${BASE}/vps/${vpsId}`, { + method: 'PUT', + headers: authHeaders(), + body: JSON.stringify(data), + }) + return handleResponse(res) +} diff --git a/vps-monitor/frontend/src/components/AddVpsModal.jsx b/vps-monitor/frontend/src/components/AddVpsModal.jsx index 59cecef..63eea07 100644 --- a/vps-monitor/frontend/src/components/AddVpsModal.jsx +++ b/vps-monitor/frontend/src/components/AddVpsModal.jsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react' import { X } from 'lucide-react' +import TagInput from './TagInput' -const DEFAULTS = { id: '', name: '', host: '', port: '8001', api_key: '', description: '' } +const DEFAULTS = { id: '', name: '', host: '', port: '8001', api_key: '', description: '', tags: [] } const FIELDS = [ { key: 'name', label: 'Nom affiché', placeholder: 'Mon VPS 1', required: true, type: 'text' }, @@ -73,6 +74,12 @@ export default function AddVpsModal({ onSave, onClose }) {

)} +
+ + setForm(f => ({ ...f, tags }))} /> +

Entrée ou virgule pour valider

+
+
+
+ +
+ {FIELDS.map(({ key, label, placeholder, required, type }) => ( +
+ + +
+ ))} + + {error && ( +

+ {error} +

+ )} + +
+ + setForm(f => ({ ...f, tags }))} /> +

Entrée ou virgule pour valider

+
+ +
+ + +
+
+ + + ) +} diff --git a/vps-monitor/frontend/src/components/Header.jsx b/vps-monitor/frontend/src/components/Header.jsx index 1d561fa..ce6114e 100644 --- a/vps-monitor/frontend/src/components/Header.jsx +++ b/vps-monitor/frontend/src/components/Header.jsx @@ -1,6 +1,6 @@ -import { Monitor, LogOut } from 'lucide-react' +import { Monitor, LogOut, Timer } from 'lucide-react' -export default function Header({ lastUpdate, onRefresh, onAddVps, refreshing, username, onLogout }) { +export default function Header({ lastUpdate, onRefresh, onAddVps, refreshing, username, onLogout, refreshInterval, onIntervalChange, intervalOptions }) { return (
@@ -17,6 +17,21 @@ export default function Header({ lastUpdate, onRefresh, onAddVps, refreshing, us
+ {/* Sélecteur d'intervalle */} +
+ + +
+ + + ))} + setInput(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={tags.length === 0 ? 'Ajouter un tag…' : ''} + className="flex-1 min-w-[120px] bg-transparent text-sm outline-none placeholder-gray-600" + /> +
+ ) +} diff --git a/vps-monitor/frontend/src/components/VpsCard.jsx b/vps-monitor/frontend/src/components/VpsCard.jsx index f6723f3..72f0590 100644 --- a/vps-monitor/frontend/src/components/VpsCard.jsx +++ b/vps-monitor/frontend/src/components/VpsCard.jsx @@ -1,6 +1,7 @@ -import { Server, Wifi, WifiOff, Trash2, ChevronDown, ChevronUp, RefreshCw, Cpu, MemoryStick, ArrowUp, ArrowDown } from 'lucide-react' +import { Server, Wifi, WifiOff, Trash2, ChevronDown, ChevronUp, RefreshCw, Cpu, MemoryStick, ArrowUp, ArrowDown, Pencil } from 'lucide-react' import { useState } from 'react' import ContainerRow from './ContainerRow' +import { tagColor } from './TagInput' function formatBytes(bps) { if (bps < 1024) return `${bps.toFixed(0)} B/s` @@ -13,7 +14,7 @@ function formatRam(bytes) { return `${(bytes / 1024 ** 3).toFixed(1)} GB` } -export default function VpsCard({ vps, onAction, onLogs, onDelete, onUpdate }) { +export default function VpsCard({ vps, onAction, onLogs, onDelete, onUpdate, onEdit }) { const [collapsed, setCollapsed] = useState(false) const [updatingProject, setUpdatingProject] = useState(null) @@ -61,6 +62,14 @@ export default function VpsCard({ vps, onAction, onLogs, onDelete, onUpdate }) { {collapsed ? : } + +