diff --git a/config/config.go b/config/config.go index 31c285d2..59ad671b 100644 --- a/config/config.go +++ b/config/config.go @@ -57,6 +57,11 @@ func IsDebug() bool { return os.Getenv("XUI_DEBUG") == "true" } +// IsSkipHSTS returns true if skipping HSTS mode is enabled via the XUI_SKIP_HSTS environment variable. +func IsSkipHSTS() bool { + return os.Getenv("XUI_SKIP_HSTS") == "true" +} + // GetBinFolderPath returns the path to the binary folder, defaulting to "bin" if not set via XUI_BIN_FOLDER. func GetBinFolderPath() string { binFolderPath := os.Getenv("XUI_BIN_FOLDER") diff --git a/database/db.go b/database/db.go index a06bb5c1..a5e2b66e 100644 --- a/database/db.go +++ b/database/db.go @@ -11,6 +11,7 @@ import ( "os" "path" "slices" + "strconv" "strings" "time" @@ -198,6 +199,36 @@ func runSeeders(isUsersEmpty bool) error { return nil } +// normalizeClientJSONFields coerces loosely-typed numeric fields in a raw +// settings.clients entry so json.Unmarshal into model.Client doesn't fail +// when older rows wrote tgId/limitIp/totalGB/etc. as strings. Empty strings +// drop the key so the field falls back to its zero value. +func normalizeClientJSONFields(obj map[string]any) { + normalizeInt := func(key string) { + raw, exists := obj[key] + if !exists { + return + } + s, ok := raw.(string) + if !ok { + return + } + trimmed := strings.ReplaceAll(strings.TrimSpace(s), " ", "") + if trimmed == "" { + delete(obj, key) + return + } + if n, err := strconv.ParseInt(trimmed, 10, 64); err == nil { + obj[key] = n + } else { + delete(obj, key) + } + } + for _, k := range []string{"tgId", "limitIp", "totalGB", "expiryTime", "reset", "created_at", "updated_at"} { + normalizeInt(k) + } +} + func seedClientsFromInboundJSON() error { var inbounds []model.Inbound if err := db.Find(&inbounds).Error; err != nil { @@ -226,12 +257,15 @@ func seedClientsFromInboundJSON() error { if !ok { continue } + normalizeClientJSONFields(obj) blob, err := json.Marshal(obj) if err != nil { continue } var c model.Client if err := json.Unmarshal(blob, &c); err != nil { + log.Printf("ClientsTable seed: skip client in inbound %d (unmarshal failed): %v; payload=%s", + inbound.Id, err, string(blob)) continue } email := strings.TrimSpace(c.Email) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index af20a3a0..4a4f129b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "eslint": "^10.3.0", "eslint-plugin-vue": "^10.9.1", "globals": "^17.6.0", - "vite": "^8.0.11", + "vite": "^8.0.14", "vue-eslint-parser": "^10.4.0" }, "engines": { @@ -610,9 +610,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", "dev": true, "license": "MIT", "funding": { @@ -620,9 +620,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", - "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", "cpu": [ "arm64" ], @@ -637,9 +637,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", "cpu": [ "arm64" ], @@ -654,9 +654,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", - "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", "cpu": [ "x64" ], @@ -671,9 +671,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", "cpu": [ "x64" ], @@ -688,9 +688,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", "cpu": [ "arm" ], @@ -705,16 +705,13 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -725,16 +722,13 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -745,16 +739,13 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -765,16 +756,13 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", "cpu": [ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -785,16 +773,13 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -805,16 +790,13 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -825,9 +807,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", - "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", "cpu": [ "arm64" ], @@ -842,9 +824,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", "cpu": [ "wasm32" ], @@ -861,9 +843,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", "cpu": [ "arm64" ], @@ -878,9 +860,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", "cpu": [ "x64" ], @@ -2202,9 +2184,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2226,9 +2205,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2250,9 +2226,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2274,9 +2247,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2623,9 +2593,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -2642,7 +2612,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2715,13 +2685,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", - "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.130.0", + "@oxc-project/types": "=0.132.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -2731,21 +2701,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.1", - "@rolldown/binding-darwin-arm64": "1.0.1", - "@rolldown/binding-darwin-x64": "1.0.1", - "@rolldown/binding-freebsd-x64": "1.0.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", - "@rolldown/binding-linux-arm64-gnu": "1.0.1", - "@rolldown/binding-linux-arm64-musl": "1.0.1", - "@rolldown/binding-linux-ppc64-gnu": "1.0.1", - "@rolldown/binding-linux-s390x-gnu": "1.0.1", - "@rolldown/binding-linux-x64-gnu": "1.0.1", - "@rolldown/binding-linux-x64-musl": "1.0.1", - "@rolldown/binding-openharmony-arm64": "1.0.1", - "@rolldown/binding-wasm32-wasi": "1.0.1", - "@rolldown/binding-win32-arm64-msvc": "1.0.1", - "@rolldown/binding-win32-x64-msvc": "1.0.1" + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" } }, "node_modules/scroll-into-view-if-needed": { @@ -2957,16 +2927,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.1", + "postcss": "^8.5.15", + "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "bin": { diff --git a/frontend/package.json b/frontend/package.json index 6c756e58..6838d821 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "eslint": "^10.3.0", "eslint-plugin-vue": "^10.9.1", "globals": "^17.6.0", - "vite": "^8.0.11", + "vite": "^8.0.14", "vue-eslint-parser": "^10.4.0" }, "overrides": { diff --git a/frontend/src/models/outbound.js b/frontend/src/models/outbound.js index 8277590e..c696bc07 100644 --- a/frontend/src/models/outbound.js +++ b/frontend/src/models/outbound.js @@ -748,6 +748,9 @@ export class SockoptStreamSettings extends CommonClass { penetrate = false, addressPortStrategy = Address_Port_Strategy.NONE, trustedXForwardedFor = [], + mark = 0, + interfaceName = "", + ) { super(); this.dialerProxy = dialerProxy; @@ -757,6 +760,9 @@ export class SockoptStreamSettings extends CommonClass { this.penetrate = penetrate; this.addressPortStrategy = addressPortStrategy; this.trustedXForwardedFor = trustedXForwardedFor; + this.mark = mark; + this.interfaceName = interfaceName; + } static fromJson(json = {}) { @@ -768,7 +774,9 @@ export class SockoptStreamSettings extends CommonClass { json.tcpMptcp, json.penetrate, json.addressPortStrategy, - json.trustedXForwardedFor || [] + json.trustedXForwardedFor || [], + json.mark ?? 0, + json.interface ?? "", ); } @@ -779,7 +787,9 @@ export class SockoptStreamSettings extends CommonClass { tcpKeepAliveInterval: this.tcpKeepAliveInterval, tcpMptcp: this.tcpMptcp, penetrate: this.penetrate, - addressPortStrategy: this.addressPortStrategy + addressPortStrategy: this.addressPortStrategy, + mark: this.mark, + interface: this.interfaceName, }; if (this.trustedXForwardedFor && this.trustedXForwardedFor.length > 0) { result.trustedXForwardedFor = this.trustedXForwardedFor; @@ -1138,8 +1148,12 @@ export class StreamSettings extends CommonClass { } static fromJson(json = {}) { + // Xray-core supports both "xhttpSettings" and "splithttpSettings" (backward-compat alias) + const xhttpJson = json.xhttpSettings ?? json.splithttpSettings; + // Normalize "splithttp" network name to "xhttp" for internal consistency + const network = json.network === 'splithttp' ? 'xhttp' : json.network; return new StreamSettings( - json.network, + network, json.security, TlsStreamSettings.fromJson(json.tlsSettings), RealityStreamSettings.fromJson(json.realitySettings), @@ -1148,7 +1162,7 @@ export class StreamSettings extends CommonClass { WsStreamSettings.fromJson(json.wsSettings), GrpcStreamSettings.fromJson(json.grpcSettings), HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings), - xHTTPStreamSettings.fromJson(json.xhttpSettings), + xHTTPStreamSettings.fromJson(xhttpJson), HysteriaStreamSettings.fromJson(json.hysteriaSettings), FinalMaskStreamSettings.fromJson(json.finalmask), SockoptStreamSettings.fromJson(json.sockopt), @@ -1379,12 +1393,28 @@ export class Outbound extends CommonClass { } else if (network === 'httpupgrade') { stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host); } else if (network === 'xhttp') { - // xHTTPStreamSettings positional args are (path, host, headers, ..., mode); - // passing `json.mode` as the 3rd argument used to land in the `headers` - // slot, dropping the mode on the floor. Build the object and set mode - // explicitly to avoid that. const xh = new xHTTPStreamSettings(json.path, json.host); if (json.mode) xh.mode = json.mode; + if (json.type && !json.mode) xh.mode = json.type; + // Padding / obfuscation — sing-box families use x_padding_bytes, + // while the extra block carries xPaddingBytes. + if (json.x_padding_bytes && !json.xPaddingBytes) json.xPaddingBytes = json.x_padding_bytes; + if (typeof json.xPaddingBytes === 'string' && json.xPaddingBytes) xh.xPaddingBytes = json.xPaddingBytes; + if (json.xPaddingObfsMode === true) { + xh.xPaddingObfsMode = true; + ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => { + if (typeof json[k] === 'string' && json[k]) xh[k] = json[k]; + }); + } + // Bidirectional string fields carried in the extra block + const xFields = ["sessionPlacement", "sessionKey", "seqPlacement", "seqKey", "uplinkDataPlacement", "uplinkDataKey", "scMaxEachPostBytes"]; + xFields.forEach(k => { + if (typeof json[k] === 'string' && json[k]) xh[k] = json[k]; + }); + // Headers — VMess extra emits them as a {name: value} map + if (json.headers && typeof json.headers === 'object' && !Array.isArray(json.headers)) { + xh.headers = Object.entries(json.headers).map(([name, value]) => ({ name, value })); + } stream.xhttp = xh; } @@ -1455,6 +1485,16 @@ export class Outbound extends CommonClass { ["xPaddingKey", "xPaddingHeader", "xPaddingPlacement", "xPaddingMethod"].forEach(k => { if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k]; }); + if (!xh.mode && typeof extra.mode === 'string' && extra.mode) xh.mode = extra.mode; + // Bidirectional string fields carried inside the extra block + const xFields = ["sessionPlacement", "sessionKey", "seqPlacement", "seqKey", "uplinkDataPlacement", "uplinkDataKey", "scMaxEachPostBytes"]; + xFields.forEach(k => { + if (typeof extra[k] === 'string' && extra[k]) xh[k] = extra[k]; + }); + // Headers — extra emits them as a {name: value} map + if (extra.headers && typeof extra.headers === 'object' && !Array.isArray(extra.headers)) { + xh.headers = Object.entries(extra.headers).map(([name, value]) => ({ name, value })); + } } catch (_) { /* ignore malformed extra */ } } stream.xhttp = xh; @@ -1997,6 +2037,28 @@ Outbound.VLESSSettings = class extends CommonClass { } static fromJson(json = {}) { + // Handle v2rayN-style nested vnext array (standard Xray JSON format) + if (!ObjectUtil.isArrEmpty(json.vnext)) { + const v = json.vnext[0] || {}; + const u = ObjectUtil.isArrEmpty(v.users) ? {} : v.users[0]; + const saved = json.testseed; + const testseed = (Array.isArray(saved) + && saved.length === 4 + && saved.every(v => Number.isInteger(v) && v > 0)) + ? saved + : []; + return new Outbound.VLESSSettings( + v.address, + v.port, + u.id, + u.flow, + u.encryption, + json.reverse?.tag || '', + ReverseSniffing.fromJson(json.reverse?.sniffing || {}), + json.testpre || 0, + testseed, + ); + } if (ObjectUtil.isEmpty(json.address) || ObjectUtil.isEmpty(json.port)) return new Outbound.VLESSSettings(); const saved = json.testseed; const testseed = (Array.isArray(saved) diff --git a/frontend/src/pages/clients/ClientsPage.vue b/frontend/src/pages/clients/ClientsPage.vue index e1a03263..7c37d577 100644 --- a/frontend/src/pages/clients/ClientsPage.vue +++ b/frontend/src/pages/clients/ClientsPage.vue @@ -43,6 +43,7 @@ const { tgBotEnable, expireDiff, trafficDiff, + pageSize, create, update, remove, @@ -442,6 +443,10 @@ function expiryColor(row) { const sortState = ref({ column: null, order: null }); const paginationState = ref({ current: 1, pageSize: 20 }); +watch(pageSize, (next) => { + if (next > 0) paginationState.value.pageSize = next; +}, { immediate: true }); + function sortableCol(col, key) { return { ...col, @@ -670,8 +675,9 @@ const columns = computed(() => [ - + + + + + + + + Override Xray's default private-IP block (needed for LAN access through proxy) + + + @@ -947,6 +988,12 @@ function regenerateWgKeys() { + + + + + + diff --git a/web/service/config.json b/web/service/config.json index 8e7fb19a..c8b52d05 100644 --- a/web/service/config.json +++ b/web/service/config.json @@ -30,7 +30,10 @@ "outbounds": [{ "protocol": "freedom", "settings": { - "domainStrategy": "AsIs" + "domainStrategy": "AsIs", + "finalRules": [ + { "action": "allow", "ip": ["geoip:private"] } + ] }, "tag": "direct" }, diff --git a/web/service/inbound.go b/web/service/inbound.go index 7fab7c48..0a996952 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -2845,7 +2845,7 @@ func (s *InboundService) MigrationRequirements() { // Fix inbounds based problems var inbounds []*model.Inbound - err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error + err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria", "hysteria2"}).Find(&inbounds).Error if err != nil && err != gorm.ErrRecordNotFound { return } @@ -2924,6 +2924,12 @@ func (s *InboundService) MigrationRequirements() { } } } + + // Heal clients table for installs where the one-shot seeder + // skipped clients due to a tgId-string unmarshal error. + if syncErr := s.clientService.SyncInbound(tx, inbounds[inbound_index].Id, modelClients); syncErr != nil { + logger.Warning("MigrationRequirements sync clients failed:", syncErr) + } } tx.Save(inbounds) diff --git a/web/web.go b/web/web.go index 35516e4d..e903a016 100644 --- a/web/web.go +++ b/web/web.go @@ -154,7 +154,8 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine := gin.Default() directHTTPS := s.isDirectHTTPSConfigured() - engine.Use(middleware.SecurityHeadersMiddleware(directHTTPS)) + sendHSTS := directHTTPS && !config.IsSkipHSTS() + engine.Use(middleware.SecurityHeadersMiddleware(sendHSTS)) webDomain, err := s.settingService.GetWebDomain() if err != nil {