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,171 @@
import { useState, useEffect, useCallback } from 'react'
import { fetchAllStatus, containerAction, addVps, deleteVps, fetchLogs } from './api/client'
import Header from './components/Header'
import VpsCard from './components/VpsCard'
import LogsModal from './components/LogsModal'
import AddVpsModal from './components/AddVpsModal'
const REFRESH_INTERVAL = 30_000
export default function App() {
const [vpsList, setVpsList] = useState([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [error, setError] = useState(null)
const [lastUpdate, setLastUpdate] = useState(null)
const [logsModal, setLogsModal] = useState(null) // { vpsId, containerId, name }
const [logsContent, setLogsContent] = useState('')
const [logsLoading, setLogsLoading] = useState(false)
const [showAddVps, setShowAddVps] = useState(false)
const refresh = useCallback(async (showSpinner = false) => {
if (showSpinner) setRefreshing(true)
try {
const data = await fetchAllStatus()
setVpsList(data)
setLastUpdate(new Date())
setError(null)
} catch (e) {
setError(e.message)
} finally {
setLoading(false)
setRefreshing(false)
}
}, [])
useEffect(() => {
refresh()
const id = setInterval(() => refresh(), REFRESH_INTERVAL)
return () => clearInterval(id)
}, [refresh])
const openLogs = async (vpsId, containerId, name) => {
setLogsModal({ vpsId, containerId, name })
setLogsLoading(true)
setLogsContent('')
try {
const data = await fetchLogs(vpsId, containerId)
setLogsContent(data.logs)
} catch (e) {
setLogsContent(`Erreur lors de la récupération des logs :\n${e.message}`)
} finally {
setLogsLoading(false)
}
}
const handleAction = async (vpsId, containerId, action) => {
await containerAction(vpsId, containerId, action)
await refresh()
}
const handleAddVps = async (formData) => {
await addVps(formData)
setShowAddVps(false)
await refresh(true)
}
const handleDeleteVps = async (vpsId) => {
if (!window.confirm('Supprimer ce VPS de la configuration ?')) return
await deleteVps(vpsId)
await refresh(true)
}
// Statistiques globales
const totalOnline = vpsList.filter(v => v.online).length
const totalContainers = vpsList.reduce((acc, v) => acc + v.containers.length, 0)
const totalRunning = vpsList.reduce((acc, v) => acc + v.containers.filter(c => c.status === 'running').length, 0)
return (
<div className="min-h-screen bg-gray-950 text-gray-100">
<Header
lastUpdate={lastUpdate}
onRefresh={() => refresh(true)}
onAddVps={() => setShowAddVps(true)}
refreshing={refreshing}
/>
<main className="max-w-7xl mx-auto px-4 py-8">
{/* Barre d'erreur backend */}
{error && (
<div className="mb-6 bg-red-950/40 border border-red-800/50 rounded-xl px-4 py-3 text-sm text-red-300">
Impossible de joindre le backend : <span className="font-mono">{error}</span>
</div>
)}
{/* Stats globales */}
{!loading && vpsList.length > 0 && (
<div className="grid grid-cols-3 gap-4 mb-8">
{[
{ 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' },
].map(({ label, value, color }) => (
<div key={label} className="bg-gray-900 border border-gray-800 rounded-xl px-4 py-3">
<p className={`text-2xl font-bold ${color}`}>{value}</p>
<p className="text-xs text-gray-500 mt-0.5">{label}</p>
</div>
))}
</div>
)}
{/* Chargement initial */}
{loading && (
<div className="text-center py-24 text-gray-600">
<svg className="w-8 h-8 animate-spin mx-auto mb-3 text-indigo-500" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Chargement
</div>
)}
{/* Aucun VPS */}
{!loading && vpsList.length === 0 && !error && (
<div className="text-center py-24 text-gray-600">
<p className="text-lg font-medium text-gray-500">Aucun VPS configuré</p>
<p className="text-sm mt-1">Cliquez sur <strong className="text-gray-400">Ajouter un VPS</strong> pour commencer.</p>
<button
onClick={() => setShowAddVps(true)}
className="mt-6 px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-sm transition-colors"
>
Ajouter un VPS
</button>
</div>
)}
{/* Grille de VPS */}
{!loading && vpsList.length > 0 && (
<div className="grid gap-5 lg:grid-cols-2">
{vpsList.map(vps => (
<VpsCard
key={vps.id}
vps={vps}
onAction={handleAction}
onLogs={openLogs}
onDelete={handleDeleteVps}
/>
))}
</div>
)}
</main>
{/* Modal logs */}
{logsModal && (
<LogsModal
name={logsModal.name}
logs={logsContent}
loading={logsLoading}
onClose={() => setLogsModal(null)}
/>
)}
{/* Modal ajout VPS */}
{showAddVps && (
<AddVpsModal
onSave={handleAddVps}
onClose={() => setShowAddVps(false)}
/>
)}
</div>
)
}