feat: initialisation complète du CORE ProxmoxPanel
Backend Go 1.23+ : - API REST + WebSocket (chi, gorilla/websocket) - Authentification PAM via SSH + JWT RS256 - Chiffrement AES-256-GCM pour secrets SQLite - Pool SSH, client Proxmox REST, hub WebSocket pub/sub - Système de modules compilés à initialisation conditionnelle - Audit log, migrations SQLite versionnées Frontend Vue 3 + Vite + TypeScript : - Thème Neumorphism sombre/clair (CSS custom properties) - Wizard d'installation, Dashboard drag-drop, Terminal xterm.js - Toutes les vues CORE + stubs modules optionnels - i18n EN/FR (vue-i18n v11) Infrastructure : - Docker multi-stage (Go → alpine, Node → nginx) - docker-compose.yml, .gitattributes, LICENSE MIT, README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
5dbcb1df07
66 changed files with 10370 additions and 0 deletions
217
backend/internal/api/proxmox.go
Normal file
217
backend/internal/api/proxmox.go
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// Handlers pour l'API Proxmox : liste LXC/VM, démarrage/arrêt, WebSocket temps réel.
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/audit"
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/crypto"
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/db"
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/proxmox"
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/websocket"
|
||||
gorillaws "github.com/gorilla/websocket"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var upgrader = gorillaws.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
// ProxmoxHandler contient les handlers Proxmox.
|
||||
type ProxmoxHandler struct {
|
||||
db *db.DB
|
||||
hub *websocket.Hub
|
||||
auditLogger *audit.Logger
|
||||
encryptor *crypto.Encryptor
|
||||
client *proxmox.Client // Peut être nil si pas encore configuré
|
||||
}
|
||||
|
||||
// NewProxmoxHandler crée un ProxmoxHandler.
|
||||
func NewProxmoxHandler(database *db.DB, hub *websocket.Hub, auditLog *audit.Logger, enc *crypto.Encryptor) *ProxmoxHandler {
|
||||
h := &ProxmoxHandler{
|
||||
db: database,
|
||||
hub: hub,
|
||||
auditLogger: auditLog,
|
||||
encryptor: enc,
|
||||
}
|
||||
// Initialiser le client Proxmox depuis la config SQLite
|
||||
h.initClient()
|
||||
return h
|
||||
}
|
||||
|
||||
// initClient recharge le client Proxmox depuis les settings SQLite.
|
||||
func (h *ProxmoxHandler) initClient() {
|
||||
url, _, _ := h.db.GetSetting("proxmox_url")
|
||||
encryptedToken, _, _ := h.db.GetSetting("proxmox_token")
|
||||
if url == "" || encryptedToken == "" {
|
||||
return
|
||||
}
|
||||
token, err := h.encryptor.Decrypt(encryptedToken)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.client = proxmox.NewClient(url, token)
|
||||
}
|
||||
|
||||
// GetResources retourne la liste de toutes les ressources Proxmox (LXC + VM + nodes).
|
||||
// GET /api/proxmox/resources
|
||||
func (h *ProxmoxHandler) GetResources(w http.ResponseWriter, r *http.Request) {
|
||||
if h.client == nil {
|
||||
h.initClient()
|
||||
}
|
||||
if h.client == nil {
|
||||
JSONError(w, "Proxmox non configuré", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
resources, err := h.client.GetResources("")
|
||||
if err != nil {
|
||||
JSONError(w, "Erreur API Proxmox : "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
JSONResponse(w, http.StatusOK, resources)
|
||||
}
|
||||
|
||||
// GetLXC retourne uniquement les conteneurs LXC.
|
||||
// GET /api/proxmox/lxc
|
||||
func (h *ProxmoxHandler) GetLXC(w http.ResponseWriter, r *http.Request) {
|
||||
if h.client == nil {
|
||||
h.initClient()
|
||||
}
|
||||
if h.client == nil {
|
||||
JSONError(w, "Proxmox non configuré", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
lxcs, err := h.client.GetLXCList()
|
||||
if err != nil {
|
||||
JSONError(w, "Erreur API Proxmox : "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
JSONResponse(w, http.StatusOK, lxcs)
|
||||
}
|
||||
|
||||
// StartLXC démarre un conteneur LXC.
|
||||
// POST /api/proxmox/lxc/{vmid}/start
|
||||
func (h *ProxmoxHandler) StartLXC(w http.ResponseWriter, r *http.Request) {
|
||||
claims := GetClaims(r)
|
||||
vmid, node, err := h.extractVMID(r)
|
||||
if err != nil {
|
||||
JSONError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if h.client == nil {
|
||||
h.initClient()
|
||||
}
|
||||
if h.client == nil {
|
||||
JSONError(w, "Proxmox non configuré", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.client.StartLXC(node, vmid); err != nil {
|
||||
JSONError(w, "Erreur démarrage LXC : "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
h.auditLogger.Log(&claims.UserID, claims.Username, "lxc_start", strconv.Itoa(vmid), nil, clientIP(r))
|
||||
JSONResponse(w, http.StatusOK, map[string]string{"message": "LXC démarré"})
|
||||
}
|
||||
|
||||
// StopLXC arrête un conteneur LXC.
|
||||
// POST /api/proxmox/lxc/{vmid}/stop
|
||||
func (h *ProxmoxHandler) StopLXC(w http.ResponseWriter, r *http.Request) {
|
||||
claims := GetClaims(r)
|
||||
vmid, node, err := h.extractVMID(r)
|
||||
if err != nil {
|
||||
JSONError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if h.client == nil {
|
||||
h.initClient()
|
||||
}
|
||||
|
||||
if err := h.client.StopLXC(node, vmid); err != nil {
|
||||
JSONError(w, "Erreur arrêt LXC : "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
h.auditLogger.Log(&claims.UserID, claims.Username, "lxc_stop", strconv.Itoa(vmid), nil, clientIP(r))
|
||||
JSONResponse(w, http.StatusOK, map[string]string{"message": "LXC arrêté"})
|
||||
}
|
||||
|
||||
// WebSocket retourne un WebSocket qui envoie les mises à jour Proxmox en temps réel.
|
||||
// GET /ws/proxmox
|
||||
func (h *ProxmoxHandler) WebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
claims := GetClaims(r)
|
||||
var userID int64
|
||||
if claims != nil {
|
||||
userID = claims.UserID
|
||||
}
|
||||
|
||||
client := h.hub.NewClient(conn, userID)
|
||||
client.Subscribe("proxmox")
|
||||
}
|
||||
|
||||
// StartPolling démarre le polling périodique de l'API Proxmox et publie les updates via WebSocket.
|
||||
// À appeler au démarrage du serveur.
|
||||
func (h *ProxmoxHandler) StartPolling() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
if h.client == nil {
|
||||
h.initClient()
|
||||
if h.client == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
resources, err := h.client.GetResources("")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
h.hub.Publish("proxmox", "resources_update", resources)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// extractVMID extrait l'ID de VM et le nom du nœud depuis l'URL et les query params.
|
||||
func (h *ProxmoxHandler) extractVMID(r *http.Request) (int, string, error) {
|
||||
vmidStr := chi.URLParam(r, "vmid")
|
||||
vmid, err := strconv.Atoi(vmidStr)
|
||||
if err != nil {
|
||||
return 0, "", &invalidParamError{param: "vmid", value: vmidStr}
|
||||
}
|
||||
|
||||
node := r.URL.Query().Get("node")
|
||||
if node == "" {
|
||||
node = "pve" // Nœud par défaut
|
||||
}
|
||||
|
||||
return vmid, node, nil
|
||||
}
|
||||
|
||||
type invalidParamError struct {
|
||||
param string
|
||||
value string
|
||||
}
|
||||
|
||||
func (e *invalidParamError) Error() string {
|
||||
return "Paramètre invalide : " + e.param + " = " + e.value
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue