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
150
backend/modules/loader.go
Normal file
150
backend/modules/loader.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue