// Handlers pour l'API Proxmox : liste LXC/VM, démarrage/arrêt, WebSocket temps réel. package api import ( "log" "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 == "" { log.Printf("[proxmox/init] Client non initialisé — proxmox_url=%q token_empty=%v", url, encryptedToken == "") return } token, err := h.encryptor.Decrypt(encryptedToken) if err != nil { log.Printf("[proxmox/init] Impossible de déchiffrer le token : %v", err) return } log.Printf("[proxmox/init] Client initialisé — url=%s", url) 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 { log.Printf("[proxmox/resources] Erreur API : %v", err) 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 }