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

150
backend/modules/loader.go Normal file
View file

@ -0,0 +1,150 @@
// Package modules — Loader de modules.
// Découvre les modules disponibles, vérifie leur état en DB, et les initialise si activés.
// Un module désactivé ne fait appel à aucune de ses méthodes Register().
package modules
import (
"database/sql"
"fmt"
"log"
"net/http"
)
// Loader charge et gère les modules actifs.
type Loader struct {
db *sql.DB
registry *coreRegistry
modules []Module
}
// NewLoader crée un Loader avec le router et la DB fournis.
func NewLoader(db *sql.DB) *Loader {
return &Loader{
db: db,
registry: newCoreRegistry(db),
}
}
// RegisterModule enregistre un module disponible (appelé à l'init, depuis main.go).
// Le module sera initialisé seulement s'il est activé en base.
func (l *Loader) RegisterModule(m Module) {
l.modules = append(l.modules, m)
}
// LoadActive charge et initialise tous les modules activés en base de données.
func (l *Loader) LoadActive() error {
for _, m := range l.modules {
enabled, err := l.isEnabled(m.ID())
if err != nil {
return fmt.Errorf("vérification module %s : %w", m.ID(), err)
}
if !enabled {
log.Printf("Module %s : désactivé, ignoré", m.ID())
continue
}
log.Printf("Module %s : chargement...", m.ID())
if err := m.Register(l.registry); err != nil {
return fmt.Errorf("initialisation module %s : %w", m.ID(), err)
}
log.Printf("Module %s : chargé avec succès", m.ID())
}
return nil
}
// isEnabled vérifie en base de données si un module est activé.
func (l *Loader) isEnabled(id string) (bool, error) {
var enabled int
err := l.db.QueryRow(`SELECT is_enabled FROM modules WHERE id = ?`, id).Scan(&enabled)
if err == sql.ErrNoRows {
return false, nil // Module inconnu = désactivé
}
return enabled == 1, err
}
// Registry retourne le registry partagé (pour accès par le serveur HTTP).
func (l *Loader) Registry() *coreRegistry {
return l.registry
}
// ---- Implémentation interne du Registry ----
// RouteEntry décrit une route HTTP enregistrée par un module.
type RouteEntry struct {
Method string
Path string
Handler http.HandlerFunc
RequireAdmin bool
}
type migrationEntry struct {
version int
sql string
fn MigrationFn
}
type translationEntry struct {
lang string
keys map[string]string
}
// coreRegistry implémente l'interface Registry.
type coreRegistry struct {
db *sql.DB
routes []RouteEntry
wsChannels map[string]WSHandler
widgets []WidgetDef
settingsTabs []SettingsTabDef
migrations []migrationEntry
translations []translationEntry
}
func newCoreRegistry(db *sql.DB) *coreRegistry {
return &coreRegistry{
db: db,
wsChannels: make(map[string]WSHandler),
}
}
func (r *coreRegistry) RegisterRoute(method, path string, handler http.HandlerFunc, requireAdmin bool) {
r.routes = append(r.routes, RouteEntry{method, path, handler, requireAdmin})
}
func (r *coreRegistry) RegisterWSChannel(channel string, handler WSHandler) {
r.wsChannels[channel] = handler
}
func (r *coreRegistry) RegisterWidget(widget WidgetDef) {
r.widgets = append(r.widgets, widget)
}
func (r *coreRegistry) RegisterSettingsTab(tab SettingsTabDef) {
r.settingsTabs = append(r.settingsTabs, tab)
}
func (r *coreRegistry) RegisterTranslations(lang string, keys map[string]string) {
r.translations = append(r.translations, translationEntry{lang, keys})
}
func (r *coreRegistry) RegisterMigration(version int, sqlStr string, fn MigrationFn) {
r.migrations = append(r.migrations, migrationEntry{version, sqlStr, fn})
}
func (r *coreRegistry) DB() *sql.DB {
return r.db
}
// GetRoutes retourne les routes enregistrées par les modules.
func (r *coreRegistry) GetRoutes() []RouteEntry {
return r.routes
}
// GetWidgets retourne les types de widgets disponibles.
func (r *coreRegistry) GetWidgets() []WidgetDef {
return r.widgets
}
// GetSettingsTabs retourne les onglets de paramètres des modules.
func (r *coreRegistry) GetSettingsTabs() []SettingsTabDef {
return r.settingsTabs
}