diff --git a/frontend-modern/src/components/Settings/Settings.tsx b/frontend-modern/src/components/Settings/Settings.tsx index 770484851..3051a4788 100644 --- a/frontend-modern/src/components/Settings/Settings.tsx +++ b/frontend-modern/src/components/Settings/Settings.tsx @@ -97,6 +97,7 @@ const Settings: Component = () => { // System settings // PBS polling interval removed - fixed at 10 seconds const [allowedOrigins, setAllowedOrigins] = createSignal('*'); + const [envOverrides, setEnvOverrides] = createSignal>({}); // Connection timeout removed - backend-only setting // Update settings @@ -350,6 +351,10 @@ const Settings: Component = () => { if (systemSettings.updateChannel) { setUpdateChannel(systemSettings.updateChannel as 'stable' | 'rc'); } + // Track environment variable overrides + if (systemSettings.envOverrides) { + setEnvOverrides(systemSettings.envOverrides); + } } else { // Fallback to old endpoint await SettingsAPI.getSettings(); @@ -1153,16 +1158,38 @@ const Settings: Component = () => {

For reverse proxy setups (* = allow all, empty = same-origin only)

- { - setAllowedOrigins(e.currentTarget.value); - setHasUnsavedChanges(true); - }} - placeholder="* or https://example.com" - class="w-full px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800" - /> +
+ { + if (!envOverrides().allowedOrigins) { + setAllowedOrigins(e.currentTarget.value); + setHasUnsavedChanges(true); + } + }} + disabled={envOverrides().allowedOrigins} + placeholder="* or https://example.com" + class={`w-full px-3 py-1.5 text-sm border rounded-lg ${ + envOverrides().allowedOrigins + ? 'border-amber-300 dark:border-amber-600 bg-amber-50 dark:bg-amber-900/20 cursor-not-allowed opacity-75' + : 'border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800' + }`} + /> + {envOverrides().allowedOrigins && ( +
+
+ + + + Overridden by ALLOWED_ORIGINS environment variable +
+
+ Remove the env var and restart to enable UI configuration +
+
+ )} +
diff --git a/internal/api/system_settings.go b/internal/api/system_settings.go index 013271b46..9419b7512 100644 --- a/internal/api/system_settings.go +++ b/internal/api/system_settings.go @@ -38,8 +38,17 @@ func (h *SystemSettingsHandler) HandleGetSystemSettings(w http.ResponseWriter, r } } + // Include env override information + response := struct { + *config.SystemSettings + EnvOverrides map[string]bool `json:"envOverrides,omitempty"` + }{ + SystemSettings: settings, + EnvOverrides: h.config.EnvOverrides, + } + w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(settings) + json.NewEncoder(w).Encode(response) } // HandleUpdateSystemSettings updates the system settings diff --git a/internal/config/config.go b/internal/config/config.go index e14d65e58..3627437b7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -107,6 +107,9 @@ type Config struct { // Deprecated - for backward compatibility Port int `envconfig:"PORT"` // Maps to BackendPort Debug bool `envconfig:"DEBUG" default:"false"` + + // Track which settings are overridden by environment variables + EnvOverrides map[string]bool `json:"-"` } // PVEInstance represents a Proxmox VE connection @@ -207,6 +210,7 @@ func Load() (*Config, error) { PVEPollingInterval: 10 * time.Second, // Deprecated - not used PBSPollingInterval: 60 * time.Second, // Default PBS polling (slower) DiscoverySubnet: "auto", + EnvOverrides: make(map[string]bool), } // Initialize persistence @@ -389,23 +393,28 @@ func Load() (*Config, error) { // NOTE: Environment variables always take precedence over UI/system.json settings if discoverySubnet := os.Getenv("DISCOVERY_SUBNET"); discoverySubnet != "" { cfg.DiscoverySubnet = discoverySubnet + cfg.EnvOverrides["discoverySubnet"] = true log.Info().Str("subnet", discoverySubnet).Msg("Discovery subnet overridden by DISCOVERY_SUBNET env var") } if logLevel := os.Getenv("LOG_LEVEL"); logLevel != "" { cfg.LogLevel = logLevel + cfg.EnvOverrides["logLevel"] = true log.Info().Str("level", logLevel).Msg("Log level overridden by LOG_LEVEL env var") } if connectionTimeout := os.Getenv("CONNECTION_TIMEOUT"); connectionTimeout != "" { if d, err := time.ParseDuration(connectionTimeout + "s"); err == nil { cfg.ConnectionTimeout = d + cfg.EnvOverrides["connectionTimeout"] = true log.Info().Dur("timeout", d).Msg("Connection timeout overridden by CONNECTION_TIMEOUT env var") } else if d, err := time.ParseDuration(connectionTimeout); err == nil { cfg.ConnectionTimeout = d + cfg.EnvOverrides["connectionTimeout"] = true log.Info().Dur("timeout", d).Msg("Connection timeout overridden by CONNECTION_TIMEOUT env var") } } if allowedOrigins := os.Getenv("ALLOWED_ORIGINS"); allowedOrigins != "" { cfg.AllowedOrigins = allowedOrigins + cfg.EnvOverrides["allowedOrigins"] = true log.Info().Str("origins", allowedOrigins).Msg("Allowed origins overridden by ALLOWED_ORIGINS env var") }