From b54481aff8db6c7d65912be1224e29c35632f70a Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sun, 5 Apr 2026 02:27:41 +0300 Subject: [PATCH] fix: NPM remote toggle auto-save, proxy resync on remote change, webhook URL as path - Remote NPM toggle now auto-saves immediately when toggled - Toggling npm_remote triggers proxy resync (re-creates routes with server_ip or container name) - Webhook URL shows just the path (/api/webhook/{secret}) instead of full URL with wrong domain - Fix tag dropdown: resolve registry ID from name before fetching tags - Remove unused fmt import --- internal/api/settings.go | 29 ++++------------------- web/src/routes/projects/[id]/+page.svelte | 7 +++++- web/src/routes/settings/npm/+page.svelte | 21 +++++++++++++++- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/internal/api/settings.go b/internal/api/settings.go index 7c28f5a..890d8df 100644 --- a/internal/api/settings.go +++ b/internal/api/settings.go @@ -2,7 +2,6 @@ package api import ( "context" - "fmt" "log/slog" "net/http" "path/filepath" @@ -234,6 +233,7 @@ func (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) { // If proxy-affecting settings changed, re-sync all proxy routes in the background. proxyChanged := existing.Domain != updated.Domain || existing.ProxyProvider != updated.ProxyProvider || + existing.NpmRemote != updated.NpmRemote || sslChanged if proxyChanged { go s.resyncAllProxies(existing, updated) @@ -267,20 +267,13 @@ func (s *Server) getWebhookURL(w http.ResponseWriter, r *http.Request) { return } - webhookURL := "" + webhookPath := "" if settings.WebhookSecret != "" { - host := settings.Domain - scheme := "https" - if host == "" { - // Fall back to request host for dev/local setups. - host = r.Host - scheme = "http" - } - webhookURL = fmt.Sprintf("%s://%s/api/webhook/%s", scheme, host, settings.WebhookSecret) + webhookPath = "/api/webhook/" + settings.WebhookSecret } respondJSON(w, http.StatusOK, map[string]string{ - "webhook_url": webhookURL, + "webhook_url": webhookPath, }) } @@ -292,19 +285,7 @@ func (s *Server) regenerateWebhookSecret(w http.ResponseWriter, r *http.Request) return } - settings, err := s.store.GetSettings() - if err != nil { - respondError(w, http.StatusInternalServerError, "failed to get settings: "+err.Error()) - return - } - - host := settings.Domain - scheme := "https" - if host == "" { - host = r.Host - scheme = "http" - } - webhookURL := fmt.Sprintf("%s://%s/api/webhook/%s", scheme, host, secret) + webhookURL := "/api/webhook/" + secret respondJSON(w, http.StatusOK, map[string]string{ "webhook_url": webhookURL, diff --git a/web/src/routes/projects/[id]/+page.svelte b/web/src/routes/projects/[id]/+page.svelte index 3343a00..e888f7a 100644 --- a/web/src/routes/projects/[id]/+page.svelte +++ b/web/src/routes/projects/[id]/+page.svelte @@ -158,7 +158,12 @@ tagsLoading = true; try { - availableTags = await api.listRegistryTags(project.registry, project.image); + // Look up registry ID from name. + const registries = await api.listRegistries(); + const reg = registries.find(r => r.name === project?.registry); + if (reg) { + availableTags = await api.listRegistryTags(reg.id, project.image); + } } catch { availableTags = []; } finally { diff --git a/web/src/routes/settings/npm/+page.svelte b/web/src/routes/settings/npm/+page.svelte index 14dc7b1..27e1751 100644 --- a/web/src/routes/settings/npm/+page.svelte +++ b/web/src/routes/settings/npm/+page.svelte @@ -130,7 +130,26 @@ } catch { sslCertName = `Certificate #${sslCertificateId}`; } } - async function init() { await loadData(); await resolveCertName(); } + let initialized = $state(false); + + async function saveNpmRemote(value: boolean) { + try { + await updateSettings({ npm_remote: value } as any); + toasts.success($t('settingsCredentials.saved')); + } catch (err) { + toasts.error(err instanceof Error ? err.message : $t('settingsCredentials.saveFailed')); + } + } + + // Auto-save npm_remote when toggled (skip initial load). + $effect(() => { + const val = npmRemote; + if (initialized) { + saveNpmRemote(val); + } + }); + + async function init() { await loadData(); await resolveCertName(); initialized = true; } $effect(() => { init(); });