diff --git a/database/model/model.go b/database/model/model.go
index 63f4a1b4..5fa934c0 100644
--- a/database/model/model.go
+++ b/database/model/model.go
@@ -21,6 +21,7 @@ const (
Shadowsocks Protocol = "shadowsocks"
Mixed Protocol = "mixed"
WireGuard Protocol = "wireguard"
+ Hysteria Protocol = "hysteria"
)
// User represents a user account in the 3x-ui panel.
@@ -118,10 +119,11 @@ type CustomGeoResource struct {
// Client represents a client configuration for Xray inbounds with traffic limits and settings.
type Client struct {
- ID string `json:"id"` // Unique client identifier
+ ID string `json:"id,omitempty"` // Unique client identifier
Security string `json:"security"` // Security method (e.g., "auto", "aes-128-gcm")
- Password string `json:"password"` // Client password
- Flow string `json:"flow"` // Flow control (XTLS)
+ Password string `json:"password,omitempty"` // Client password
+ Flow string `json:"flow,omitempty"` // Flow control (XTLS)
+ Auth string `json:"auth,omitempty"` // Auth password (Hysteria)
Email string `json:"email"` // Client email identifier
LimitIP int `json:"limitIp"` // IP limit for this client
TotalGB int64 `json:"totalGB" form:"totalGB"` // Total traffic limit in GB
diff --git a/sub/subJsonService.go b/sub/subJsonService.go
index 05552fe8..7ce93e22 100644
--- a/sub/subJsonService.go
+++ b/sub/subJsonService.go
@@ -194,6 +194,8 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
newOutbounds = append(newOutbounds, s.genVless(inbound, streamSettings, client))
case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
+ case "hysteria":
+ newOutbounds = append(newOutbounds, s.genHy(inbound, newStream, client))
}
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
@@ -389,6 +391,49 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
return result
}
+func (s *SubJsonService) genHy(inbound *model.Inbound, newStream map[string]any, client model.Client) json_util.RawMessage {
+ outbound := Outbound{}
+
+ outbound.Protocol = string(inbound.Protocol)
+ outbound.Tag = "proxy"
+
+ if s.mux != "" {
+ outbound.Mux = json_util.RawMessage(s.mux)
+ }
+
+ var settings, stream map[string]any
+ json.Unmarshal([]byte(inbound.Settings), &settings)
+ version, _ := settings["version"].(float64)
+ outbound.Settings = map[string]any{
+ "version": int(version),
+ "address": inbound.Listen,
+ "port": inbound.Port,
+ }
+
+ json.Unmarshal([]byte(inbound.StreamSettings), &stream)
+ hyStream := stream["hysteriaSettings"].(map[string]any)
+ outHyStream := map[string]any{
+ "version": int(version),
+ "auth": client.Auth,
+ }
+ if udpIdleTimeout, ok := hyStream["udpIdleTimeout"].(float64); ok {
+ outHyStream["udpIdleTimeout"] = int(udpIdleTimeout)
+ }
+ newStream["hysteriaSettings"] = outHyStream
+
+ if finalmask, ok := hyStream["finalmask"].(map[string]any); ok {
+ newStream["finalmask"] = finalmask
+ }
+
+ newStream["network"] = "hysteria"
+ newStream["security"] = "tls"
+
+ outbound.StreamSettings, _ = json.MarshalIndent(newStream, "", " ")
+
+ result, _ := json.MarshalIndent(outbound, "", " ")
+ return result
+}
+
type Outbound struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
diff --git a/sub/subService.go b/sub/subService.go
index b6422dd4..a4d38485 100644
--- a/sub/subService.go
+++ b/sub/subService.go
@@ -120,7 +120,7 @@ func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error)
FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
WHERE
- protocol in ('vmess','vless','trojan','shadowsocks')
+ protocol in ('vmess','vless','trojan','shadowsocks','hysteria')
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
)`, subId, true).Find(&inbounds).Error
if err != nil {
@@ -171,6 +171,8 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
return s.genTrojanLink(inbound, email)
case "shadowsocks":
return s.genShadowsocksLink(inbound, email)
+ case "hysteria":
+ return s.genHysteriaLink(inbound, email)
}
return ""
}
@@ -885,6 +887,70 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
return url.String()
}
+func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
+ address := s.address
+ if inbound.Protocol != model.Hysteria {
+ return ""
+ }
+ var stream map[string]interface{}
+ json.Unmarshal([]byte(inbound.StreamSettings), &stream)
+ clients, _ := s.inboundService.GetClients(inbound)
+ clientIndex := -1
+ for i, client := range clients {
+ if client.Email == email {
+ clientIndex = i
+ break
+ }
+ }
+ auth := clients[clientIndex].Auth
+ port := inbound.Port
+ params := make(map[string]string)
+
+ params["security"] = "tls"
+ tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
+ alpns, _ := tlsSetting["alpn"].([]interface{})
+ var alpn []string
+ for _, a := range alpns {
+ alpn = append(alpn, a.(string))
+ }
+ if len(alpn) > 0 {
+ params["alpn"] = strings.Join(alpn, ",")
+ }
+ if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
+ params["sni"], _ = sniValue.(string)
+ }
+
+ tlsSettings, _ := searchKey(tlsSetting, "settings")
+ if tlsSetting != nil {
+ if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
+ params["fp"], _ = fpValue.(string)
+ }
+ if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
+ if insecure.(bool) {
+ params["insecure"] = "1"
+ }
+ }
+ }
+
+ var settings map[string]interface{}
+ json.Unmarshal([]byte(inbound.Settings), &settings)
+ version, _ := settings["version"].(float64)
+ protocol := "hysteria2"
+ if int(version) == 1 {
+ protocol = "hysteria"
+ }
+
+ link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, address, port)
+ url, _ := url.Parse(link)
+ q := url.Query()
+ for k, v := range params {
+ q.Add(k, v)
+ }
+ url.RawQuery = q.Encode()
+ url.Fragment = s.genRemark(inbound, email, "")
+ return url.String()
+}
+
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
separationChar := string(s.remarkModel[0])
orderChars := s.remarkModel[1:]
diff --git a/web/assets/js/model/dbinbound.js b/web/assets/js/model/dbinbound.js
index c347a7eb..e1802635 100644
--- a/web/assets/js/model/dbinbound.js
+++ b/web/assets/js/model/dbinbound.js
@@ -125,7 +125,7 @@ class DBInbound {
sniffing: sniffing,
clientStats: this.clientStats,
};
-
+
this._cachedInbound = Inbound.fromJson(config);
return this._cachedInbound;
}
@@ -147,6 +147,7 @@ class DBInbound {
case Protocols.VMESS:
case Protocols.VLESS:
case Protocols.TROJAN:
+ case Protocols.HYSTERIA:
return true;
case Protocols.SHADOWSOCKS:
return this.toInbound().isSSMultiUser;
@@ -161,6 +162,7 @@ class DBInbound {
case Protocols.VLESS:
case Protocols.TROJAN:
case Protocols.SHADOWSOCKS:
+ case Protocols.HYSTERIA:
return true;
default:
return false;
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index b6059cf7..f74b2736 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -8,6 +8,7 @@ const Protocols = {
HTTP: 'http',
WIREGUARD: 'wireguard',
TUN: 'tun',
+ HYSTERIA: 'hysteria',
};
const SSMethods = {
@@ -589,6 +590,106 @@ class xHTTPStreamSettings extends XrayCommonClass {
}
}
+class HysteriaStreamSettings extends XrayCommonClass {
+ constructor(
+ protocol,
+ version = 2,
+ auth = '',
+ udpIdleTimeout = 60,
+ masquerade,
+ ) {
+ super(protocol);
+ this.version = version;
+ this.auth = auth;
+ this.udpIdleTimeout = udpIdleTimeout;
+ this.masquerade = masquerade;
+ }
+
+ static fromJson(json = {}) {
+ return new HysteriaStreamSettings(
+ json.protocol,
+ json.version ?? 2,
+ json.auth ?? '',
+ json.udpIdleTimeout ?? 60,
+ json.masquerade ? HysteriaMasquerade.fromJson(json.masquerade) : undefined,
+ );
+ }
+
+ toJson() {
+ return {
+ protocol: this.protocol,
+ version: this.version,
+ auth: this.auth,
+ udpIdleTimeout: this.udpIdleTimeout,
+ masquerade: this.masqueradeSwitch ? this.masquerade.toJson() : undefined,
+ };
+ }
+
+ get masqueradeSwitch() {
+ return this.masquerade != undefined;
+ }
+
+ set masqueradeSwitch(value) {
+ this.masquerade = value ? new HysteriaMasquerade() : undefined;
+ }
+};
+
+class HysteriaMasquerade extends XrayCommonClass {
+ constructor(
+ type = 'proxy',
+ dir = '',
+ url = '',
+ rewriteHost = false,
+ insecure = false,
+ content = '',
+ headers = [],
+ statusCode = 0,
+ ) {
+ super();
+ this.type = type;
+ this.dir = dir;
+ this.url = url;
+ this.rewriteHost = rewriteHost;
+ this.insecure = insecure;
+ this.content = content;
+ this.headers = headers;
+ this.statusCode = statusCode;
+ }
+
+ addHeader(name, value) {
+ this.headers.push({ name: name, value: value });
+ }
+
+ removeHeader(index) {
+ this.headers.splice(index, 1);
+ }
+
+ static fromJson(json = {}) {
+ return new HysteriaMasquerade(
+ json.type,
+ json.dir,
+ json.url,
+ json.rewriteHost,
+ json.insecure,
+ json.content,
+ XrayCommonClass.toHeaders(json.headers),
+ json.statusCode,
+ );
+ }
+
+ toJson() {
+ return {
+ type: this.type,
+ dir: this.dir,
+ url: this.url,
+ rewriteHost: this.rewriteHost,
+ insecure: this.insecure,
+ content: this.content,
+ headers: XrayCommonClass.toV2Headers(this.headers, false),
+ statusCode: this.statusCode,
+ };
+ }
+};
class TlsStreamSettings extends XrayCommonClass {
constructor(
serverName = '',
@@ -987,6 +1088,12 @@ class UdpMask extends XrayCommonClass {
case 'header-wechat':
case 'header-wireguard':
return {};
+ case 'header-custom':
+ return { client: [], server: [] };
+ case 'noise':
+ return { reset: 0, noise: [] };
+ case 'sudoku':
+ return { ascii: '', customTable: '', customTables: [], paddingMin: 0, paddingMax: 0 };
default:
return settings;
}
@@ -1021,7 +1128,6 @@ class FinalMaskStreamSettings extends XrayCommonClass {
return {
udp: this.udp.map(udp => udp.toJson())
};
-
}
}
@@ -1037,6 +1143,7 @@ class StreamSettings extends XrayCommonClass {
grpcSettings = new GrpcStreamSettings(),
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
+ hysteriaSettings = new HysteriaStreamSettings(),
finalmask = new FinalMaskStreamSettings(),
sockopt = undefined,
) {
@@ -1052,6 +1159,7 @@ class StreamSettings extends XrayCommonClass {
this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.xhttp = xhttpSettings;
+ this.hysteria = hysteriaSettings;
this.finalmask = finalmask;
this.sockopt = sockopt;
}
@@ -1116,6 +1224,7 @@ class StreamSettings extends XrayCommonClass {
GrpcStreamSettings.fromJson(json.grpcSettings),
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
xHTTPStreamSettings.fromJson(json.xhttpSettings),
+ HysteriaStreamSettings.fromJson(json.hysteriaSettings),
FinalMaskStreamSettings.fromJson(json.finalmask),
SockoptStreamSettings.fromJson(json.sockopt),
);
@@ -1135,6 +1244,7 @@ class StreamSettings extends XrayCommonClass {
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
+ hysteriaSettings: network === 'hysteria' ? this.hysteria.toJson() : undefined,
finalmask: this.hasFinalMask ? this.finalmask.toJson() : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
@@ -1201,6 +1311,7 @@ class Inbound extends XrayCommonClass {
case Protocols.VLESS: return this.settings.vlesses;
case Protocols.TROJAN: return this.settings.trojans;
case Protocols.SHADOWSOCKS: return this.isSSMultiUser ? this.settings.shadowsockses : null;
+ case Protocols.HYSTERIA: return this.settings.hysterias;
default: return null;
}
}
@@ -1212,9 +1323,14 @@ class Inbound extends XrayCommonClass {
set protocol(protocol) {
this._protocol = protocol;
this.settings = Inbound.Settings.getSettings(protocol);
+ this.stream = new StreamSettings();
if (protocol === Protocols.TROJAN) {
this.tls = false;
}
+ if (protocol === Protocols.HYSTERIA) {
+ this.stream.network = 'hysteria';
+ this.stream.security = 'tls';
+ }
}
get network() {
@@ -1316,6 +1432,7 @@ class Inbound extends XrayCommonClass {
}
canEnableTls() {
+ if (this.protocol === Protocols.HYSTERIA) return true;
if (![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.network);
}
@@ -1342,7 +1459,7 @@ class Inbound extends XrayCommonClass {
}
canEnableStream() {
- return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
+ return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS, Protocols.HYSTERIA].includes(this.protocol);
}
reset() {
@@ -1689,6 +1806,26 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
+ genHysteriaLink(address = '', port = this.port, remark = '', clientAuth) {
+ const protocol = this.settings.version == 2 ? "hysteria2" : "hysteria";
+ const link = `${protocol}://${clientAuth}@${address}:${port}`;
+
+ const params = new Map();
+ params.set("security", "tls");
+ if (this.stream.tls.settings.fingerprint?.length > 0) params.set("fp", this.stream.tls.settings.fingerprint);
+ if (this.stream.tls.alpn?.length > 0) params.set("alpn", this.stream.tls.alpn);
+ if (this.stream.tls.settings.allowInsecure) params.set("insecure", "1");
+ if (this.stream.tls.settings.echConfigList?.length > 0) params.set("ech", this.stream.tls.settings.echConfigList.join(','));
+ if (this.stream.tls.sni?.length > 0) params.set("sni", this.stream.tls.sni);
+
+ const url = new URL(link);
+ for (const [key, value] of params) {
+ url.searchParams.set(key, value);
+ }
+ url.hash = encodeURIComponent(remark);
+ return url.toString();
+ }
+
getWireguardLink(address, port, remark, peerId) {
let txt = `[Interface]\n`
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
@@ -1721,6 +1858,8 @@ class Inbound extends XrayCommonClass {
return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : '');
case Protocols.TROJAN:
return this.genTrojanLink(address, port, forceTls, remark, client.password);
+ case Protocols.HYSTERIA:
+ return this.genHysteriaLink(address, port, remark, client.auth.length > 0 ? client.auth : this.stream.hysteria.auth);
default: return '';
}
}
@@ -1827,6 +1966,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
case Protocols.TUN: return new Inbound.TunSettings(protocol);
+ case Protocols.HYSTERIA: return new Inbound.HysteriaSettings(protocol);
default: return null;
}
}
@@ -1842,6 +1982,7 @@ Inbound.Settings = class extends XrayCommonClass {
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
case Protocols.TUN: return Inbound.TunSettings.fromJson(json);
+ case Protocols.HYSTERIA: return Inbound.HysteriaSettings.fromJson(json);
default: return null;
}
}
@@ -1851,6 +1992,94 @@ Inbound.Settings = class extends XrayCommonClass {
}
};
+/** Shared user-quota fields and UI helpers for multi-user protocol clients. */
+Inbound.ClientBase = class extends XrayCommonClass {
+ constructor(
+ email = RandomUtil.randomLowerAndNum(8),
+ limitIp = 0,
+ totalGB = 0,
+ expiryTime = 0,
+ enable = true,
+ tgId = '',
+ subId = RandomUtil.randomLowerAndNum(16),
+ comment = '',
+ reset = 0,
+ created_at = undefined,
+ updated_at = undefined,
+ ) {
+ super();
+ this.email = email;
+ this.limitIp = limitIp;
+ this.totalGB = totalGB;
+ this.expiryTime = expiryTime;
+ this.enable = enable;
+ this.tgId = tgId;
+ this.subId = subId;
+ this.comment = comment;
+ this.reset = reset;
+ this.created_at = created_at;
+ this.updated_at = updated_at;
+ }
+
+ static commonArgsFromJson(json = {}) {
+ return [
+ json.email,
+ json.limitIp,
+ json.totalGB,
+ json.expiryTime,
+ json.enable,
+ json.tgId,
+ json.subId,
+ json.comment,
+ json.reset,
+ json.created_at,
+ json.updated_at,
+ ];
+ }
+
+ _clientBaseToJson() {
+ return {
+ email: this.email,
+ limitIp: this.limitIp,
+ totalGB: this.totalGB,
+ expiryTime: this.expiryTime,
+ enable: this.enable,
+ tgId: this.tgId,
+ subId: this.subId,
+ comment: this.comment,
+ reset: this.reset,
+ created_at: this.created_at,
+ updated_at: this.updated_at,
+ };
+ }
+
+ get _expiryTime() {
+ if (this.expiryTime === 0 || this.expiryTime === '') {
+ return null;
+ }
+ if (this.expiryTime < 0) {
+ return this.expiryTime / -86400000;
+ }
+ return moment(this.expiryTime);
+ }
+
+ set _expiryTime(t) {
+ if (t == null || t === '') {
+ this.expiryTime = 0;
+ } else {
+ this.expiryTime = t.valueOf();
+ }
+ }
+
+ get _totalGB() {
+ return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
+ }
+
+ set _totalGB(gb) {
+ this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
+ }
+};
+
Inbound.VmessSettings = class extends Inbound.Settings {
constructor(protocol,
vmesses = [new Inbound.VmessSettings.VMESS()]) {
@@ -1879,7 +2108,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
static fromJson(json = {}) {
return new Inbound.VmessSettings(
Protocols.VMESS,
- json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)),
+ (json.clients || []).map(client => Inbound.VmessSettings.VMESS.fromJson(client)),
);
}
@@ -1890,80 +2119,32 @@ Inbound.VmessSettings = class extends Inbound.Settings {
}
};
-Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
+Inbound.VmessSettings.VMESS = class extends Inbound.ClientBase {
constructor(
id = RandomUtil.randomUUID(),
security = USERS_SECURITY.AUTO,
- email = RandomUtil.randomLowerAndNum(8),
- limitIp = 0,
- totalGB = 0,
- expiryTime = 0,
- enable = true,
- tgId = '',
- subId = RandomUtil.randomLowerAndNum(16),
- comment = '',
- reset = 0,
- created_at = undefined,
- updated_at = undefined
+ email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at,
) {
- super();
+ super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at);
this.id = id;
this.security = security;
- this.email = email;
- this.limitIp = limitIp;
- this.totalGB = totalGB;
- this.expiryTime = expiryTime;
- this.enable = enable;
- this.tgId = tgId;
- this.subId = subId;
- this.comment = comment;
- this.reset = reset;
- this.created_at = created_at;
- this.updated_at = updated_at;
}
static fromJson(json = {}) {
return new Inbound.VmessSettings.VMESS(
json.id,
json.security,
- json.email,
- json.limitIp,
- json.totalGB,
- json.expiryTime,
- json.enable,
- json.tgId,
- json.subId,
- json.comment,
- json.reset,
- json.created_at,
- json.updated_at,
+ ...Inbound.ClientBase.commonArgsFromJson(json),
);
}
- get _expiryTime() {
- if (this.expiryTime === 0 || this.expiryTime === "") {
- return null;
- }
- if (this.expiryTime < 0) {
- return this.expiryTime / -86400000;
- }
- return moment(this.expiryTime);
- }
- set _expiryTime(t) {
- if (t == null || t === "") {
- this.expiryTime = 0;
- } else {
- this.expiryTime = t.valueOf();
- }
+ toJson() {
+ return {
+ id: this.id,
+ security: this.security,
+ ...this._clientBaseToJson(),
+ };
}
- get _totalGB() {
- return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
- }
-
- set _totalGB(gb) {
- this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
- }
-
};
Inbound.VLESSSettings = class extends Inbound.Settings {
@@ -2041,85 +2222,36 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
return json;
}
-
-
};
-Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
+Inbound.VLESSSettings.VLESS = class extends Inbound.ClientBase {
constructor(
id = RandomUtil.randomUUID(),
flow = '',
- email = RandomUtil.randomLowerAndNum(8),
- limitIp = 0,
- totalGB = 0,
- expiryTime = 0,
- enable = true,
- tgId = '',
- subId = RandomUtil.randomLowerAndNum(16),
- comment = '',
- reset = 0,
- created_at = undefined,
- updated_at = undefined
+ email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at,
) {
- super();
+ super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at);
this.id = id;
this.flow = flow;
- this.email = email;
- this.limitIp = limitIp;
- this.totalGB = totalGB;
- this.expiryTime = expiryTime;
- this.enable = enable;
- this.tgId = tgId;
- this.subId = subId;
- this.comment = comment;
- this.reset = reset;
- this.created_at = created_at;
- this.updated_at = updated_at;
}
static fromJson(json = {}) {
return new Inbound.VLESSSettings.VLESS(
json.id,
json.flow,
- json.email,
- json.limitIp,
- json.totalGB,
- json.expiryTime,
- json.enable,
- json.tgId,
- json.subId,
- json.comment,
- json.reset,
- json.created_at,
- json.updated_at,
+ ...Inbound.ClientBase.commonArgsFromJson(json),
);
}
- get _expiryTime() {
- if (this.expiryTime === 0 || this.expiryTime === "") {
- return null;
- }
- if (this.expiryTime < 0) {
- return this.expiryTime / -86400000;
- }
- return moment(this.expiryTime);
- }
-
- set _expiryTime(t) {
- if (t == null || t === "") {
- this.expiryTime = 0;
- } else {
- this.expiryTime = t.valueOf();
- }
- }
- get _totalGB() {
- return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
- }
-
- set _totalGB(gb) {
- this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
+ toJson() {
+ return {
+ id: this.id,
+ flow: this.flow,
+ ...this._clientBaseToJson(),
+ };
}
};
+
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
constructor(name = "", alpn = '', path = '', dest = '', xver = 0) {
super();
@@ -2179,7 +2311,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
static fromJson(json = {}) {
return new Inbound.TrojanSettings(
Protocols.TROJAN,
- json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
+ (json.clients || []).map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
}
@@ -2191,95 +2323,28 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
}
};
-Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
+Inbound.TrojanSettings.Trojan = class extends Inbound.ClientBase {
constructor(
password = RandomUtil.randomSeq(10),
- email = RandomUtil.randomLowerAndNum(8),
- limitIp = 0,
- totalGB = 0,
- expiryTime = 0,
- enable = true,
- tgId = '',
- subId = RandomUtil.randomLowerAndNum(16),
- comment = '',
- reset = 0,
- created_at = undefined,
- updated_at = undefined
+ email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at,
) {
- super();
+ super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at);
this.password = password;
- this.email = email;
- this.limitIp = limitIp;
- this.totalGB = totalGB;
- this.expiryTime = expiryTime;
- this.enable = enable;
- this.tgId = tgId;
- this.subId = subId;
- this.comment = comment;
- this.reset = reset;
- this.created_at = created_at;
- this.updated_at = updated_at;
}
toJson() {
return {
password: this.password,
- email: this.email,
- limitIp: this.limitIp,
- totalGB: this.totalGB,
- expiryTime: this.expiryTime,
- enable: this.enable,
- tgId: this.tgId,
- subId: this.subId,
- comment: this.comment,
- reset: this.reset,
- created_at: this.created_at,
- updated_at: this.updated_at,
+ ...this._clientBaseToJson(),
};
}
static fromJson(json = {}) {
return new Inbound.TrojanSettings.Trojan(
json.password,
- json.email,
- json.limitIp,
- json.totalGB,
- json.expiryTime,
- json.enable,
- json.tgId,
- json.subId,
- json.comment,
- json.reset,
- json.created_at,
- json.updated_at,
+ ...Inbound.ClientBase.commonArgsFromJson(json),
);
}
-
- get _expiryTime() {
- if (this.expiryTime === 0 || this.expiryTime === "") {
- return null;
- }
- if (this.expiryTime < 0) {
- return this.expiryTime / -86400000;
- }
- return moment(this.expiryTime);
- }
-
- set _expiryTime(t) {
- if (t == null || t === "") {
- this.expiryTime = 0;
- } else {
- this.expiryTime = t.valueOf();
- }
- }
- get _totalGB() {
- return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
- }
-
- set _totalGB(gb) {
- this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
- }
-
};
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
@@ -2343,7 +2408,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
json.method,
json.password,
json.network,
- json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
+ (json.clients || []).map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
json.ivCheck,
);
}
@@ -2359,53 +2424,22 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
}
};
-Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
+Inbound.ShadowsocksSettings.Shadowsocks = class extends Inbound.ClientBase {
constructor(
method = '',
password = RandomUtil.randomShadowsocksPassword(),
- email = RandomUtil.randomLowerAndNum(8),
- limitIp = 0,
- totalGB = 0,
- expiryTime = 0,
- enable = true,
- tgId = '',
- subId = RandomUtil.randomLowerAndNum(16),
- comment = '',
- reset = 0,
- created_at = undefined,
- updated_at = undefined
+ email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at,
) {
- super();
+ super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at);
this.method = method;
this.password = password;
- this.email = email;
- this.limitIp = limitIp;
- this.totalGB = totalGB;
- this.expiryTime = expiryTime;
- this.enable = enable;
- this.tgId = tgId;
- this.subId = subId;
- this.comment = comment;
- this.reset = reset;
- this.created_at = created_at;
- this.updated_at = updated_at;
}
toJson() {
return {
method: this.method,
password: this.password,
- email: this.email,
- limitIp: this.limitIp,
- totalGB: this.totalGB,
- expiryTime: this.expiryTime,
- enable: this.enable,
- tgId: this.tgId,
- subId: this.subId,
- comment: this.comment,
- reset: this.reset,
- created_at: this.created_at,
- updated_at: this.updated_at,
+ ...this._clientBaseToJson(),
};
}
@@ -2413,45 +2447,56 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
return new Inbound.ShadowsocksSettings.Shadowsocks(
json.method,
json.password,
- json.email,
- json.limitIp,
- json.totalGB,
- json.expiryTime,
- json.enable,
- json.tgId,
- json.subId,
- json.comment,
- json.reset,
- json.created_at,
- json.updated_at,
+ ...Inbound.ClientBase.commonArgsFromJson(json),
+ );
+ }
+};
+
+Inbound.HysteriaSettings = class extends Inbound.Settings {
+ constructor(protocol, version = 2, hysterias = [new Inbound.HysteriaSettings.Hysteria()]) {
+ super(protocol);
+ this.version = version;
+ this.hysterias = hysterias;
+ }
+
+ static fromJson(json = {}) {
+ return new Inbound.HysteriaSettings(
+ Protocols.HYSTERIA,
+ json.version ?? 2,
+ (json.clients || []).map(client => Inbound.HysteriaSettings.Hysteria.fromJson(client)),
);
}
- get _expiryTime() {
- if (this.expiryTime === 0 || this.expiryTime === "") {
- return null;
- }
- if (this.expiryTime < 0) {
- return this.expiryTime / -86400000;
- }
- return moment(this.expiryTime);
+ toJson() {
+ return {
+ version: this.version,
+ clients: Inbound.HysteriaSettings.toJsonArray(this.hysterias),
+ };
+ }
+};
+
+Inbound.HysteriaSettings.Hysteria = class extends Inbound.ClientBase {
+ constructor(
+ auth = RandomUtil.randomSeq(10),
+ email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at,
+ ) {
+ super(email, limitIp, totalGB, expiryTime, enable, tgId, subId, comment, reset, created_at, updated_at);
+ this.auth = auth;
}
- set _expiryTime(t) {
- if (t == null || t === "") {
- this.expiryTime = 0;
- } else {
- this.expiryTime = t.valueOf();
- }
- }
- get _totalGB() {
- return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
+ toJson() {
+ return {
+ auth: this.auth,
+ ...this._clientBaseToJson(),
+ };
}
- set _totalGB(gb) {
- this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
+ static fromJson(json = {}) {
+ return new Inbound.HysteriaSettings.Hysteria(
+ json.auth,
+ ...Inbound.ClientBase.commonArgsFromJson(json),
+ );
}
-
};
Inbound.TunnelSettings = class extends Inbound.Settings {
@@ -2707,4 +2752,4 @@ Inbound.TunSettings = class extends Inbound.Settings {
userLevel: this.userLevel || 0,
};
}
-};
+};
\ No newline at end of file
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 56606231..b8ec4c12 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -782,8 +782,8 @@ class Outbound extends CommonClass {
}
canEnableTls() {
- if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.Hysteria].includes(this.protocol)) return false;
- if (this.protocol === Protocols.Hysteria) return this.stream.network === 'hysteria';
+ if (this.protocol === Protocols.Hysteria) return true;
+ if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.stream.network);
}
diff --git a/web/html/component/aClientTable.html b/web/html/component/aClientTable.html
index d9a9b5f5..0e32d45c 100644
--- a/web/html/component/aClientTable.html
+++ b/web/html/component/aClientTable.html
@@ -30,7 +30,7 @@
-
+
@@ -165,7 +165,7 @@
{{ i18n "delete"}}
-
+
{{ i18n "enable"}}
diff --git a/web/html/form/client.html b/web/html/form/client.html
index 908f28d2..da19fe8f 100644
--- a/web/html/form/client.html
+++ b/web/html/form/client.html
@@ -1,5 +1,6 @@
{{define "form/client"}}
-
+
@@ -10,38 +11,62 @@
{{ i18n "pages.inbounds.emailDesc" }}
{{ i18n "pages.inbounds.email" }}
-
+
-
+
{{ i18n "reset" }}
{{ i18n "password" }}
-
-
+
+
-
+
{{ i18n "reset" }}
- ID
+ Auth Password
+
+
+
+
+
+
+
+
+
+ {{ i18n "reset" }}
+
+ ID
-
-
- [[ key ]]
+
+
+ [[ key
+ ]]
@@ -51,7 +76,8 @@
{{ i18n "pages.inbounds.subscriptionDesc" }}
Subscription
-
+
@@ -66,7 +92,8 @@
-
+
@@ -77,19 +104,21 @@
{{ i18n "pages.inbounds.IPLimitDesc"}}
- {{ i18n "pages.inbounds.IPLimit"}}
+ {{ i18n "pages.inbounds.IPLimit"}}
-
+
-
+
{{ i18n "pages.inbounds.IPLimitlogDesc" }}
- {{ i18n "pages.inbounds.IPLimitlog" }}
+ {{ i18n "pages.inbounds.IPLimitlog" }}
@@ -98,19 +127,24 @@
{{ i18n "pages.inbounds.IPLimitlogclear" }}
-
+
-
-
- {{ i18n "none" }}
- [[ key ]]
+
+ {{ i18n "none" }}
+ [[ key
+ ]]
@@ -123,45 +157,57 @@
-
+
-
+
[[ SizeFormatter.sizeFormat(clientStats.up) ]] /
[[ SizeFormatter.sizeFormat(clientStats.down) ]]
([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
- {{ i18n "pages.inbounds.resetTraffic" }}
+ {{ i18n "pages.inbounds.resetTraffic"
+ }}
-
+
-
-
+
+
- {{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}
+ {{ i18n
+ "pages.inbounds.leaveBlankToNeverExpire" }}
{{ i18n "pages.inbounds.expireDate" }}
-
-
+
+
Expired
- {{ i18n "pages.client.renewDesc" }}
+ {{ i18n "pages.client.renewDesc"
+ }}
{{ i18n "pages.client.renew" }}
diff --git a/web/html/form/inbound.html b/web/html/form/inbound.html
index 5c982b6c..4e89024d 100644
--- a/web/html/form/inbound.html
+++ b/web/html/form/inbound.html
@@ -74,7 +74,8 @@
{{ i18n
"pages.inbounds.periodicTrafficReset.never" }}
{{ i18n
- "pages.inbounds.periodicTrafficReset.hourly" }}
+ "pages.inbounds.periodicTrafficReset.hourly"
+ }}
{{ i18n
"pages.inbounds.periodicTrafficReset.daily" }}
{{ i18n
@@ -154,6 +155,11 @@
{{template "form/tun"}}
+
+
+ {{template "form/hysteria"}}
+
+
{{template "form/streamSettings"}}
diff --git a/web/html/form/protocol/hysteria.html b/web/html/form/protocol/hysteria.html
new file mode 100644
index 00000000..f612f056
--- /dev/null
+++ b/web/html/form/protocol/hysteria.html
@@ -0,0 +1,32 @@
+{{define "form/hysteria"}}
+
+
+ {{template "form/client"}}
+
+
+
+
+
+
+
+ | [[ client.email ]] |
+ [[ client.auth ]] |
+
+
+
+
+
+
+
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/html/form/stream/stream_hysteria.html b/web/html/form/stream/stream_hysteria.html
new file mode 100644
index 00000000..5e43d462
--- /dev/null
+++ b/web/html/form/stream/stream_hysteria.html
@@ -0,0 +1,75 @@
+{{define "form/streamHysteria"}}
+
+
+
+
+
+
+
+
+
+
+
+ Masquerade
+
+
+ File
+ Proxy
+ String
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ [[
+ index+1 ]]
+
+
+ -
+
+
+
+
+
+
+
+
+
+{{end}}
\ No newline at end of file
diff --git a/web/html/form/stream/stream_settings.html b/web/html/form/stream/stream_settings.html
index 5b00ef25..700fe793 100644
--- a/web/html/form/stream/stream_settings.html
+++ b/web/html/form/stream/stream_settings.html
@@ -1,7 +1,8 @@
{{define "form/streamSettings"}}
+ :wrapper-col="{ md: {span:14} }"
+ v-if="inbound.protocol != Protocols.HYSTERIA">
+
+
+ {{template "form/streamHysteria"}}
+
+
{{template "form/streamHTTPUpgrade"}}
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index 2fe82388..2462e901 100644
--- a/web/html/form/tls_settings.html
+++ b/web/html/form/tls_settings.html
@@ -2,15 +2,18 @@
-
-
-
- {{ i18n "none" }}
- Reality
- TLS
-
-
+
+
+
+
+ {{ i18n "none" }}
+ Reality
+ TLS
+
+
+
+ TLS
diff --git a/web/html/inbounds.html b/web/html/inbounds.html
index 231fc0c0..60de0750 100644
--- a/web/html/inbounds.html
+++ b/web/html/inbounds.html
@@ -2,14 +2,18 @@
{{ template "page/head_end" .}}
{{ template "page/body_start" .}}
-
+
-
+
-
+
@@ -21,7 +25,8 @@
-
@@ -29,7 +34,8 @@
-
@@ -38,15 +44,19 @@
-
+
-
@@ -60,33 +70,50 @@
-
[[ total.clients ]]
-
+
- [[ clientEmail ]]
+ [[
+ clientEmail ]]
- [[ total.deactive.length ]]
+ [[
+ total.deactive.length ]]
-
+
- [[ clientEmail ]]
+ [[
+ clientEmail ]]
- [[ total.depleted.length ]]
+ [[
+ total.depleted.length ]]
- [[ clientEmail ]]
+ [[
+ clientEmail ]]
- [[ total.expiring.length ]]
+ [[
+ total.expiring.length ]]
-
+
- [[ clientEmail ]]
+ [[
+ clientEmail ]]
- [[ onlineClients.length ]]
+ [[
+ onlineClients.length ]]
@@ -100,14 +127,18 @@
-
- {{ i18n "pages.inbounds.addInbound" }}
+
+ {{ i18n
+ "pages.inbounds.addInbound" }}
- {{ i18n "pages.inbounds.generalActions" }}
+ {{ i18n
+ "pages.inbounds.generalActions" }}
- generalActions(a)" :theme="themeSwitcher.currentTheme">
+ generalActions(a)"
+ :theme="themeSwitcher.currentTheme">
{{ i18n "pages.inbounds.importInbound" }}
@@ -118,7 +149,8 @@
- {{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
+ {{ i18n "pages.inbounds.export" }} - {{ i18n
+ "pages.settings.subSettings" }}
@@ -128,7 +160,8 @@
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
-
+
{{ i18n "pages.inbounds.delDepletedClients" }}
@@ -138,20 +171,28 @@
-
-
+
+
-
+
{{ i18n "pages.inbounds.autoRefresh" }}
- {{ i18n "pages.inbounds.autoRefreshInterval" }}
-
- [[ key ]]s
+ {{ i18n "pages.inbounds.autoRefreshInterval"
+ }}
+
+ [[ key ]]s
@@ -160,27 +201,38 @@
-
+
-
-
+
- {{ i18n "none" }}
- {{ i18n "disabled" }}
- {{ i18n "depleted" }}
- {{ i18n "depletingSoon" }}
- {{ i18n "online" }}
+ {{ i18n "none" }}
+ {{ i18n "disabled"
+ }}
+ {{ i18n "depleted"
+ }}
+ {{ i18n "depletingSoon"
+ }}
+ {{ i18n "online"
+ }}
-
e.preventDefault()" type="more"
:style="{ fontSize: '20px', textDecoration: 'solid' }">
- clickAction(a, dbInbound)"
+ clickAction(a, dbInbound)"
:theme="themeSwitcher.currentTheme">
@@ -211,7 +264,8 @@
- {{ i18n "pages.inbounds.resetInboundClientTraffics"}}
+ {{ i18n
+ "pages.inbounds.resetInboundClientTraffics"}}
@@ -219,9 +273,11 @@
- {{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
+ {{ i18n "pages.inbounds.export"}} - {{ i18n
+ "pages.settings.subSettings" }}
-
+
{{ i18n "pages.inbounds.delDepletedClients" }}
@@ -237,10 +293,12 @@
{{ i18n "pages.inbounds.exportInbound" }}
- {{ i18n "pages.inbounds.resetTraffic" }}
+ {{ i18n
+ "pages.inbounds.resetTraffic" }}
- {{ i18n "pages.inbounds.clone"}}
+ {{ i18n
+ "pages.inbounds.clone"}}
@@ -256,26 +314,38 @@
- [[ dbInbound.protocol ]]
-
- [[ dbInbound.toInbound().stream.network ]]
- [[
+ dbInbound.protocol ]]
+
+ [[
+ dbInbound.toInbound().stream.network ]]
+ TLS
- Reality
- [[ clientCount[dbInbound.id].clients ]]
-
+ [[
+ clientCount[dbInbound.id].clients ]]
+
-