fix: store 502, refresh button, rebuild UX pour modules
- GetRegistryModules: endpoint /api/v1/orgs/{org}/repos + timeout 10s
répond toujours HTTP 200 avec {modules, error} au lieu de 502
- InstallRegistryModule: essaie branche master puis main pour module.json
- Ajouter FORGEJO_URL / FORGEJO_ORG dans docker-compose.yml
- Frontend: bouton rafraîchir store + affichage erreur
- Frontend: bannière rebuild en cours + bannière rebuild terminé
- Frontend: polling /api/health toutes les 3s après rebuild/restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a61f805cd0
commit
22a5fed8cc
4 changed files with 157 additions and 39 deletions
|
|
@ -2,11 +2,14 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/audit"
|
||||
"git.geronzi.fr/proxmoxPanel/core/backend/internal/crypto"
|
||||
|
|
@ -316,13 +319,24 @@ type RegistryModule struct {
|
|||
Installed bool `json:"installed"`
|
||||
}
|
||||
|
||||
// GetRegistryModules liste les repos de l'organisation proxmoxPanel sur Forgejo.
|
||||
// registryResp est la réponse unifiée de /api/registry/modules.
|
||||
type registryResp struct {
|
||||
Modules []RegistryModule `json:"modules"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// forgejoClient est un client HTTP avec timeout pour l'API Forgejo.
|
||||
var forgejoClient = &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
// GetRegistryModules liste les repos de l'organisation sur Forgejo.
|
||||
// Utilise GET /api/v1/orgs/{org}/repos (retourne un tableau JSON direct).
|
||||
// Répond toujours 200 — l'erreur éventuelle est dans le champ "error".
|
||||
// GET /api/registry/modules
|
||||
func (h *SettingsHandler) GetRegistryModules(w http.ResponseWriter, r *http.Request) {
|
||||
// Récupérer les modules déjà installés en DB
|
||||
rows, err := h.db.Query(`SELECT id FROM modules`)
|
||||
if err != nil {
|
||||
JSONError(w, "Erreur lecture modules", http.StatusInternalServerError)
|
||||
JSONResponse(w, http.StatusOK, registryResp{Modules: []RegistryModule{}, Error: "Erreur lecture DB : " + err.Error()})
|
||||
return
|
||||
}
|
||||
installed := make(map[string]bool)
|
||||
|
|
@ -333,37 +347,53 @@ func (h *SettingsHandler) GetRegistryModules(w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
rows.Close()
|
||||
|
||||
// Appel à l'API Forgejo
|
||||
resp, err := http.Get("https://git.geronzi.fr/api/v1/repos/search?q=&owner=proxmoxPanel&limit=50")
|
||||
// Base URL et organisation configurables via variables d'environnement
|
||||
forgejoURL := envOr("FORGEJO_URL", "https://git.geronzi.fr")
|
||||
forgejoOrg := envOr("FORGEJO_ORG", "proxmoxPanel")
|
||||
|
||||
// Appel à l'API Forgejo : /api/v1/orgs/{org}/repos retourne un tableau JSON direct
|
||||
apiURL := fmt.Sprintf("%s/api/v1/orgs/%s/repos?limit=50", forgejoURL, forgejoOrg)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
|
||||
resp, err := forgejoClient.Do(req)
|
||||
if err != nil {
|
||||
JSONError(w, fmt.Sprintf("Erreur accès store : %v", err), http.StatusBadGateway)
|
||||
JSONResponse(w, http.StatusOK, registryResp{
|
||||
Modules: []RegistryModule{},
|
||||
Error: fmt.Sprintf("Impossible de joindre le store (%s) : %v", forgejoURL, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
JSONError(w, "Erreur lecture réponse store", http.StatusBadGateway)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
JSONResponse(w, http.StatusOK, registryResp{
|
||||
Modules: []RegistryModule{},
|
||||
Error: fmt.Sprintf("Store a répondu HTTP %d : %s", resp.StatusCode, string(body)),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Structure de réponse Gitea/Forgejo
|
||||
var forgejoResp struct {
|
||||
Data []struct {
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
} `json:"data"`
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
// /api/v1/orgs/{org}/repos retourne directement un tableau []repo
|
||||
var repos []struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &forgejoResp); err != nil {
|
||||
JSONError(w, "Erreur parsing réponse store", http.StatusBadGateway)
|
||||
if err := json.Unmarshal(body, &repos); err != nil {
|
||||
JSONResponse(w, http.StatusOK, registryResp{
|
||||
Modules: []RegistryModule{},
|
||||
Error: fmt.Sprintf("Réponse store invalide : %v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var modules []RegistryModule
|
||||
for _, repo := range forgejoResp.Data {
|
||||
// Exclure le repo "core"
|
||||
modules := make([]RegistryModule, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
if repo.Name == "core" {
|
||||
continue
|
||||
}
|
||||
|
|
@ -376,10 +406,15 @@ func (h *SettingsHandler) GetRegistryModules(w http.ResponseWriter, r *http.Requ
|
|||
})
|
||||
}
|
||||
|
||||
if modules == nil {
|
||||
modules = []RegistryModule{}
|
||||
JSONResponse(w, http.StatusOK, registryResp{Modules: modules})
|
||||
}
|
||||
|
||||
// envOr retourne la valeur de la variable d'environnement ou la valeur par défaut.
|
||||
func envOr(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
JSONResponse(w, http.StatusOK, modules)
|
||||
return def
|
||||
}
|
||||
|
||||
// moduleJSON représente le fichier module.json d'un module.
|
||||
|
|
@ -403,8 +438,21 @@ func (h *SettingsHandler) InstallRegistryModule(w http.ResponseWriter, r *http.R
|
|||
id := chi.URLParam(r, "id")
|
||||
|
||||
// Récupérer module.json depuis Forgejo
|
||||
url := fmt.Sprintf("https://git.geronzi.fr/api/v1/repos/proxmoxPanel/%s/raw/module.json?ref=main", id)
|
||||
resp, err := http.Get(url)
|
||||
forgejoURL := envOr("FORGEJO_URL", "https://git.geronzi.fr")
|
||||
forgejoOrg := envOr("FORGEJO_ORG", "proxmoxPanel")
|
||||
// Essayer d'abord la branche master puis main
|
||||
moduleJSONURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/raw/module.json?ref=master", forgejoURL, forgejoOrg, id)
|
||||
reqCtx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
req, _ := http.NewRequestWithContext(reqCtx, "GET", moduleJSONURL, nil)
|
||||
resp, err := forgejoClient.Do(req)
|
||||
if err == nil && resp.StatusCode == http.StatusNotFound {
|
||||
// Retenter sur la branche main
|
||||
resp.Body.Close()
|
||||
moduleJSONURL = fmt.Sprintf("%s/api/v1/repos/%s/%s/raw/module.json?ref=main", forgejoURL, forgejoOrg, id)
|
||||
req2, _ := http.NewRequestWithContext(reqCtx, "GET", moduleJSONURL, nil)
|
||||
resp, err = forgejoClient.Do(req2)
|
||||
}
|
||||
if err != nil {
|
||||
JSONError(w, fmt.Sprintf("Impossible d'accéder au module %s : %v", id, err), http.StatusBadGateway)
|
||||
return
|
||||
|
|
@ -441,7 +489,7 @@ func (h *SettingsHandler) InstallRegistryModule(w http.ResponseWriter, r *http.R
|
|||
}
|
||||
|
||||
// URL du repo
|
||||
repoURL := fmt.Sprintf("https://git.geronzi.fr/proxmoxPanel/%s", id)
|
||||
repoURL := fmt.Sprintf("%s/%s/%s", envOr("FORGEJO_URL", "https://git.geronzi.fr"), envOr("FORGEJO_ORG", "proxmoxPanel"), id)
|
||||
|
||||
hasBackend := 0
|
||||
if mod.HasBackend {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue