diff --git a/backend/internal/api/updates.go b/backend/internal/api/updates.go index 5b4ad55..6b7f128 100644 --- a/backend/internal/api/updates.go +++ b/backend/internal/api/updates.go @@ -8,6 +8,8 @@ import ( "log" "math/rand" "net/http" + "strconv" + "strings" "time" "git.geronzi.fr/proxmoxPanel/core/backend/internal/audit" @@ -38,6 +40,143 @@ func NewUpdatesHandler(database *db.DB, sshPool *ssh.Pool, hub *websocket.Hub, a } } +// sshCredentials récupère les credentials SSH depuis la configuration SQLite. +func (h *UpdatesHandler) sshCredentials() (host, user, pass string, err error) { + host, _, _ = h.db.GetSetting("ssh_host") + user, _, _ = h.db.GetSetting("ssh_username") + encPass, _, _ := h.db.GetSetting("ssh_password") + if encPass != "" { + pass, err = h.encryptor.Decrypt(encPass) + if err != nil { + return "", "", "", fmt.Errorf("impossible de déchiffrer le mot de passe SSH") + } + } + if host == "" || user == "" || pass == "" { + return "", "", "", fmt.Errorf("SSH non configuré") + } + return host, user, pass, nil +} + +// GetTargets retourne la liste des cibles disponibles : host Proxmox + tous les LXC. +// GET /api/updates/targets +func (h *UpdatesHandler) GetTargets(w http.ResponseWriter, r *http.Request) { + sshHost, sshUser, sshPass, err := h.sshCredentials() + if err != nil { + JSONError(w, err.Error(), http.StatusServiceUnavailable) + return + } + + type Target struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + VMID int `json:"vmid,omitempty"` + } + + targets := []Target{ + {ID: "host", Name: "Proxmox Host", Status: "running"}, + } + + output, err := h.sshPool.RunCommand(sshHost, sshUser, sshPass, "pct list 2>/dev/null") + if err == nil { + for _, line := range strings.Split(output, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + fields := strings.Fields(line) + if len(fields) < 2 || fields[0] == "VMID" { + continue + } + vmid, parseErr := strconv.Atoi(fields[0]) + if parseErr != nil { + continue + } + status := fields[1] + name := fields[0] // fallback VMID si pas de nom + if len(fields) >= 3 { + name = fields[len(fields)-1] + } + targets = append(targets, Target{ + ID: fmt.Sprintf("lxc:%d", vmid), + Name: name, + Status: status, + VMID: vmid, + }) + } + } + + JSONResponse(w, http.StatusOK, targets) +} + +// GetPackages retourne la liste des paquets pouvant être mis à jour pour une cible. +// GET /api/updates/packages?target=host|lxc:100 +func (h *UpdatesHandler) GetPackages(w http.ResponseWriter, r *http.Request) { + target := r.URL.Query().Get("target") + if target == "" { + JSONError(w, "Paramètre 'target' requis", http.StatusBadRequest) + return + } + + sshHost, sshUser, sshPass, err := h.sshCredentials() + if err != nil { + JSONError(w, err.Error(), http.StatusServiceUnavailable) + return + } + + var command string + switch { + case target == "host": + command = "apt list --upgradable 2>/dev/null" + case len(target) > 4 && target[:4] == "lxc:": + lxcID := target[4:] + command = fmt.Sprintf("pct exec %s -- apt list --upgradable 2>/dev/null", lxcID) + default: + JSONError(w, "Cible invalide", http.StatusBadRequest) + return + } + + output, err := h.sshPool.RunCommand(sshHost, sshUser, sshPass, command) + if err != nil { + log.Printf("[updates/packages] Erreur SSH pour %s : %v", target, err) + JSONError(w, "Erreur SSH : "+err.Error(), http.StatusBadGateway) + return + } + + JSONResponse(w, http.StatusOK, parseAptPackages(output)) +} + +// parseAptPackages analyse la sortie de `apt list --upgradable`. +func parseAptPackages(output string) []map[string]string { + var packages []map[string]string + for _, line := range strings.Split(output, "\n") { + line = strings.TrimSpace(line) + if !strings.Contains(line, "[upgradable from:") { + continue + } + // Format : name/repo version arch [upgradable from: old_version] + parts := strings.Fields(line) + if len(parts) < 2 { + continue + } + name := strings.SplitN(parts[0], "/", 2)[0] + version := parts[1] + oldVersion := "" + if idx := strings.Index(line, "upgradable from: "); idx >= 0 { + oldVersion = strings.TrimRight(line[idx+len("upgradable from: "):], "] ") + } + packages = append(packages, map[string]string{ + "name": name, + "version": version, + "old_version": oldVersion, + }) + } + if packages == nil { + packages = []map[string]string{} + } + return packages +} + // RunUpdate lance une mise à jour apt sur la cible spécifiée. // POST /api/updates/run // Body: { "target": "host" | "lxc:100" | "all" } diff --git a/backend/main.go b/backend/main.go index f64b611..d4242da 100644 --- a/backend/main.go +++ b/backend/main.go @@ -146,6 +146,8 @@ func main() { r.Group(func(r chi.Router) { r.Use(api.RequireAdmin) r.Post("/api/updates/run", updatesHandler.RunUpdate) + r.Get("/api/updates/targets", updatesHandler.GetTargets) + r.Get("/api/updates/packages", updatesHandler.GetPackages) }) r.Get("/api/updates/history", updatesHandler.GetHistory) diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index dccbc0c..82f6ab4 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -78,7 +78,7 @@ "disconnected": "Disconnected" }, "updates": { - "desc": "Run apt updates on the host or LXC containers.", + "desc": "Check and run apt updates on the host or LXC containers.", "selectTarget": "Select target", "targetHost": "Proxmox Host", "targetAll": "All LXC", @@ -87,6 +87,16 @@ "output": "Output", "history": "History", "noHistory": "No updates performed", + "checkUpdates": "Check", + "checkAll": "Check all", + "updateTarget": "Update", + "updateAll": "Update all", + "packagesToUpdate": "package(s) to update", + "upToDate": "Up to date", + "notChecked": "Not checked", + "checking": "Checking...", + "loadingTargets": "Loading targets...", + "stopped": "Stopped", "status": { "running": "Running", "success": "Success", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 37a4b17..edc3e34 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -78,7 +78,7 @@ "disconnected": "Déconnecté" }, "updates": { - "desc": "Lancez des mises à jour apt sur le host ou les LXC.", + "desc": "Vérifiez et lancez des mises à jour apt sur le host ou les LXC.", "selectTarget": "Sélectionner la cible", "targetHost": "Host Proxmox", "targetAll": "Tous les LXC", @@ -87,6 +87,16 @@ "output": "Sortie", "history": "Historique", "noHistory": "Aucune mise à jour effectuée", + "checkUpdates": "Vérifier", + "checkAll": "Tout vérifier", + "updateTarget": "Mettre à jour", + "updateAll": "Tout mettre à jour", + "packagesToUpdate": "paquet(s) à mettre à jour", + "upToDate": "À jour", + "notChecked": "Non vérifié", + "checking": "Vérification...", + "loadingTargets": "Chargement des cibles...", + "stopped": "Arrêté", "status": { "running": "En cours", "success": "Succès", diff --git a/frontend/src/views/Updates.vue b/frontend/src/views/Updates.vue index 076fc91..a8eb7ab 100644 --- a/frontend/src/views/Updates.vue +++ b/frontend/src/views/Updates.vue @@ -1,47 +1,109 @@