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,96 @@
import { useState, useEffect } from 'react'
import { X } from 'lucide-react'
const DEFAULTS = { id: '', name: '', host: '', port: '8001', api_key: '', description: '' }
const FIELDS = [
{ key: 'name', label: 'Nom affiché', placeholder: 'Mon VPS 1', required: true, type: 'text' },
{ key: 'id', label: 'Identifiant unique', placeholder: 'vps-1', required: true, type: 'text' },
{ key: 'host', label: 'IP ou hostname', placeholder: '192.168.1.10', required: true, type: 'text' },
{ key: 'port', label: 'Port agent', placeholder: '8001', required: true, type: 'number' },
{ key: 'api_key', label: 'Clé API agent', placeholder: '••••••••', required: true, type: 'password' },
{ key: 'description', label: 'Description', placeholder: 'Optionnel', required: false, type: 'text' },
]
export default function AddVpsModal({ onSave, onClose }) {
const [form, setForm] = useState(DEFAULTS)
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
useEffect(() => {
const handler = (e) => { if (e.key === 'Escape') onClose() }
window.addEventListener('keydown', handler)
return () => window.removeEventListener('keydown', handler)
}, [onClose])
const set = (key) => (e) => setForm(f => ({ ...f, [key]: e.target.value }))
const handleSubmit = async (e) => {
e.preventDefault()
setSaving(true)
setError('')
try {
await onSave({ ...form, port: parseInt(form.port, 10) })
} catch (err) {
setError(err.message)
setSaving(false)
}
}
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/75 backdrop-blur-sm"
onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
>
<div className="w-full max-w-md bg-gray-900 border border-gray-700 rounded-xl shadow-2xl">
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-700">
<h3 className="font-semibold text-sm">Ajouter un VPS</h3>
<button onClick={onClose} className="p-1.5 rounded-lg hover:bg-gray-800 text-gray-500 hover:text-gray-200 transition-colors">
<X size={16} />
</button>
</div>
<form onSubmit={handleSubmit} className="p-4 space-y-3">
{FIELDS.map(({ key, label, placeholder, required, type }) => (
<div key={key}>
<label className="block text-xs text-gray-400 mb-1">
{label} {required && <span className="text-red-400">*</span>}
</label>
<input
type={type}
value={form[key]}
onChange={set(key)}
placeholder={placeholder}
required={required}
className="w-full px-3 py-2 rounded-lg bg-gray-800 border border-gray-700 text-sm placeholder-gray-600 focus:outline-none focus:border-indigo-500 transition-colors"
/>
</div>
))}
{error && (
<p className="text-xs text-red-400 bg-red-950/30 border border-red-900/40 rounded-lg px-3 py-2">
{error}
</p>
)}
<div className="flex gap-2 pt-1">
<button
type="button"
onClick={onClose}
className="flex-1 py-2 rounded-lg border border-gray-700 hover:bg-gray-800 text-sm transition-colors"
>
Annuler
</button>
<button
type="submit"
disabled={saving}
className="flex-1 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-sm transition-colors font-medium"
>
{saving ? 'Enregistrement…' : 'Ajouter'}
</button>
</div>
</form>
</div>
</div>
)
}