feat: add ability to override the UI configuration with environment variables

This commit is contained in:
Elias Schneider
2025-02-12 14:20:52 +01:00
parent 2d78349b38
commit 4e858420e9
5 changed files with 65 additions and 21 deletions

View File

@@ -25,6 +25,7 @@ type EnvConfigSchema struct {
Host string `env:"HOST"` Host string `env:"HOST"`
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"` MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"` GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
UiConfigDisabled bool `env:"PUBLIC_UI_CONFIG_DISABLED"`
} }
var EnvConfig = &EnvConfigSchema{ var EnvConfig = &EnvConfigSchema{
@@ -38,6 +39,7 @@ var EnvConfig = &EnvConfigSchema{
Host: "0.0.0.0", Host: "0.0.0.0",
MaxMindLicenseKey: "", MaxMindLicenseKey: "",
GeoLiteDBPath: "data/GeoLite2-City.mmdb", GeoLiteDBPath: "data/GeoLite2-City.mmdb",
UiConfigDisabled: false,
} }
func init() { func init() {

View File

@@ -184,3 +184,10 @@ func (e *OidcAccessDeniedError) Error() string {
} }
func (e *OidcAccessDeniedError) HttpStatusCode() int { return http.StatusForbidden } func (e *OidcAccessDeniedError) HttpStatusCode() int { return http.StatusForbidden }
type UiConfigDisabledError struct{}
func (e *UiConfigDisabledError) Error() string {
return "The configuration can't be changed since the UI configuration is disabled"
}
func (e *UiConfigDisabledError) HttpStatusCode() int { return http.StatusForbidden }

View File

@@ -188,12 +188,15 @@ var defaultDbConfig = model.AppConfig{
} }
func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) { func (s *AppConfigService) UpdateAppConfig(input dto.AppConfigUpdateDto) ([]model.AppConfigVariable, error) {
var savedConfigVariables []model.AppConfigVariable if common.EnvConfig.UiConfigDisabled {
return nil, &common.UiConfigDisabledError{}
}
tx := s.db.Begin() tx := s.db.Begin()
rt := reflect.ValueOf(input).Type() rt := reflect.ValueOf(input).Type()
rv := reflect.ValueOf(input) rv := reflect.ValueOf(input)
var savedConfigVariables []model.AppConfigVariable
for i := 0; i < rt.NumField(); i++ { for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i) field := rt.Field(i)
key := field.Tag.Get("json") key := field.Tag.Get("json")
@@ -254,9 +257,13 @@ func (s *AppConfigService) ListAppConfig(showAll bool) ([]model.AppConfigVariabl
return nil, err return nil, err
} }
// Set the value to the default value if it is empty
for i := range configuration { for i := range configuration {
if configuration[i].Value == "" && configuration[i].DefaultValue != "" { if common.EnvConfig.UiConfigDisabled {
// Set the value to the environment variable if the UI config is disabled
configuration[i].Value = s.getConfigVariableFromEnvironmentVariable(configuration[i].Key, configuration[i].DefaultValue)
} else if configuration[i].Value == "" && configuration[i].DefaultValue != "" {
// Set the value to the default value if it is empty
configuration[i].Value = configuration[i].DefaultValue configuration[i].Value = configuration[i].DefaultValue
} }
} }
@@ -355,12 +362,25 @@ func (s *AppConfigService) LoadDbConfigFromDb() error {
return err return err
} }
if storedConfigVar.Value == "" && storedConfigVar.DefaultValue != "" { if common.EnvConfig.UiConfigDisabled {
storedConfigVar.Value = s.getConfigVariableFromEnvironmentVariable(currentConfigVar.Key, storedConfigVar.DefaultValue)
} else if storedConfigVar.Value == "" && storedConfigVar.DefaultValue != "" {
storedConfigVar.Value = storedConfigVar.DefaultValue storedConfigVar.Value = storedConfigVar.DefaultValue
} }
dbConfigField.Set(reflect.ValueOf(storedConfigVar)) dbConfigField.Set(reflect.ValueOf(storedConfigVar))
} }
return nil return nil
} }
func (s *AppConfigService) getConfigVariableFromEnvironmentVariable(key, fallbackValue string) string {
environmentVariableName := utils.CamelCaseToScreamingSnakeCase(key)
if value, exists := os.LookupEnv(environmentVariableName); exists {
return value
}
return fallbackValue
}

View File

@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"net/url" "net/url"
"regexp"
"strings"
"unicode" "unicode"
) )
@@ -62,3 +64,12 @@ func CamelCaseToSnakeCase(s string) string {
} }
return string(result) return string(result)
} }
func CamelCaseToScreamingSnakeCase(s string) string {
// Insert underscores before uppercase letters (except the first one)
re := regexp.MustCompile(`([a-z0-9])([A-Z])`)
snake := re.ReplaceAllString(s, `${1}_${2}`)
// Convert to uppercase
return strings.ToUpper(snake)
}

View File

@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { env } from '$env/dynamic/public';
import CollapsibleCard from '$lib/components/collapsible-card.svelte'; import CollapsibleCard from '$lib/components/collapsible-card.svelte';
import AppConfigService from '$lib/services/app-config-service'; import AppConfigService from '$lib/services/app-config-service';
import appConfigStore from '$lib/stores/application-configuration-store'; import appConfigStore from '$lib/stores/application-configuration-store';
@@ -13,6 +14,7 @@
let { data } = $props(); let { data } = $props();
let appConfig = $state(data.appConfig); let appConfig = $state(data.appConfig);
const uiConfigDisabled = env.PUBLIC_UI_CONFIG_DISABLED === 'true';
const appConfigService = new AppConfigService(); const appConfigService = new AppConfigService();
async function updateAppConfig(updatedAppConfig: Partial<AllAppConfig>) { async function updateAppConfig(updatedAppConfig: Partial<AllAppConfig>) {
@@ -55,26 +57,28 @@
<title>Application Configuration</title> <title>Application Configuration</title>
</svelte:head> </svelte:head>
<CollapsibleCard id="application-configuration-general" title="General" defaultExpanded> <fieldset class="flex flex-col gap-5" disabled={uiConfigDisabled}>
<CollapsibleCard id="application-configuration-general" title="General" defaultExpanded>
<AppConfigGeneralForm {appConfig} callback={updateAppConfig} /> <AppConfigGeneralForm {appConfig} callback={updateAppConfig} />
</CollapsibleCard> </CollapsibleCard>
<CollapsibleCard <CollapsibleCard
id="application-configuration-email" id="application-configuration-email"
title="Email" title="Email"
description="Enable email notifications to alert users when a login is detected from a new device or description="Enable email notifications to alert users when a login is detected from a new device or
location." location."
> >
<AppConfigEmailForm {appConfig} callback={updateAppConfig} /> <AppConfigEmailForm {appConfig} callback={updateAppConfig} />
</CollapsibleCard> </CollapsibleCard>
<CollapsibleCard <CollapsibleCard
id="application-configuration-ldap" id="application-configuration-ldap"
title="LDAP" title="LDAP"
description="Configure LDAP settings to sync users and groups from an LDAP server." description="Configure LDAP settings to sync users and groups from an LDAP server."
> >
<AppConfigLdapForm {appConfig} callback={updateAppConfig} /> <AppConfigLdapForm {appConfig} callback={updateAppConfig} />
</CollapsibleCard> </CollapsibleCard>
</fieldset>
<CollapsibleCard id="application-configuration-images" title="Images"> <CollapsibleCard id="application-configuration-images" title="Images">
<UpdateApplicationImages callback={updateImages} /> <UpdateApplicationImages callback={updateImages} />