mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-09 06:06:08 +00:00
fix(settings): repair legacy path settings that block every settings save
A subJsonPath (or subPath/subClashPath/webBasePath) stored without its leading/trailing slash — written before the slash rules existed, or restored from an old backup — fails the frontend's whole-form validation, so every save on the Settings page is rejected client-side. The backend's CheckValid would normalize the value, but a save request never reaches it, leaving the panel wedged until someone edits the database by hand. Normalize the stored path rows at startup, mirroring CheckValid's slash rules. The pass is idempotent and not seeder-gated, since a restored backup can reintroduce bad values at any time. Also add the missing pages.settings.validation.pathLeadingSlash key to all 13 locales — the validation error used to render as its raw key. Closes #5726
This commit is contained in:
+35
-1
@@ -664,7 +664,10 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
if err := seedWireguardPeersToClients(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
// Idempotent, not seeder-gated: bad values can re-enter via a restored
|
||||
// backup, so re-check on every start.
|
||||
return normalizeSettingPaths()
|
||||
}
|
||||
|
||||
// resetIpLimitsWithoutFail2ban zeroes every client's IP limit on hosts where
|
||||
@@ -769,6 +772,37 @@ func clearLegacyProxySettings() error {
|
||||
})
|
||||
}
|
||||
|
||||
// normalizeSettingPaths repairs URI-path settings persisted before the
|
||||
// leading/trailing-slash rules existed (or restored from an old backup),
|
||||
// mirroring entity.AllSetting.CheckValid. CheckValid self-heals these on save,
|
||||
// but the frontend rejects the whole Settings form on the bad stored value
|
||||
// before a save can ever reach it (#5726), so the stored rows themselves must
|
||||
// be fixed. Idempotent; runs on every start.
|
||||
func normalizeSettingPaths() error {
|
||||
pathKeys := []string{"webBasePath", "subPath", "subJsonPath", "subClashPath"}
|
||||
var rows []model.Setting
|
||||
if err := db.Where("key IN ?", pathKeys).Find(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
fixed := row.Value
|
||||
if !strings.HasPrefix(fixed, "/") {
|
||||
fixed = "/" + fixed
|
||||
}
|
||||
if !strings.HasSuffix(fixed, "/") {
|
||||
fixed += "/"
|
||||
}
|
||||
if fixed == row.Value {
|
||||
continue
|
||||
}
|
||||
if err := db.Model(&model.Setting{}).Where("id = ?", row.Id).
|
||||
Update("value", fixed).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeInboundClientTgId() error {
|
||||
var inbounds []model.Inbound
|
||||
if err := db.Find(&inbounds).Error; err != nil {
|
||||
|
||||
@@ -153,3 +153,44 @@ func TestNormalizeInboundClientSubId_FillsMissingAndPreservesExisting(t *testing
|
||||
t.Fatalf("expected one InboundClientSubIdFix history row, got %d", historyCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeSettingPaths_RepairsLegacyValues(t *testing.T) {
|
||||
dbDir := t.TempDir()
|
||||
t.Setenv("XUI_DB_FOLDER", dbDir)
|
||||
if err := InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
|
||||
t.Fatalf("InitDB failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = CloseDB() })
|
||||
|
||||
seed := []model.Setting{
|
||||
{Key: "subJsonPath", Value: "YIrCXJOOOL"},
|
||||
{Key: "subPath", Value: "/sub"},
|
||||
{Key: "subClashPath", Value: "clash/"},
|
||||
{Key: "webBasePath", Value: "/panel/"},
|
||||
}
|
||||
for i := range seed {
|
||||
if err := db.Create(&seed[i]).Error; err != nil {
|
||||
t.Fatalf("seed setting %s: %v", seed[i].Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := normalizeSettingPaths(); err != nil {
|
||||
t.Fatalf("normalizeSettingPaths: %v", err)
|
||||
}
|
||||
|
||||
want := map[string]string{
|
||||
"subJsonPath": "/YIrCXJOOOL/",
|
||||
"subPath": "/sub/",
|
||||
"subClashPath": "/clash/",
|
||||
"webBasePath": "/panel/",
|
||||
}
|
||||
for key, expected := range want {
|
||||
var row model.Setting
|
||||
if err := db.Where("key = ?", key).First(&row).Error; err != nil {
|
||||
t.Fatalf("read %s: %v", key, err)
|
||||
}
|
||||
if row.Value != expected {
|
||||
t.Errorf("%s = %q, want %q", key, row.Value, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user