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
|
|
@ -1080,13 +1080,20 @@ document.addEventListener('alpine:init', () => {
|
|||
toggling: {},
|
||||
storeModules: [],
|
||||
storeLoading: true,
|
||||
storeError: '',
|
||||
installing: {},
|
||||
rebuildRequired: false,
|
||||
rebuilding: false,
|
||||
rebuildDone: false,
|
||||
_pollTimer: null,
|
||||
|
||||
async init() {
|
||||
await Promise.all([this.load(), this.loadStore()])
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this._pollTimer) clearInterval(this._pollTimer)
|
||||
},
|
||||
|
||||
async load() {
|
||||
this.loading = true
|
||||
try {
|
||||
|
|
@ -1099,10 +1106,20 @@ document.addEventListener('alpine:init', () => {
|
|||
|
||||
async loadStore() {
|
||||
this.storeLoading = true
|
||||
this.storeError = ''
|
||||
try {
|
||||
const res = await apiFetch('/api/registry/modules')
|
||||
if (res.ok) this.storeModules = await res.json() || []
|
||||
if (res.ok) {
|
||||
// L'API retourne { modules: [...], error: "..." }
|
||||
const data = await res.json()
|
||||
this.storeModules = data.modules || []
|
||||
if (data.error) this.storeError = data.error
|
||||
} else {
|
||||
this.storeError = `Erreur serveur (HTTP ${res.status})`
|
||||
this.storeModules = []
|
||||
}
|
||||
} catch(e) {
|
||||
this.storeError = 'Impossible de joindre le store : ' + e.message
|
||||
this.storeModules = []
|
||||
} finally {
|
||||
this.storeLoading = false
|
||||
|
|
@ -1115,12 +1132,17 @@ document.addEventListener('alpine:init', () => {
|
|||
const action = mod.is_enabled ? 'disable' : 'enable'
|
||||
const res = await apiFetch(`/api/modules/${mod.id}/${action}`, { method: 'POST' })
|
||||
if (res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
mod.is_enabled = !mod.is_enabled
|
||||
if (data.restarting) {
|
||||
Alpine.store('toasts').info('Redémarrage du container en cours…')
|
||||
this._startRebuildPoll()
|
||||
}
|
||||
// Rafraîchir la sidebar
|
||||
const sb = document.querySelector('[x-data="sidebar()"]')
|
||||
if (sb && sb._x_dataStack) {
|
||||
if (sb) {
|
||||
const sidebarData = Alpine.$data(sb)
|
||||
if (sidebarData && sidebarData.refreshNav) await sidebarData.refreshNav()
|
||||
if (sidebarData?.refreshNav) await sidebarData.refreshNav()
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
|
|
@ -1135,9 +1157,15 @@ document.addEventListener('alpine:init', () => {
|
|||
try {
|
||||
const res = await apiFetch(`/api/registry/modules/${mod.id}/install`, { method: 'POST' })
|
||||
if (res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
mod.installed = true
|
||||
this.rebuildRequired = true
|
||||
Alpine.store('toasts').success(`Module ${mod.id} installé — rebuild requis`)
|
||||
if (data.rebuilding) {
|
||||
this.rebuilding = true
|
||||
Alpine.store('toasts').info(`Module ${mod.id} installé — rebuild en cours (~2 min)`)
|
||||
this._startRebuildPoll()
|
||||
} else {
|
||||
Alpine.store('toasts').success(`Module ${mod.id} installé`)
|
||||
}
|
||||
await this.load()
|
||||
} else {
|
||||
const b = await res.json().catch(() => ({}))
|
||||
|
|
@ -1150,6 +1178,25 @@ document.addEventListener('alpine:init', () => {
|
|||
}
|
||||
},
|
||||
|
||||
// Poll /api/health toutes les 3s pour détecter le retour du container après rebuild/restart.
|
||||
_startRebuildPoll() {
|
||||
if (this._pollTimer) return
|
||||
// Attendre 5s avant de commencer à poller (le container est encore en train de s'arrêter)
|
||||
setTimeout(() => {
|
||||
this._pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/health')
|
||||
if (res.ok) {
|
||||
clearInterval(this._pollTimer)
|
||||
this._pollTimer = null
|
||||
this.rebuilding = false
|
||||
this.rebuildDone = true
|
||||
}
|
||||
} catch(_) { /* container encore hors ligne, on attend */ }
|
||||
}, 3000)
|
||||
}, 5000)
|
||||
},
|
||||
|
||||
t(key) { return Alpine.store('i18n').t(key) },
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -90,9 +90,21 @@
|
|||
</div>
|
||||
|
||||
<!-- Store : modules disponibles -->
|
||||
<h3 class="section-title" style="margin-top:2rem"><i class="lnid-download-2"></i> Store</h3>
|
||||
<div style="display:flex;align-items:center;gap:.75rem;margin-top:2rem">
|
||||
<h3 class="section-title" style="margin:0"><i class="lnid-download-2"></i> Store</h3>
|
||||
<button class="neu-btn neu-btn--sm neu-btn--icon-sm" @click="loadStore()" :disabled="storeLoading" title="Rafraîchir le store">
|
||||
<i class="lnid-refresh-2" :class="{ 'spin': storeLoading }"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="section-desc">Modules disponibles depuis <a href="https://git.geronzi.fr/proxmoxPanel" target="_blank">git.geronzi.fr/proxmoxPanel</a></p>
|
||||
|
||||
<!-- Erreur store -->
|
||||
<div class="neu-card" x-show="storeError && !storeLoading"
|
||||
style="margin-bottom:1rem;border-left:3px solid var(--neu-danger);display:flex;align-items:center;gap:.75rem">
|
||||
<i class="lnid-warning-circle-1" style="color:var(--neu-danger)"></i>
|
||||
<span x-text="storeError"></span>
|
||||
</div>
|
||||
|
||||
<div class="loading-state" x-show="storeLoading">
|
||||
<div class="spinner-lg"></div><span>Chargement du store…</span>
|
||||
</div>
|
||||
|
|
@ -121,15 +133,23 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p class="empty-state" x-show="!storeLoading && storeModules.length === 0">
|
||||
<p class="empty-state" x-show="!storeLoading && !storeError && storeModules.length === 0">
|
||||
Aucun module disponible dans le store
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Message rebuild -->
|
||||
<div class="neu-card rebuild-notice" x-show="rebuildRequired" style="margin-top:1rem;border-left:3px solid var(--neu-warning)">
|
||||
<i class="lnid-warning-circle-1" style="color:var(--neu-warning)"></i>
|
||||
<span>Un ou plusieurs modules ont été installés. <strong>Un rebuild du container est nécessaire pour les activer.</strong></span>
|
||||
<!-- Bannière rebuild en cours -->
|
||||
<div class="neu-card rebuild-notice" x-show="rebuilding"
|
||||
style="margin-top:1rem;border-left:3px solid var(--neu-accent);display:flex;align-items:center;gap:.75rem">
|
||||
<div class="spinner-sm" style="flex-shrink:0"></div>
|
||||
<span>Rebuild du container en cours (~1-2 min) — l'interface redémarrera automatiquement.</span>
|
||||
</div>
|
||||
|
||||
<!-- Rebuild terminé (backend revenu) -->
|
||||
<div class="neu-card rebuild-notice" x-show="rebuildDone"
|
||||
style="margin-top:1rem;border-left:3px solid var(--neu-success);display:flex;align-items:center;gap:.75rem">
|
||||
<i class="lnid-circle-check-1" style="color:var(--neu-success)"></i>
|
||||
<span>Rebuild terminé. <button class="neu-btn neu-btn--sm" @click="window.location.reload()">Recharger la page</button></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue