From 4a5d8aa70229f765d319bd39486fdcb6a64806b6 Mon Sep 17 00:00:00 2001 From: Sora39831 <540587985@qq.com> Date: Sat, 4 Apr 2026 14:45:31 +0800 Subject: [PATCH] fix(settings): preserve turnstile and omitted config fields --- web/assets/js/model/setting.js | 10 ++++++- web/controller/setting.go | 40 +++++++++++++++++++++++-- web/service/setting.go | 7 ++++- web/service/setting_test.go | 54 ++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 5 deletions(-) diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js index af80a63e..8814bf43 100644 --- a/web/assets/js/model/setting.js +++ b/web/assets/js/model/setting.js @@ -77,6 +77,14 @@ class AllSetting { this.ldapDefaultExpiryDays = 0; this.ldapDefaultLimitIP = 0; + this.dbType = "sqlite"; + this.dbHost = "127.0.0.1"; + this.dbPort = "3306"; + this.dbUser = ""; + this.dbName = "3xui"; + + this.turnstileSiteKey = ""; + if (data == null) { return } @@ -86,4 +94,4 @@ class AllSetting { equals(other) { return ObjectUtil.equals(this, other); } -} \ No newline at end of file +} diff --git a/web/controller/setting.go b/web/controller/setting.go index fc5486bc..be0629ba 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -1,7 +1,9 @@ package controller import ( + "encoding/json" "errors" + "io" "time" "github.com/mhsanaei/3x-ui/v2/util/crypto" @@ -66,15 +68,47 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) { jsonObj(c, result, nil) } +func bindSettingUpdate(c *gin.Context) (*entity.AllSetting, map[string]struct{}, error) { + allSetting := &entity.AllSetting{} + presentKeys := map[string]struct{}{} + + if c.ContentType() == "application/json" { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + return nil, nil, err + } + if err := json.Unmarshal(body, allSetting); err != nil { + return nil, nil, err + } + var raw map[string]json.RawMessage + if err := json.Unmarshal(body, &raw); err != nil { + return nil, nil, err + } + for key := range raw { + presentKeys[key] = struct{}{} + } + return allSetting, presentKeys, nil + } + + if err := c.ShouldBind(allSetting); err != nil { + return nil, nil, err + } + if err := c.Request.ParseForm(); err == nil { + for key := range c.Request.PostForm { + presentKeys[key] = struct{}{} + } + } + return allSetting, presentKeys, nil +} + // updateSetting updates all settings with the provided data. func (a *SettingController) updateSetting(c *gin.Context) { - allSetting := &entity.AllSetting{} - err := c.ShouldBind(allSetting) + allSetting, presentKeys, err := bindSettingUpdate(c) if err != nil { jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err) return } - err = a.settingService.UpdateAllSetting(allSetting) + err = a.settingService.UpdateAllSetting(allSetting, presentKeys) jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err) } diff --git a/web/service/setting.go b/web/service/setting.go index 35cdfc24..c9cc99ae 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -977,7 +977,7 @@ func (s *SettingService) SetTurnstileSecretKey(value string) error { return s.setString("turnstileSecretKey", value) } -func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { +func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting, presentKeys map[string]struct{}) error { if err := allSetting.CheckValid(); err != nil { return err } @@ -995,6 +995,11 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { if key == "-" || key == "" { continue } + if presentKeys != nil { + if _, ok := presentKeys[key]; !ok { + continue + } + } fieldV := v.FieldByName(field.Name) settings[key] = fmt.Sprint(fieldV.Interface()) } diff --git a/web/service/setting_test.go b/web/service/setting_test.go index 32208e50..45d574f7 100644 --- a/web/service/setting_test.go +++ b/web/service/setting_test.go @@ -295,3 +295,57 @@ func TestRoundTripNestedFormat(t *testing.T) { t.Error("expected 'tgBot' group in nested JSON") } } + +func TestUpdateAllSettingPreservesOmittedFields(t *testing.T) { + setupTestSettings(t) + + svc := &SettingService{} + if err := svc.setString("turnstileSiteKey", "site-key-123"); err != nil { + t.Fatalf("setString turnstileSiteKey error: %v", err) + } + if err := svc.setString("dbType", "mariadb"); err != nil { + t.Fatalf("setString dbType error: %v", err) + } + if err := svc.setString("dbHost", "10.0.0.8"); err != nil { + t.Fatalf("setString dbHost error: %v", err) + } + + allSetting, err := svc.GetAllSetting() + if err != nil { + t.Fatalf("GetAllSetting error: %v", err) + } + allSetting.WebPort = 9443 + + presentKeys := map[string]struct{}{} + for _, key := range []string{ + "webListen", "webDomain", "webPort", "webCertFile", "webKeyFile", "webBasePath", "sessionMaxAge", + "pageSize", "expireDiff", "trafficDiff", "remarkModel", "datepicker", + "tgBotEnable", "tgBotToken", "tgBotProxy", "tgBotAPIServer", "tgBotChatId", "tgRunTime", "tgBotBackup", "tgBotLoginNotify", "tgCpu", "tgLang", + "timeLocation", "twoFactorEnable", "twoFactorToken", + "subEnable", "subJsonEnable", "subTitle", "subSupportUrl", "subProfileUrl", "subAnnounce", "subEnableRouting", "subRoutingRules", "subListen", "subPort", "subPath", "subDomain", "subCertFile", "subKeyFile", "subUpdates", "externalTrafficInformEnable", "externalTrafficInformURI", "subEncrypt", "subShowInfo", "subURI", "subJsonPath", "subJsonURI", "subJsonFragment", "subJsonNoises", "subJsonMux", "subJsonRules", + "ldapEnable", "ldapHost", "ldapPort", "ldapUseTLS", "ldapBindDN", "ldapPassword", "ldapBaseDN", "ldapUserFilter", "ldapUserAttr", "ldapVlessField", "ldapSyncCron", "ldapFlagField", "ldapTruthyValues", "ldapInvertFlag", "ldapInboundTags", "ldapAutoCreate", "ldapAutoDelete", "ldapDefaultTotalGB", "ldapDefaultExpiryDays", "ldapDefaultLimitIP", + } { + presentKeys[key] = struct{}{} + } + + if err := svc.UpdateAllSetting(allSetting, presentKeys); err != nil { + t.Fatalf("UpdateAllSetting error: %v", err) + } + + settings, err := loadSettings() + if err != nil { + t.Fatalf("loadSettings error: %v", err) + } + if got := settings["turnstileSiteKey"]; got != "site-key-123" { + t.Fatalf("expected turnstileSiteKey to be preserved, got %q", got) + } + if got := settings["dbType"]; got != "mariadb" { + t.Fatalf("expected dbType to be preserved, got %q", got) + } + if got := settings["dbHost"]; got != "10.0.0.8" { + t.Fatalf("expected dbHost to be preserved, got %q", got) + } + if got := settings["webPort"]; got != "9443" { + t.Fatalf("expected webPort to be updated, got %q", got) + } +}