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>
150 lines
3.9 KiB
Go
150 lines
3.9 KiB
Go
// 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
|
|
}
|