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:
enzo 2026-03-20 21:08:53 +01:00
commit 5dbcb1df07
66 changed files with 10370 additions and 0 deletions

View 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
}