- router: tryRefresh() au premier chargement → plus besoin de se reconnecter après F5 (user restauré depuis le cookie refresh) - migration 003: terminal marqué is_core=1 + is_enabled=1 - proxmox.go: logs pour diagnostiquer l'erreur 502 (visible dans Paramètres → Logs) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
222 lines
5.9 KiB
Go
222 lines
5.9 KiB
Go
// 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
|
|
}
|