mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-10 03:51:54 +00:00
feat: add UI warnings for environment variable overrides
- Track which settings are overridden by env vars in backend - Expose env override information in system settings API - Show clear warnings in UI when settings are controlled by env vars - Disable input fields when overridden by environment variables - Add helpful instructions for users to remove env vars if needed This improves UX by making it clear why UI changes don't take effect when environment variables are set. Follows container best practices where env vars have highest precedence, while clearly communicating this behavior to users. Addresses user confusion when UI settings don't work due to env var overrides.
This commit is contained in:
parent
362ace960d
commit
3af29f4b09
3 changed files with 56 additions and 11 deletions
|
|
@ -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<Record<string, boolean>>({});
|
||||
// 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 = () => {
|
|||
<div>
|
||||
<label class="text-sm font-medium text-gray-900 dark:text-gray-100">CORS Allowed Origins</label>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-2">For reverse proxy setups (* = allow all, empty = same-origin only)</p>
|
||||
<input
|
||||
type="text"
|
||||
value={allowedOrigins()}
|
||||
onChange={(e) => {
|
||||
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"
|
||||
/>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={allowedOrigins()}
|
||||
onChange={(e) => {
|
||||
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 && (
|
||||
<div class="mt-2 p-2 bg-amber-100 dark:bg-amber-900/30 border border-amber-300 dark:border-amber-700 rounded text-xs text-amber-800 dark:text-amber-200">
|
||||
<div class="flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span>Overridden by ALLOWED_ORIGINS environment variable</span>
|
||||
</div>
|
||||
<div class="mt-1 text-amber-700 dark:text-amber-300">
|
||||
Remove the env var and restart to enable UI configuration
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue