// Handlers pour la page paramètres : lecture/écriture de la configuration globale. package api import ( "net/http" "git.geronzi.fr/proxmoxPanel/core/backend/internal/audit" "git.geronzi.fr/proxmoxPanel/core/backend/internal/db" "github.com/go-chi/chi/v5" ) // SettingsHandler contient les handlers de configuration. type SettingsHandler struct { db *db.DB auditLogger *audit.Logger } // NewSettingsHandler crée un SettingsHandler. func NewSettingsHandler(database *db.DB, auditLog *audit.Logger) *SettingsHandler { return &SettingsHandler{db: database, auditLogger: auditLog} } // paramètres publics (non-sensibles) accessibles par les admins. var publicSettings = []string{ "instance_name", "public_url", "default_lang", "proxmox_url", "ssh_host", "ssh_username", } // GetAll retourne tous les paramètres publics de l'application. // GET /api/settings func (h *SettingsHandler) GetAll(w http.ResponseWriter, r *http.Request) { result := make(map[string]string) for _, key := range publicSettings { value, _, err := h.db.GetSetting(key) if err == nil { result[key] = value } } JSONResponse(w, http.StatusOK, result) } // UpdateSetting met à jour un paramètre spécifique. // PUT /api/settings/{key} // Body: { "value": "..." } func (h *SettingsHandler) UpdateSetting(w http.ResponseWriter, r *http.Request) { claims := GetClaims(r) key := chi.URLParam(r, "key") if key == "" { JSONError(w, "Clé de paramètre manquante", http.StatusBadRequest) return } // Vérifier que la clé est modifiable allowed := false for _, k := range publicSettings { if k == key { allowed = true break } } if !allowed { JSONError(w, "Paramètre non modifiable via l'API", http.StatusForbidden) return } var body struct { Value string `json:"value"` } if err := decodeJSON(r, &body); err != nil { JSONError(w, "Corps de requête invalide", http.StatusBadRequest) return } if err := h.db.SetSetting(key, body.Value, false); err != nil { JSONError(w, "Erreur sauvegarde paramètre", http.StatusInternalServerError) return } h.auditLogger.Log(&claims.UserID, claims.Username, "setting_update", key, map[string]string{"key": key}, clientIP(r)) JSONResponse(w, http.StatusOK, map[string]string{"message": "Paramètre mis à jour"}) } // GetModules retourne la liste de tous les modules et leur état. // GET /api/modules func (h *SettingsHandler) GetModules(w http.ResponseWriter, r *http.Request) { rows, err := h.db.Query(` SELECT id, name, description, version, is_core, is_enabled, installed_at FROM modules ORDER BY is_core DESC, name ASC `) if err != nil { JSONError(w, "Erreur lecture modules", http.StatusInternalServerError) return } defer rows.Close() type module struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Version string `json:"version"` IsCore bool `json:"is_core"` IsEnabled bool `json:"is_enabled"` InstalledAt *string `json:"installed_at,omitempty"` } var modules []module for rows.Next() { var m module var isCore, isEnabled int var installedAt *string rows.Scan(&m.ID, &m.Name, &m.Description, &m.Version, &isCore, &isEnabled, &installedAt) m.IsCore = isCore == 1 m.IsEnabled = isEnabled == 1 m.InstalledAt = installedAt modules = append(modules, m) } JSONResponse(w, http.StatusOK, modules) } // EnableModule active un module. // POST /api/modules/{id}/enable func (h *SettingsHandler) EnableModule(w http.ResponseWriter, r *http.Request) { claims := GetClaims(r) id := chi.URLParam(r, "id") result, err := h.db.Exec(`UPDATE modules SET is_enabled = 1 WHERE id = ?`, id) if err != nil { JSONError(w, "Erreur activation module", http.StatusInternalServerError) return } n, _ := result.RowsAffected() if n == 0 { JSONError(w, "Module introuvable", http.StatusNotFound) return } h.auditLogger.Log(&claims.UserID, claims.Username, "module_enable", id, nil, clientIP(r)) JSONResponse(w, http.StatusOK, map[string]string{"message": "Module activé (redémarrage requis pour prendre effet)"}) } // DisableModule désactive un module (ne peut pas désactiver les modules CORE). // POST /api/modules/{id}/disable func (h *SettingsHandler) DisableModule(w http.ResponseWriter, r *http.Request) { claims := GetClaims(r) id := chi.URLParam(r, "id") // Vérifier que ce n'est pas un module CORE var isCore int if err := h.db.QueryRow(`SELECT is_core FROM modules WHERE id = ?`, id).Scan(&isCore); err != nil { JSONError(w, "Module introuvable", http.StatusNotFound) return } if isCore == 1 { JSONError(w, "Les modules CORE ne peuvent pas être désactivés", http.StatusForbidden) return } h.db.Exec(`UPDATE modules SET is_enabled = 0 WHERE id = ?`, id) h.auditLogger.Log(&claims.UserID, claims.Username, "module_disable", id, nil, clientIP(r)) JSONResponse(w, http.StatusOK, map[string]string{"message": "Module désactivé"}) } // GetAuditLog retourne le journal d'audit paginé. // GET /api/settings/audit func (h *SettingsHandler) GetAuditLog(w http.ResponseWriter, r *http.Request) { rows, err := h.db.Query(` SELECT id, username, action, resource, details, ip, created_at FROM audit_log ORDER BY created_at DESC LIMIT 100 `) if err != nil { JSONError(w, "Erreur lecture audit", http.StatusInternalServerError) return } defer rows.Close() type entry struct { ID int64 `json:"id"` Username string `json:"username"` Action string `json:"action"` Resource *string `json:"resource,omitempty"` Details *string `json:"details,omitempty"` IP *string `json:"ip,omitempty"` CreatedAt string `json:"created_at"` } var entries []entry for rows.Next() { var e entry var resource, details, ip *string rows.Scan(&e.ID, &e.Username, &e.Action, &resource, &details, &ip, &e.CreatedAt) e.Resource = resource e.Details = details e.IP = ip entries = append(entries, e) } JSONResponse(w, http.StatusOK, entries) }