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:
171
vps-monitor/frontend/src/App.jsx
Normal file
171
vps-monitor/frontend/src/App.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user