mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 21:24:10 +00:00
Merge branch 'main' into bash
This commit is contained in:
commit
b5cb069a07
14 changed files with 419 additions and 158 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
184
frontend/package-lock.json
generated
184
frontend/package-lock.json
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(() => [
|
|||
</a-select>
|
||||
</div>
|
||||
|
||||
<a-table v-if="!isMobile" :columns="columns" :data-source="sortedClients" :loading="loading" row-key="email"
|
||||
:row-selection="rowSelection" :pagination="tablePagination" size="small" @change="onTableChange">
|
||||
<a-table v-if="!isMobile" :columns="columns" :data-source="sortedClients" :loading="loading"
|
||||
row-key="email" :row-selection="rowSelection" :pagination="tablePagination" size="small"
|
||||
@change="onTableChange">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'email'">
|
||||
<div class="email-cell">
|
||||
|
|
@ -842,6 +848,11 @@ const columns = computed(() => [
|
|||
background: var(--bg-page);
|
||||
}
|
||||
|
||||
.clients-page :deep(.ant-pagination-options-size-changer),
|
||||
.clients-page :deep(.ant-pagination-options-size-changer .ant-select-selector) {
|
||||
min-width: 100px !important;
|
||||
}
|
||||
|
||||
.clients-page.is-dark {
|
||||
--bg-page: #1e1e1e;
|
||||
--bg-card: #252526;
|
||||
|
|
@ -874,7 +885,7 @@ const columns = computed(() => [
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.filter-bar.mobile > * {
|
||||
.filter-bar.mobile>* {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
|
@ -911,11 +922,25 @@ const columns = computed(() => [
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.dot-green { background: #52c41a; }
|
||||
.dot-blue { background: #1677ff; }
|
||||
.dot-red { background: #ff4d4f; }
|
||||
.dot-orange { background: #fa8c16; }
|
||||
.dot-gray { background: rgba(128, 128, 128, 0.6); }
|
||||
.dot-green {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.dot-blue {
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.dot-red {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
|
||||
.dot-orange {
|
||||
background: #fa8c16;
|
||||
}
|
||||
|
||||
.dot-gray {
|
||||
background: rgba(128, 128, 128, 0.6);
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
margin: 0 0 0 4px;
|
||||
|
|
@ -1050,8 +1075,6 @@ const columns = computed(() => [
|
|||
</style>
|
||||
|
||||
<style>
|
||||
/* AD-Vue popovers teleport their content to <body>, so scoped styles
|
||||
don't reach them — this block has to be unscoped. */
|
||||
.client-email-list {
|
||||
max-height: 280px;
|
||||
min-width: 160px;
|
||||
|
|
@ -1059,9 +1082,17 @@ const columns = computed(() => [
|
|||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.client-email-list > div {
|
||||
.client-email-list>div {
|
||||
padding: 2px 0;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ant-select-dropdown:has(.ant-select-item-option[title$="/ page"]) {
|
||||
min-width: 110px !important;
|
||||
}
|
||||
|
||||
.ant-select-dropdown:has(.ant-select-item-option[title$="/ page"]) .ant-select-item-option-content {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export function useClients() {
|
|||
const tgBotEnable = ref(false);
|
||||
const expireDiff = ref(0);
|
||||
const trafficDiff = ref(0);
|
||||
const pageSize = ref(0);
|
||||
|
||||
async function refresh() {
|
||||
loading.value = true;
|
||||
|
|
@ -48,6 +49,7 @@ export function useClients() {
|
|||
tgBotEnable.value = !!s.tgBotEnable;
|
||||
expireDiff.value = (s.expireDiff ?? 0) * 86400000;
|
||||
trafficDiff.value = (s.trafficDiff ?? 0) * 1073741824;
|
||||
pageSize.value = s.pageSize ?? 0;
|
||||
}
|
||||
|
||||
async function create(payload) {
|
||||
|
|
@ -199,6 +201,7 @@ export function useClients() {
|
|||
tgBotEnable,
|
||||
expireDiff,
|
||||
trafficDiff,
|
||||
pageSize,
|
||||
refresh,
|
||||
create,
|
||||
update,
|
||||
|
|
|
|||
|
|
@ -19,41 +19,20 @@ import { useDatepicker } from '@/composables/useDatepicker.js';
|
|||
const { t } = useI18n();
|
||||
const { datepicker } = useDatepicker();
|
||||
|
||||
// One modal handles every protocol's info / share view because the
|
||||
// legacy template did the same. The big v-if forks at the top decide
|
||||
// which sub-block of the body renders:
|
||||
// • multi-user inbound (VMess/VLess/Trojan/SS-multi/Hysteria) → per-
|
||||
// client row + share links
|
||||
// • SS single-user → connection details + share link
|
||||
// • WireGuard → secret/peers + per-peer config download
|
||||
// • Mixed/HTTP/Tunnel → connection details only
|
||||
//
|
||||
// We display links via QrPanel — each link gets its own QR + copy +
|
||||
// (for WireGuard configs) download button.
|
||||
|
||||
const props = defineProps({
|
||||
open: { type: Boolean, default: false },
|
||||
// Result of inbounds-page checkFallback() so the link-gen sees the
|
||||
// root inbound's listen/port/security when the dbInbound is a
|
||||
// domain-socket fallback (`@<name>`).
|
||||
dbInbound: { type: Object, default: null },
|
||||
// Index into inbound.clients to focus on for multi-user inbounds.
|
||||
clientIndex: { type: Number, default: 0 },
|
||||
// Sidecar config the legacy panel keyed off `app.*`.
|
||||
remarkModel: { type: String, default: '-ieo' },
|
||||
expireDiff: { type: Number, default: 0 },
|
||||
trafficDiff: { type: Number, default: 0 },
|
||||
ipLimitEnable: { type: Boolean, default: false },
|
||||
tgBotEnable: { type: Boolean, default: false },
|
||||
// Address of the node hosting this inbound; '' for local. Wired
|
||||
// through to share/QR link generation so node-managed inbounds
|
||||
// produce links that connect to the node, not the central panel.
|
||||
nodeAddress: { type: String, default: '' },
|
||||
subSettings: {
|
||||
type: Object,
|
||||
default: () => ({ enable: false, subURI: '', subJsonURI: '', subJsonEnable: false }),
|
||||
},
|
||||
// Email -> ts (last-online unix-ms) map fetched at the page level.
|
||||
lastOnlineMap: { type: Object, default: () => ({}) },
|
||||
});
|
||||
|
||||
|
|
@ -598,7 +577,8 @@ const showSubscriptionTab = computed(
|
|||
<div v-if="inbound.settings.gateway?.length" class="info-row">
|
||||
<dt>Gateway</dt>
|
||||
<dd><a-tag v-for="(ip, j) in inbound.settings.gateway" :key="`tun-i-gw-${j}`" color="green"
|
||||
class="value-tag">{{ ip }}</a-tag></dd>
|
||||
class="value-tag">{{
|
||||
ip }}</a-tag></dd>
|
||||
</div>
|
||||
<div v-if="inbound.settings.dns?.length" class="info-row">
|
||||
<dt>DNS</dt>
|
||||
|
|
@ -612,7 +592,8 @@ const showSubscriptionTab = computed(
|
|||
<div v-if="inbound.settings.autoSystemRoutingTable?.length" class="info-row">
|
||||
<dt>Auto system routes</dt>
|
||||
<dd><a-tag v-for="(cidr, j) in inbound.settings.autoSystemRoutingTable" :key="`tun-i-rt-${j}`"
|
||||
color="green">{{ cidr }}</a-tag></dd>
|
||||
color="green">{{
|
||||
cidr }}</a-tag></dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
|
|
@ -670,12 +651,101 @@ const showSubscriptionTab = computed(
|
|||
<span class="account-sep">:</span>
|
||||
<a-tag class="value-tag">{{ account.pass }}</a-tag>
|
||||
<a-tooltip :title="t('copy')">
|
||||
<a-button size="small" @click="copyText(`${account.user}:${account.pass}`)">
|
||||
<template #icon>
|
||||
<CopyOutlined />
|
||||
</template>
|
||||
<a-button size="small" type="text"
|
||||
@click="copyText(`${account.user}:${account.pass}`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-space :size="4" wrap class="share-buttons share-desktop">
|
||||
<a-tooltip :title="`socks5://${dbInbound.address}:${dbInbound.port}@${account.user}:${account.pass}`">
|
||||
<a-button size="small"
|
||||
@click="copyText(`socks5://${dbInbound.address}:${dbInbound.port}@${account.user}:${account.pass}`)">
|
||||
SOCKS5
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="`http://${dbInbound.address}:${dbInbound.port}@${account.user}:${account.pass}`">
|
||||
<a-button size="small"
|
||||
@click="copyText(`http://${dbInbound.address}:${dbInbound.port}@${account.user}:${account.pass}`)">
|
||||
HTTP
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="https://t.me/socks?server=...&port=...&user=...&pass=...">
|
||||
<a-button size="small"
|
||||
@click="copyText(`https://t.me/socks?server=${encodeURIComponent(dbInbound.address)}&port=${dbInbound.port}&user=${encodeURIComponent(account.user)}&pass=${encodeURIComponent(account.pass)}`)">
|
||||
Telegram
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<a-dropdown :trigger="['click']" class="share-mobile">
|
||||
<a-button size="small">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
{{ t('copy') }}
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }) => {
|
||||
const h = dbInbound.address;
|
||||
const port = dbInbound.port;
|
||||
if (key === 'telegram') {
|
||||
copyText(`https://t.me/socks?server=${encodeURIComponent(h)}&port=${port}&user=${encodeURIComponent(account.user)}&pass=${encodeURIComponent(account.pass)}`);
|
||||
} else {
|
||||
copyText(`${key}://${h}:${port}@${account.user}:${account.pass}`);
|
||||
}
|
||||
}">
|
||||
<a-menu-item key="socks5">SOCKS5</a-menu-item>
|
||||
<a-menu-item key="http">HTTP</a-menu-item>
|
||||
<a-menu-item key="telegram">Telegram</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</dd>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.settings.auth === 'noauth'">
|
||||
<div class="info-row">
|
||||
<dt>{{ t('copy') }}</dt>
|
||||
<dd>
|
||||
<a-space :size="4" wrap class="share-buttons share-desktop">
|
||||
<a-tooltip :title="`socks5://${dbInbound.address}:${dbInbound.port}`">
|
||||
<a-button size="small"
|
||||
@click="copyText(`socks5://${dbInbound.address}:${dbInbound.port}`)">
|
||||
SOCKS5
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="`http://${dbInbound.address}:${dbInbound.port}`">
|
||||
<a-button size="small"
|
||||
@click="copyText(`http://${dbInbound.address}:${dbInbound.port}`)">
|
||||
HTTP
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="https://t.me/socks?server=...&port=...">
|
||||
<a-button size="small"
|
||||
@click="copyText(`https://t.me/socks?server=${encodeURIComponent(dbInbound.address)}&port=${dbInbound.port}`)">
|
||||
Telegram
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
<a-dropdown :trigger="['click']" class="share-mobile">
|
||||
<a-button size="small">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
{{ t('copy') }}
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }) => {
|
||||
const h = dbInbound.address;
|
||||
const port = dbInbound.port;
|
||||
if (key === 'telegram') {
|
||||
copyText(`https://t.me/socks?server=${encodeURIComponent(h)}&port=${port}`);
|
||||
} else {
|
||||
copyText(`${key}://${h}:${port}`);
|
||||
}
|
||||
}">
|
||||
<a-menu-item key="socks5">SOCKS5</a-menu-item>
|
||||
<a-menu-item key="http">HTTP</a-menu-item>
|
||||
<a-menu-item key="telegram">Telegram</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</dd>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -897,6 +967,7 @@ const showSubscriptionTab = computed(
|
|||
white-space: normal;
|
||||
word-break: break-all;
|
||||
display: inline-block;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.value-block {
|
||||
|
|
@ -927,6 +998,27 @@ const showSubscriptionTab = computed(
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.share-buttons,
|
||||
.share-mobile {
|
||||
margin-inline-start: 4px;
|
||||
padding-inline-start: 8px;
|
||||
border-inline-start: 1px solid rgba(128, 128, 128, 0.25);
|
||||
}
|
||||
|
||||
.share-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.share-desktop {
|
||||
display: none !important;
|
||||
}
|
||||
.share-mobile {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.security-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -469,6 +469,13 @@ function cycleTheme() {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.login-form :deep(input.ant-input:-webkit-autofill) {
|
||||
-webkit-text-fill-color: var(--color-text) !important;
|
||||
-webkit-box-shadow: 0 0 0 1000px var(--bg-card) inset !important;
|
||||
box-shadow: 0 0 0 1000px var(--bg-card) inset !important;
|
||||
transition: background-color 9999s ease-in-out 0s, color 9999s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.submit-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ const shadowrocketUrl = computed(() => {
|
|||
if (!subUrl) return '';
|
||||
const separator = subUrl.includes('?') ? '&' : '?';
|
||||
const rawUrl = subUrl + separator + 'flag=shadowrocket';
|
||||
const base64Url = encodeURIComponent(btoa(rawUrl));
|
||||
const base64Url = btoa(rawUrl).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
const remark = encodeURIComponent(subTitle || sId || 'Subscription');
|
||||
return `shadowrocket://add/sub/${base64Url}?remark=${remark}`;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -328,6 +328,47 @@ function regenerateWgKeys() {
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item label="Final Rules">
|
||||
<a-button size="small" type="primary" @click="outbound.settings.addFinalRule('allow')">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<span class="ml-8" style="opacity: 0.6;">
|
||||
Override Xray's default private-IP block (needed for LAN access through proxy)
|
||||
</span>
|
||||
</a-form-item>
|
||||
<template v-for="(rule, index) in outbound.settings.finalRules || []" :key="`fr-${index}`">
|
||||
<a-form-item :wrapper-col="{ md: { span: 14, offset: 8 } }" :colon="false">
|
||||
<div class="item-heading">
|
||||
<span>Rule {{ index + 1 }}</span>
|
||||
<DeleteOutlined class="danger-icon" @click="outbound.settings.delFinalRule(index)" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="Action">
|
||||
<a-select v-model:value="rule.action">
|
||||
<a-select-option v-for="x in ['allow', 'block']" :key="x" :value="x">{{ x }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Network">
|
||||
<a-select v-model:value="rule.network" allow-clear placeholder="(any)">
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
<a-select-option value="tcp,udp">tcp,udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Port">
|
||||
<a-input v-model:value="rule.port" placeholder="e.g. 80,443 or 1000-2000" />
|
||||
</a-form-item>
|
||||
<a-form-item label="IP / CIDR / geoip">
|
||||
<a-select v-model:value="rule.ip" mode="tags" :token-separators="[',', ' ']"
|
||||
placeholder="e.g. 10.0.0.0/8, geoip:private, ext:cn.dat:cn" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="rule.action === 'block'" label="Block delay (ms)">
|
||||
<a-input v-model:value="rule.blockDelay" placeholder="optional: 5000-10000" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- ============== Blackhole ============== -->
|
||||
|
|
@ -947,6 +988,12 @@ function regenerateWgKeys() {
|
|||
<a-form-item label="Penetrate">
|
||||
<a-switch v-model:checked="outbound.stream.sockopt.penetrate" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Mark (fwmark)">
|
||||
<a-input-number v-model:value="outbound.stream.sockopt.mark" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Interface">
|
||||
<a-input v-model:value="outbound.stream.sockopt.interfaceName" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@
|
|||
"outbounds": [{
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "AsIs"
|
||||
"domainStrategy": "AsIs",
|
||||
"finalRules": [
|
||||
{ "action": "allow", "ip": ["geoip:private"] }
|
||||
]
|
||||
},
|
||||
"tag": "direct"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue