mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-08 14:14:19 +00:00
refactor(settings): organize x-ui.json by module and purpose
This commit is contained in:
parent
4a5d8aa702
commit
b0ce1a7ace
5 changed files with 312 additions and 50 deletions
|
|
@ -118,6 +118,39 @@ func GetLogFolder() string {
|
||||||
return "/var/log/x-ui"
|
return "/var/log/x-ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settingGroupAliases = map[string][]string{
|
||||||
|
"dbType": {"databaseConnection", "other"},
|
||||||
|
"dbHost": {"databaseConnection", "other"},
|
||||||
|
"dbPort": {"databaseConnection", "other"},
|
||||||
|
"dbUser": {"databaseConnection", "other"},
|
||||||
|
"dbPassword": {"databaseConnection", "other"},
|
||||||
|
"dbName": {"databaseConnection", "other"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func readGroupedString(settings map[string]any, key string) string {
|
||||||
|
if groups, ok := settingGroupAliases[key]; ok {
|
||||||
|
for _, groupName := range groups {
|
||||||
|
if group, ok := settings[groupName].(map[string]any); ok {
|
||||||
|
if value, ok := group[key].(string); ok && value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value, ok := settings[key].(string); ok && value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func settingsLayoutMeta() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"layout": "按模块-用途来归类",
|
||||||
|
"schema": "module-purpose-v1",
|
||||||
|
"description": "Top-level groups are organized by module and purpose for easier maintenance and development.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
func copyFile(src, dst string) error {
|
||||||
in, err := os.Open(src)
|
in, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -173,15 +206,7 @@ func GetDBTypeFromJSON() string {
|
||||||
return "sqlite"
|
return "sqlite"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check nested format: "other" group contains "dbType"
|
if dbType := readGroupedString(settings, "dbType"); dbType != "" {
|
||||||
if other, ok := settings["other"].(map[string]any); ok {
|
|
||||||
if dbType, ok := other["dbType"].(string); ok && dbType != "" {
|
|
||||||
return dbType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check flat format: top-level "dbType"
|
|
||||||
if dbType, ok := settings["dbType"].(string); ok && dbType != "" {
|
|
||||||
return dbType
|
return dbType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,36 +235,18 @@ func GetDBConfigFromJSON() DBConfig {
|
||||||
return DBConfig{Type: "sqlite", Host: "127.0.0.1", Port: "3306", Name: "3xui"}
|
return DBConfig{Type: "sqlite", Host: "127.0.0.1", Port: "3306", Name: "3xui"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readString extracts a value from either nested (group.key) or flat format
|
|
||||||
readString := func(nestedGroup, flatKey string) string {
|
|
||||||
if group, ok := settings[nestedGroup].(map[string]any); ok {
|
|
||||||
if v, ok := group[flatKey].(string); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v, ok := settings[flatKey].(string); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read dbType from the same parsed settings
|
|
||||||
dbType := "sqlite"
|
dbType := "sqlite"
|
||||||
if other, ok := settings["other"].(map[string]any); ok {
|
if t := readGroupedString(settings, "dbType"); t != "" {
|
||||||
if t, ok := other["dbType"].(string); ok && t != "" {
|
|
||||||
dbType = t
|
|
||||||
}
|
|
||||||
} else if t, ok := settings["dbType"].(string); ok && t != "" {
|
|
||||||
dbType = t
|
dbType = t
|
||||||
}
|
}
|
||||||
|
|
||||||
return DBConfig{
|
return DBConfig{
|
||||||
Type: dbType,
|
Type: dbType,
|
||||||
Host: readString("other", "dbHost"),
|
Host: readGroupedString(settings, "dbHost"),
|
||||||
Port: readString("other", "dbPort"),
|
Port: readGroupedString(settings, "dbPort"),
|
||||||
User: readString("other", "dbUser"),
|
User: readGroupedString(settings, "dbUser"),
|
||||||
Password: readString("other", "dbPassword"),
|
Password: readGroupedString(settings, "dbPassword"),
|
||||||
Name: readString("other", "dbName"),
|
Name: readGroupedString(settings, "dbName"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,14 +263,13 @@ func WriteSettingToJSON(key, value string) error {
|
||||||
if err := json.Unmarshal(data, &settings); err != nil {
|
if err := json.Unmarshal(data, &settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, exists := settings["_meta"]; !exists {
|
||||||
// Check if the key lives in a nested group
|
settings["_meta"] = settingsLayoutMeta()
|
||||||
groupMap := map[string]string{
|
|
||||||
"dbType": "other", "dbHost": "other", "dbPort": "other",
|
|
||||||
"dbUser": "other", "dbPassword": "other", "dbName": "other",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if group, ok := groupMap[key]; ok {
|
// Check if the key lives in a nested group
|
||||||
|
if groups, ok := settingGroupAliases[key]; ok && len(groups) > 0 {
|
||||||
|
group := groups[0]
|
||||||
if _, exists := settings[group]; !exists {
|
if _, exists := settings[group]; !exists {
|
||||||
settings[group] = make(map[string]any)
|
settings[group] = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -129,3 +131,73 @@ func TestGetLogFolderCustom(t *testing.T) {
|
||||||
t.Errorf("log folder should be '/custom/logs', got %s", GetLogFolder())
|
t.Errorf("log folder should be '/custom/logs', got %s", GetLogFolder())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetDBConfigFromJSONSupportsModulePurposeLayout(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
t.Setenv("XUI_DB_FOLDER", tmpDir)
|
||||||
|
|
||||||
|
settings := map[string]any{
|
||||||
|
"_meta": map[string]any{
|
||||||
|
"layout": "按模块-用途来归类",
|
||||||
|
},
|
||||||
|
"databaseConnection": map[string]any{
|
||||||
|
"dbType": "mariadb",
|
||||||
|
"dbHost": "10.0.0.12",
|
||||||
|
"dbPort": "3307",
|
||||||
|
"dbUser": "panel",
|
||||||
|
"dbPassword": "secret",
|
||||||
|
"dbName": "paneldb",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarshalIndent error: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(GetSettingPath(), data, 0644); err != nil {
|
||||||
|
t.Fatalf("WriteFile error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := GetDBConfigFromJSON()
|
||||||
|
if cfg.Type != "mariadb" || cfg.Host != "10.0.0.12" || cfg.Port != "3307" || cfg.User != "panel" || cfg.Password != "secret" || cfg.Name != "paneldb" {
|
||||||
|
t.Fatalf("unexpected DB config: %+v", cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteSettingToJSONUsesModulePurposeGroup(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
t.Setenv("XUI_DB_FOLDER", tmpDir)
|
||||||
|
|
||||||
|
initial := map[string]any{
|
||||||
|
"_meta": map[string]any{
|
||||||
|
"layout": "按模块-用途来归类",
|
||||||
|
},
|
||||||
|
"databaseConnection": map[string]any{},
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(initial, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarshalIndent error: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(GetSettingPath(), data, 0644); err != nil {
|
||||||
|
t.Fatalf("WriteFile error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := WriteSettingToJSON("dbHost", "127.0.0.2"); err != nil {
|
||||||
|
t.Fatalf("WriteSettingToJSON error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := os.ReadFile(GetSettingPath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFile error: %v", err)
|
||||||
|
}
|
||||||
|
var parsed map[string]any
|
||||||
|
if err := json.Unmarshal(updated, &parsed); err != nil {
|
||||||
|
t.Fatalf("Unmarshal error: %v", err)
|
||||||
|
}
|
||||||
|
group, ok := parsed["databaseConnection"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected databaseConnection group, got %T", parsed["databaseConnection"])
|
||||||
|
}
|
||||||
|
if got, ok := group["dbHost"].(string); !ok || got != "127.0.0.2" {
|
||||||
|
t.Fatalf("expected databaseConnection.dbHost to be updated, got %v", group["dbHost"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,115 @@ var defaultValueMap = map[string]string{
|
||||||
// settingGroups defines the nested JSON structure for on-disk settings.
|
// settingGroups defines the nested JSON structure for on-disk settings.
|
||||||
// Each group maps nested keys to their flat equivalents in defaultValueMap.
|
// Each group maps nested keys to their flat equivalents in defaultValueMap.
|
||||||
var settingGroups = map[string]map[string]string{
|
var settingGroups = map[string]map[string]string{
|
||||||
|
"panelNetwork": {
|
||||||
|
"listen": "webListen",
|
||||||
|
"domain": "webDomain",
|
||||||
|
"port": "webPort",
|
||||||
|
"basePath": "webBasePath",
|
||||||
|
},
|
||||||
|
"panelTLS": {
|
||||||
|
"certFile": "webCertFile",
|
||||||
|
"keyFile": "webKeyFile",
|
||||||
|
},
|
||||||
|
"panelSecurity": {
|
||||||
|
"sessionMaxAge": "sessionMaxAge",
|
||||||
|
"twoFactorEnable": "twoFactorEnable",
|
||||||
|
"twoFactorToken": "twoFactorToken",
|
||||||
|
"turnstileSiteKey": "turnstileSiteKey",
|
||||||
|
"turnstileSecretKey": "turnstileSecretKey",
|
||||||
|
"secret": "secret",
|
||||||
|
},
|
||||||
|
"panelUX": {
|
||||||
|
"timeLocation": "timeLocation",
|
||||||
|
"datepicker": "datepicker",
|
||||||
|
"pageSize": "pageSize",
|
||||||
|
"expireDiff": "expireDiff",
|
||||||
|
"trafficDiff": "trafficDiff",
|
||||||
|
"remarkModel": "remarkModel",
|
||||||
|
},
|
||||||
|
"telegramBot": {
|
||||||
|
"enable": "tgBotEnable",
|
||||||
|
"token": "tgBotToken",
|
||||||
|
"proxy": "tgBotProxy",
|
||||||
|
"apiServer": "tgBotAPIServer",
|
||||||
|
"chatId": "tgBotChatId",
|
||||||
|
"runTime": "tgRunTime",
|
||||||
|
"backup": "tgBotBackup",
|
||||||
|
"loginNotify": "tgBotLoginNotify",
|
||||||
|
"cpu": "tgCpu",
|
||||||
|
"lang": "tgLang",
|
||||||
|
},
|
||||||
|
"subscriptionNetwork": {
|
||||||
|
"enable": "subEnable",
|
||||||
|
"jsonEnable": "subJsonEnable",
|
||||||
|
"listen": "subListen",
|
||||||
|
"port": "subPort",
|
||||||
|
"path": "subPath",
|
||||||
|
"domain": "subDomain",
|
||||||
|
"certFile": "subCertFile",
|
||||||
|
"keyFile": "subKeyFile",
|
||||||
|
"updates": "subUpdates",
|
||||||
|
"encrypt": "subEncrypt",
|
||||||
|
"showInfo": "subShowInfo",
|
||||||
|
"uri": "subURI",
|
||||||
|
"jsonPath": "subJsonPath",
|
||||||
|
"jsonURI": "subJsonURI",
|
||||||
|
},
|
||||||
|
"subscriptionBranding": {
|
||||||
|
"title": "subTitle",
|
||||||
|
"supportUrl": "subSupportUrl",
|
||||||
|
"profileUrl": "subProfileUrl",
|
||||||
|
"announce": "subAnnounce",
|
||||||
|
},
|
||||||
|
"subscriptionRouting": {
|
||||||
|
"enableRouting": "subEnableRouting",
|
||||||
|
"routingRules": "subRoutingRules",
|
||||||
|
"jsonFragment": "subJsonFragment",
|
||||||
|
"jsonNoises": "subJsonNoises",
|
||||||
|
"jsonMux": "subJsonMux",
|
||||||
|
"jsonRules": "subJsonRules",
|
||||||
|
},
|
||||||
|
"ldapConnection": {
|
||||||
|
"enable": "ldapEnable",
|
||||||
|
"host": "ldapHost",
|
||||||
|
"port": "ldapPort",
|
||||||
|
"useTLS": "ldapUseTLS",
|
||||||
|
"bindDN": "ldapBindDN",
|
||||||
|
"password": "ldapPassword",
|
||||||
|
"baseDN": "ldapBaseDN",
|
||||||
|
"userFilter": "ldapUserFilter",
|
||||||
|
"userAttr": "ldapUserAttr",
|
||||||
|
"vlessField": "ldapVlessField",
|
||||||
|
},
|
||||||
|
"ldapSync": {
|
||||||
|
"syncCron": "ldapSyncCron",
|
||||||
|
"flagField": "ldapFlagField",
|
||||||
|
"truthyValues": "ldapTruthyValues",
|
||||||
|
"invertFlag": "ldapInvertFlag",
|
||||||
|
"inboundTags": "ldapInboundTags",
|
||||||
|
"autoCreate": "ldapAutoCreate",
|
||||||
|
"autoDelete": "ldapAutoDelete",
|
||||||
|
"defaultTotalGB": "ldapDefaultTotalGB",
|
||||||
|
"defaultExpiryDays": "ldapDefaultExpiryDays",
|
||||||
|
"defaultLimitIP": "ldapDefaultLimitIP",
|
||||||
|
},
|
||||||
|
"systemIntegration": {
|
||||||
|
"externalTrafficInformEnable": "externalTrafficInformEnable",
|
||||||
|
"externalTrafficInformURI": "externalTrafficInformURI",
|
||||||
|
"warp": "warp",
|
||||||
|
"xrayOutboundTestUrl": "xrayOutboundTestUrl",
|
||||||
|
},
|
||||||
|
"databaseConnection": {
|
||||||
|
"dbType": "dbType",
|
||||||
|
"dbHost": "dbHost",
|
||||||
|
"dbPort": "dbPort",
|
||||||
|
"dbUser": "dbUser",
|
||||||
|
"dbPassword": "dbPassword",
|
||||||
|
"dbName": "dbName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var legacySettingGroups = map[string]map[string]string{
|
||||||
"web": {
|
"web": {
|
||||||
"listen": "webListen",
|
"listen": "webListen",
|
||||||
"domain": "webDomain",
|
"domain": "webDomain",
|
||||||
|
|
@ -215,6 +324,14 @@ var settingGroups = map[string]map[string]string{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func settingsLayoutMeta() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"layout": "按模块-用途来归类",
|
||||||
|
"schema": "module-purpose-v1",
|
||||||
|
"description": "Top-level groups are organized by module and purpose for easier maintenance and development.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// flatToNestedKey maps flat keys to their [group, nestedKey] pair.
|
// flatToNestedKey maps flat keys to their [group, nestedKey] pair.
|
||||||
var flatToNestedKey map[string][2]string
|
var flatToNestedKey map[string][2]string
|
||||||
|
|
||||||
|
|
@ -231,6 +348,7 @@ func init() {
|
||||||
// using the settingGroups mapping. Keys not in any group are placed at the top level.
|
// using the settingGroups mapping. Keys not in any group are placed at the top level.
|
||||||
func expandToNested(flat map[string]string) map[string]any {
|
func expandToNested(flat map[string]string) map[string]any {
|
||||||
result := make(map[string]any)
|
result := make(map[string]any)
|
||||||
|
result["_meta"] = settingsLayoutMeta()
|
||||||
|
|
||||||
// Initialize all groups
|
// Initialize all groups
|
||||||
for group := range settingGroups {
|
for group := range settingGroups {
|
||||||
|
|
@ -273,6 +391,12 @@ func flattenNested(nested map[string]any) map[string]string {
|
||||||
result[flatKey] = fmt.Sprint(strVal)
|
result[flatKey] = fmt.Sprint(strVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if groupKeys, ok := legacySettingGroups[key]; ok {
|
||||||
|
for nestedKey, flatKey := range groupKeys {
|
||||||
|
if strVal, exists := v[nestedKey]; exists {
|
||||||
|
result[flatKey] = fmt.Sprint(strVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Top-level value (ungrouped key)
|
// Top-level value (ungrouped key)
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,15 @@ func TestSettingsFileFormat(t *testing.T) {
|
||||||
t.Fatalf("settings file is not valid JSON: %v", err)
|
t.Fatalf("settings file is not valid JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify nested format: should contain group objects
|
// Verify nested format: should contain group objects and metadata.
|
||||||
for _, group := range []string{"web", "tgBot", "sub", "ldap", "other"} {
|
for _, group := range []string{
|
||||||
|
"_meta",
|
||||||
|
"panelNetwork", "panelTLS", "panelSecurity", "panelUX",
|
||||||
|
"telegramBot",
|
||||||
|
"subscriptionNetwork", "subscriptionBranding", "subscriptionRouting",
|
||||||
|
"ldapConnection", "ldapSync",
|
||||||
|
"systemIntegration", "databaseConnection",
|
||||||
|
} {
|
||||||
val, exists := parsed[group]
|
val, exists := parsed[group]
|
||||||
if !exists {
|
if !exists {
|
||||||
t.Errorf("expected group %q in nested JSON", group)
|
t.Errorf("expected group %q in nested JSON", group)
|
||||||
|
|
@ -169,6 +176,13 @@ func TestSettingsFileFormat(t *testing.T) {
|
||||||
t.Errorf("expected group %q to be an object, got %T", group, val)
|
t.Errorf("expected group %q to be an object, got %T", group, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if meta, ok := parsed["_meta"].(map[string]any); ok {
|
||||||
|
if layout, ok := meta["layout"].(string); !ok || layout != "按模块-用途来归类" {
|
||||||
|
t.Errorf("expected _meta.layout to explain grouping, got %v", meta["layout"])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("expected _meta group in nested JSON")
|
||||||
|
}
|
||||||
|
|
||||||
// Verify pretty-printed (has newlines)
|
// Verify pretty-printed (has newlines)
|
||||||
hasNewline := false
|
hasNewline := false
|
||||||
|
|
@ -240,6 +254,52 @@ func TestLegacyFlatFormatBackwardCompat(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLegacyNestedFormatBackwardCompat(t *testing.T) {
|
||||||
|
setupTestSettings(t)
|
||||||
|
|
||||||
|
legacy := map[string]any{
|
||||||
|
"web": map[string]any{
|
||||||
|
"port": "8088",
|
||||||
|
"basePath": "/legacy/",
|
||||||
|
},
|
||||||
|
"tgBot": map[string]any{
|
||||||
|
"enable": "true",
|
||||||
|
},
|
||||||
|
"other": map[string]any{
|
||||||
|
"dbType": "mariadb",
|
||||||
|
"dbHost": "192.168.1.10",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(legacy, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarshalIndent error: %v", err)
|
||||||
|
}
|
||||||
|
path := config.GetSettingPath()
|
||||||
|
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||||
|
t.Fatalf("WriteFile error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := loadSettings()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("loadSettings error: %v", err)
|
||||||
|
}
|
||||||
|
if loaded["webPort"] != "8088" {
|
||||||
|
t.Errorf("expected webPort=8088, got %s", loaded["webPort"])
|
||||||
|
}
|
||||||
|
if loaded["webBasePath"] != "/legacy/" {
|
||||||
|
t.Errorf("expected webBasePath=/legacy/, got %s", loaded["webBasePath"])
|
||||||
|
}
|
||||||
|
if loaded["tgBotEnable"] != "true" {
|
||||||
|
t.Errorf("expected tgBotEnable=true, got %s", loaded["tgBotEnable"])
|
||||||
|
}
|
||||||
|
if loaded["dbType"] != "mariadb" {
|
||||||
|
t.Errorf("expected dbType=mariadb, got %s", loaded["dbType"])
|
||||||
|
}
|
||||||
|
if loaded["dbHost"] != "192.168.1.10" {
|
||||||
|
t.Errorf("expected dbHost=192.168.1.10, got %s", loaded["dbHost"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRoundTripNestedFormat(t *testing.T) {
|
func TestRoundTripNestedFormat(t *testing.T) {
|
||||||
setupTestSettings(t)
|
setupTestSettings(t)
|
||||||
|
|
||||||
|
|
@ -280,19 +340,19 @@ func TestRoundTripNestedFormat(t *testing.T) {
|
||||||
if err := json.Unmarshal(data, &parsed); err != nil {
|
if err := json.Unmarshal(data, &parsed); err != nil {
|
||||||
t.Fatalf("settings file is not valid JSON: %v", err)
|
t.Fatalf("settings file is not valid JSON: %v", err)
|
||||||
}
|
}
|
||||||
if webGroup, ok := parsed["web"].(map[string]any); ok {
|
if webGroup, ok := parsed["panelNetwork"].(map[string]any); ok {
|
||||||
if port, ok := webGroup["port"].(string); !ok || port != "9090" {
|
if port, ok := webGroup["port"].(string); !ok || port != "9090" {
|
||||||
t.Errorf("expected web.port=9090 in nested JSON, got %v", webGroup["port"])
|
t.Errorf("expected panelNetwork.port=9090 in nested JSON, got %v", webGroup["port"])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Error("expected 'web' group in nested JSON")
|
t.Error("expected 'panelNetwork' group in nested JSON")
|
||||||
}
|
}
|
||||||
if tgGroup, ok := parsed["tgBot"].(map[string]any); ok {
|
if tgGroup, ok := parsed["telegramBot"].(map[string]any); ok {
|
||||||
if enable, ok := tgGroup["enable"].(string); !ok || enable != "true" {
|
if enable, ok := tgGroup["enable"].(string); !ok || enable != "true" {
|
||||||
t.Errorf("expected tgBot.enable=true in nested JSON, got %v", tgGroup["enable"])
|
t.Errorf("expected telegramBot.enable=true in nested JSON, got %v", tgGroup["enable"])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Error("expected 'tgBot' group in nested JSON")
|
t.Error("expected 'telegramBot' group in nested JSON")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
6
x-ui.sh
6
x-ui.sh
|
|
@ -2220,9 +2220,9 @@ db_show_status() {
|
||||||
if [ "$current_type" = "mariadb" ]; then
|
if [ "$current_type" = "mariadb" ]; then
|
||||||
local json_path="/etc/x-ui/x-ui.json"
|
local json_path="/etc/x-ui/x-ui.json"
|
||||||
if command -v jq >/dev/null 2>&1; then
|
if command -v jq >/dev/null 2>&1; then
|
||||||
local host=$(jq -r '.other.dbHost // "127.0.0.1"' "$json_path" 2>/dev/null)
|
local host=$(jq -r '.databaseConnection.dbHost // .other.dbHost // "127.0.0.1"' "$json_path" 2>/dev/null)
|
||||||
local port=$(jq -r '.other.dbPort // "3306"' "$json_path" 2>/dev/null)
|
local port=$(jq -r '.databaseConnection.dbPort // .other.dbPort // "3306"' "$json_path" 2>/dev/null)
|
||||||
local dbname=$(jq -r '.other.dbName // "3xui"' "$json_path" 2>/dev/null)
|
local dbname=$(jq -r '.databaseConnection.dbName // .other.dbName // "3xui"' "$json_path" 2>/dev/null)
|
||||||
else
|
else
|
||||||
local host=$(grep -o '"dbHost"[[:space:]]*:[[:space:]]*"[^"]*"' "$json_path" 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
local host=$(grep -o '"dbHost"[[:space:]]*:[[:space:]]*"[^"]*"' "$json_path" 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||||
local port=$(grep -o '"dbPort"[[:space:]]*:[[:space:]]*"[^"]*"' "$json_path" 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
local port=$(grep -o '"dbPort"[[:space:]]*:[[:space:]]*"[^"]*"' "$json_path" 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue