diff --git a/vps-monitor/backend/__pycache__/main.cpython-313.pyc b/vps-monitor/backend/__pycache__/main.cpython-313.pyc index 81e3f2e..9ac257c 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 5ab6511..f6da13d 100644 --- a/vps-monitor/backend/main.py +++ b/vps-monitor/backend/main.py @@ -727,6 +727,23 @@ def edit_vps(vps_id: str, body: VpsUpdateRequest, _: Annotated[dict, Depends(get return {"status": "ok"} +@app.get("/api/vps/{vps_id}/export") +def export_vps(vps_id: str, _: Annotated[dict, Depends(get_current_user)]): + """Exporte la configuration complète d'un VPS (y compris la clé API) pour import ailleurs.""" + 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 { + "id": vps["id"], + "name": vps["name"], + "host": vps["host"], + "port": vps["port"], + "api_key": vps["api_key"], + "description": vps.get("description", ""), + "tags": vps.get("tags", []), + } + + @app.get("/api/vps/{vps_id}/stats") def get_vps_stats( vps_id: str, diff --git a/vps-monitor/docker-compose.yml b/vps-monitor/docker-compose.yml index cf05434..c8ba68a 100644 --- a/vps-monitor/docker-compose.yml +++ b/vps-monitor/docker-compose.yml @@ -1,18 +1,21 @@ services: - backend: - image: git.jeanbonapp.com/jeanbon/scriptvps/backend:latest + AppliSurveillance-api: + image: git.jeanbonapp.com/jeanbon/vps-monitor-backend:latest + hostname: backend ports: - - "8000:8000" + - "8020:8000" volumes: - ./data:/app/data - env_file: ./backend/.env + env_file: + - path: ./.env + required: false restart: unless-stopped - frontend: - image: git.jeanbonapp.com/jeanbon/scriptvps/frontend:latest + AppliSurveillance-ui: + image: git.jeanbonapp.com/jeanbon/vps-monitor-frontend:latest ports: - - "3000:80" + - "3020:80" depends_on: - - backend + - AppliSurveillance-api restart: unless-stopped diff --git a/vps-monitor/frontend/src/App.jsx b/vps-monitor/frontend/src/App.jsx index ff9c040..2851778 100644 --- a/vps-monitor/frontend/src/App.jsx +++ b/vps-monitor/frontend/src/App.jsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react' -import { fetchAllStatus, containerAction, addVps, deleteVps, fetchLogs, authStatus, getToken, setToken, composeUpdate, updateVps, updateAgent } from './api/client' +import { fetchAllStatus, containerAction, addVps, deleteVps, fetchLogs, authStatus, getToken, setToken, composeUpdate, updateVps, updateAgent, exportVps } from './api/client' import Header from './components/Header' import VpsCard from './components/VpsCard' import LogsModal from './components/LogsModal' @@ -198,6 +198,11 @@ export default function App() { await refresh(true) } + const handleExportVps = async (vpsId) => { + const config = await exportVps(vpsId) + await navigator.clipboard.writeText(JSON.stringify(config, null, 2)) + } + // Attente vérification auth if (!authChecked) return null @@ -305,6 +310,7 @@ export default function App() { onEdit={setEditVps} onStats={(vpsId, vpsName) => setStatsModal({ vpsId, vpsName })} onUpdateAgent={handleUpdateAgent} + onExport={handleExportVps} /> ))} diff --git a/vps-monitor/frontend/src/api/client.js b/vps-monitor/frontend/src/api/client.js index 310114b..f094eff 100644 --- a/vps-monitor/frontend/src/api/client.js +++ b/vps-monitor/frontend/src/api/client.js @@ -117,6 +117,11 @@ export async function updateVps(vpsId, data) { return handleResponse(res) } +export async function exportVps(vpsId) { + const res = await fetch(`${BASE}/vps/${vpsId}/export`, { headers: authHeaders() }) + return handleResponse(res) +} + export async function fetchVpsStats(vpsId, duration = 600) { const res = await fetch(`${BASE}/vps/${vpsId}/stats?duration=${duration}`, { headers: authHeaders() }) return handleResponse(res) diff --git a/vps-monitor/frontend/src/components/AddVpsModal.jsx b/vps-monitor/frontend/src/components/AddVpsModal.jsx index 63eea07..f348fc9 100644 --- a/vps-monitor/frontend/src/components/AddVpsModal.jsx +++ b/vps-monitor/frontend/src/components/AddVpsModal.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { X } from 'lucide-react' +import { X, Upload } from 'lucide-react' import TagInput from './TagInput' const DEFAULTS = { id: '', name: '', host: '', port: '8001', api_key: '', description: '', tags: [] } @@ -14,9 +14,12 @@ const FIELDS = [ ] export default function AddVpsModal({ onSave, onClose }) { + const [mode, setMode] = useState('manual') // 'manual' | 'import' const [form, setForm] = useState(DEFAULTS) const [saving, setSaving] = useState(false) const [error, setError] = useState('') + const [json, setJson] = useState('') + const [jsonError, setJsonError] = useState('') useEffect(() => { const handler = (e) => { if (e.key === 'Escape') onClose() } @@ -26,6 +29,31 @@ export default function AddVpsModal({ onSave, onClose }) { const set = (key) => (e) => setForm(f => ({ ...f, [key]: e.target.value })) + const handleImportJson = () => { + setJsonError('') + try { + const parsed = JSON.parse(json.trim()) + const required = ['id', 'name', 'host', 'port', 'api_key'] + const missing = required.filter(k => !parsed[k]) + if (missing.length > 0) { + setJsonError(`Champs manquants : ${missing.join(', ')}`) + return + } + setForm({ + id: String(parsed.id), + name: String(parsed.name), + host: String(parsed.host), + port: String(parsed.port), + api_key: String(parsed.api_key), + description: String(parsed.description ?? ''), + tags: Array.isArray(parsed.tags) ? parsed.tags : [], + }) + setMode('manual') + } catch { + setJsonError('JSON invalide. Assurez-vous d\'avoir collé la config exportée.') + } + } + const handleSubmit = async (e) => { e.preventDefault() setSaving(true) @@ -45,58 +73,115 @@ export default function AddVpsModal({ onSave, onClose }) { >
-

Ajouter un VPS

+
+

Ajouter un VPS

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

- {error} + {mode === 'import' ? ( +

+

+ Collez le JSON copié via le bouton Exporter d'une autre instance.

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

Entrée ou virgule pour valider

+