From 099f2fc52bf913bd8f73f77897516d369931717c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:04:51 +0000 Subject: [PATCH 01/28] Bump golang.org/x/text from 0.8.0 to 0.9.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 377ffab8..f434de5d 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/shirou/gopsutil/v3 v3.23.3 github.com/xtls/xray-core v1.8.0 go.uber.org/atomic v1.10.0 - golang.org/x/text v0.8.0 + golang.org/x/text v0.9.0 google.golang.org/grpc v1.54.0 gorm.io/driver/sqlite v1.4.4 gorm.io/gorm v1.24.6 diff --git a/go.sum b/go.sum index 61f0ec46..0cac3612 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From e1da43053d23c995bcd6e7267cb20042398cd64f Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 9 Apr 2023 23:13:18 +0330 Subject: [PATCH 02/28] alireza update pack Co-Authored-By: Alireza Ahmadi --- README.md | 6 +- database/model/model.go | 7 +- web/assets/css/custom.css | 57 ++- web/assets/js/model/models.js | 4 +- web/assets/js/model/xray.js | 257 +++++++------ web/controller/api.go | 79 ++-- web/controller/inbound.go | 28 +- web/controller/server.go | 5 +- web/controller/setting.go | 31 ++ web/controller/sub.go | 42 ++ web/entity/entity.go | 4 +- web/html/common/head.html | 1 + web/html/xui/client_bulk_modal.html | 42 +- web/html/xui/client_modal.html | 20 +- web/html/xui/form/client.html | 25 +- web/html/xui/form/protocol/trojan.html | 2 +- web/html/xui/form/protocol/vless.html | 2 +- web/html/xui/form/tls_settings.html | 1 + web/html/xui/inbound_client_table.html | 8 +- web/html/xui/inbound_info_modal.html | 54 ++- web/html/xui/inbound_modal.html | 4 + web/html/xui/inbounds.html | 245 ++++++++---- web/html/xui/index.html | 41 +- web/html/xui/setting.html | 4 +- web/service/inbound.go | 130 +++++-- web/service/server.go | 4 +- web/service/setting.go | 36 +- web/service/sub.go | 505 +++++++++++++++++++++++++ web/service/tgbot.go | 32 +- web/service/xray.go | 27 +- web/translation/translate.en_US.toml | 18 +- web/translation/translate.fa_IR.toml | 28 +- web/translation/translate.zh_Hans.toml | 24 +- web/web.go | 13 +- 34 files changed, 1415 insertions(+), 371 deletions(-) create mode 100644 web/controller/sub.go create mode 100644 web/service/sub.go diff --git a/README.md b/README.md index 1f5fdcd8..a24cbe45 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Set the robot-related parameters in the panel background, including: Reference syntax: +- 30 * * * * * //Notify at the 30s of each point +- 0 */10 * * * * //Notify at the first second of each 10 minutes - @hourly // hourly notification - @daily // Daily notification (00:00 in the morning) - @every 8h // notify every 8 hours @@ -89,13 +91,13 @@ Reference syntax: - Login notification - CPU threshold notification - Threshold for Expiration time and Traffic to report in advance -- Support client report if client's telegram username is added to the end of `email` like 'test123@telegram_username' +- Support client report menu if client's telegram username added to the user's configurations - Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously - Menu based bot - Search client by email ( only admin ) - Check all inbounds - Check server status -- Check Exhausted users +- Check depleted users - Receive backup by request and in periodic reports # A Special Thanks To diff --git a/database/model/model.go b/database/model/model.go index 606b922c..69724213 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -44,9 +44,9 @@ type Inbound struct { Sniffing string `json:"sniffing" form:"sniffing"` } type InboundClientIps struct { - Id int `json:"id" gorm:"primaryKey;autoIncrement"` + Id int `json:"id" gorm:"primaryKey;autoIncrement"` ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"` - Ips string `json:"ips" form:"ips"` + Ips string `json:"ips" form:"ips"` } func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { @@ -80,4 +80,7 @@ type Client struct { LimitIP int `json:"limitIp"` TotalGB int64 `json:"totalGB" form:"totalGB"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` + Enable bool `json:"enable" from:"enable"` + TgID string `json:"tgId" from:"tgId"` + SubID string `json:"subId" from:"subId"` } diff --git a/web/assets/css/custom.css b/web/assets/css/custom.css index 038e30a1..229d8500 100644 --- a/web/assets/css/custom.css +++ b/web/assets/css/custom.css @@ -156,6 +156,12 @@ padding:16px; } +.ant-table-expand-icon-th, +.ant-table-row-expand-icon-cell { + width: 30px; + min-width: 30px; +} + .ant-menu-dark, .ant-menu-dark .ant-menu-sub, .ant-layout-header, @@ -174,6 +180,7 @@ .ant-card-dark:hover { border-color: #e8e8e8; + box-shadow: 0 2px 8px rgba(255,255,255,.15); } .ant-card-dark .ant-table-thead th { @@ -216,20 +223,25 @@ .ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td, .ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled), -.ant-card-dark .ant-calendar-date:hover { +.ant-card-dark .ant-calendar-date:hover, +.ant-card-dark .ant-select-dropdown-menu-item-active, +.ant-card-dark li.ant-calendar-time-picker-select-option-selected { background-color: #004488; } -.ant-card-dark tbody .ant-table-expanded-row { +.ant-card-dark tbody .ant-table-expanded-row, +.ant-card-dark .ant-calendar-time-picker-inner { color: hsla(0,0%,100%,.65); background-color: #1a212a; } .ant-card-dark .ant-input, .ant-card-dark .ant-input-number, +.ant-card-dark .ant-input-number-handler-wrap, .ant-card-dark .ant-calendar-input, .ant-card-dark .ant-select-dropdown-menu-item-selected, -.ant-card-dark .ant-select-selection { +.ant-card-dark .ant-select-selection, +.ant-card-dark .ant-calendar-picker-clear { color: hsla(0,0%,100%,.65); background-color: #2e3b52; } @@ -239,6 +251,12 @@ background-color: #161b22; } +.ant-dropdown-menu-dark, +.ant-card-dark .ant-modal-content { + border: 1px solid rgba(255, 255, 255, 0.65); + box-shadow: 0 2px 8px rgba(255,255,255,.15); +} + .ant-card-dark .ant-modal-content, .ant-card-dark .ant-modal-body, .ant-card-dark .ant-modal-header, @@ -280,6 +298,12 @@ border: 1px solid hsla(0,0%,100%,.30); } +.ant-card-dark .ant-tag { + color: hsla(0,0%,100%,.65); + background: rgba(255,255,255,.04); + border-color: #434343; +} + .ant-card-dark .ant-tag-blue { color: #3c9ae8; background: #111d2c; @@ -334,6 +358,29 @@ color: hsla(0,0%,100%,.65); background-color: #073763; border-color: #1890ff; - text-shadow: 0 -1px 0 rgba(0,0,0,.12); - box-shadow: 0 2px 0 rgba(0,0,0,.045); + text-shadow: 0 -1px 0 rgba(255,255,255,.12); + box-shadow: 0 2px 0 rgba(255,255,255,.045); +} +.ant-card-dark .ant-btn-primary:hover { + background-color: #40a9ff; + border-color: #40a9ff; +} + +.ant-dark .ant-popover-content { + border: 1px solid #e8e8e8; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(255,255,255,.15); +} + +.ant-dark .ant-popover-inner { + background: #222a37; +} + +.ant-dark .ant-popover-title, +.ant-dark .ant-popover-inner-content { + color: hsla(0,0%,100%,.65); +} + +.ant-dark .ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow { + border-color: transparent #2e3b52 #2e3b52 transparent; } \ No newline at end of file diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js index 485464e4..1de76850 100644 --- a/web/assets/js/model/models.js +++ b/web/assets/js/model/models.js @@ -171,13 +171,13 @@ class AllSetting { this.webCertFile = ""; this.webKeyFile = ""; this.webBasePath = "/"; + this.expireDiff = ""; + this.trafficDiff = ""; this.tgBotEnable = false; this.tgBotToken = ""; this.tgBotChatId = ""; this.tgRunTime = "@daily"; this.tgBotBackup = false; - this.tgExpireDiff = ""; - this.tgTrafficDiff = ""; this.tgCpu = ""; this.xrayTemplateConfig = ""; diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index eb6d07f3..65f4bfd9 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -92,6 +92,7 @@ const UTLS_FINGERPRINT = { }; const ALPN_OPTION = { + H3: "h3", H2: "h2", HTTP1: "http/1.1", }; @@ -166,20 +167,20 @@ class XrayCommonClass { } class TcpStreamSettings extends XrayCommonClass { - constructor( - type = 'none', - acceptProxyProtocol = false, - request = new TcpStreamSettings.TcpRequest(), - response = new TcpStreamSettings.TcpResponse(), - ) { + constructor(acceptProxyProtocol=false, + type='none', + request=new TcpStreamSettings.TcpRequest(), + response=new TcpStreamSettings.TcpResponse(), + ) { super(); + this.acceptProxyProtocol = acceptProxyProtocol; this.type = type; this.request = request; this.response = response; this.acceptProxyProtocol = acceptProxyProtocol; } - static fromJson(json = {}) { + static fromJson(json={}) { let header = json.header; if (!header) { header = {}; @@ -194,6 +195,7 @@ class TcpStreamSettings extends XrayCommonClass { toJson() { return { + acceptProxyProtocol: this.acceptProxyProtocol, header: { type: this.type, request: this.type === 'http' ? this.request.toJson() : undefined, @@ -205,10 +207,10 @@ class TcpStreamSettings extends XrayCommonClass { } TcpStreamSettings.TcpRequest = class extends XrayCommonClass { - constructor(version = '1.1', - method = 'GET', - path = ['/'], - headers = [], + constructor(version='1.1', + method='GET', + path=['/'], + headers=[], ) { super(); this.version = version; @@ -242,7 +244,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json={}) { return new TcpStreamSettings.TcpRequest( json.version, json.method, @@ -261,10 +263,10 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass { }; TcpStreamSettings.TcpResponse = class extends XrayCommonClass { - constructor(version = '1.1', - status = '200', - reason = 'OK', - headers = [], + constructor(version='1.1', + status='200', + reason='OK', + headers=[], ) { super(); this.version = version; @@ -281,7 +283,7 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass { this.headers.splice(index, 1); } - static fromJson(json = {}) { + static fromJson(json={}) { return new TcpStreamSettings.TcpResponse( json.version, json.status, @@ -474,9 +476,13 @@ class GrpcStreamSettings extends XrayCommonClass { } class TlsStreamSettings extends XrayCommonClass { - constructor(serverName = '', minVersion = TLS_VERSION_OPTION.TLS10, maxVersion = TLS_VERSION_OPTION.TLS12, - cipherSuites = '', - certificates = [new TlsStreamSettings.Cert()], alpn=[''] ,settings=[new TlsStreamSettings.Settings()]) { + constructor(serverName='', + minVersion = TLS_VERSION_OPTION.TLS12, + maxVersion = TLS_VERSION_OPTION.TLS13, + cipherSuites = '', + certificates=[new TlsStreamSettings.Cert()], + alpn=[], + settings=[new TlsStreamSettings.Settings()]) { super(); this.server = serverName; this.minVersion = minVersion; @@ -484,7 +490,7 @@ class TlsStreamSettings extends XrayCommonClass { this.cipherSuites = cipherSuites; this.certs = certificates; this.alpn = alpn; - this.settings = settings; + this.settings = settings; } addCert(cert) { @@ -497,15 +503,15 @@ class TlsStreamSettings extends XrayCommonClass { static fromJson(json={}) { let certs; - let settings; + let settings; if (!ObjectUtil.isEmpty(json.certificates)) { certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert)); } + if (!ObjectUtil.isEmpty(json.settings)) { let values = json.settings[0]; settings = [new TlsStreamSettings.Settings(values.allowInsecure , values.fingerprint, values.serverName)]; } - return new TlsStreamSettings( json.serverName, json.minVersion, @@ -513,7 +519,7 @@ class TlsStreamSettings extends XrayCommonClass { json.cipherSuites, certs, json.alpn, - settings, + settings, ); } @@ -526,7 +532,6 @@ class TlsStreamSettings extends XrayCommonClass { certificates: TlsStreamSettings.toJsonArray(this.certs), alpn: this.alpn, settings: TlsStreamSettings.toJsonArray(this.settings), - }; } } @@ -573,40 +578,39 @@ TlsStreamSettings.Cert = class extends XrayCommonClass { }; TlsStreamSettings.Settings = class extends XrayCommonClass { - constructor(allowInsecure = false, fingerprint = '', serverName = '') { - super(); - this.allowInsecure = allowInsecure; - this.fingerprint = fingerprint; - this.serverName = serverName; - } - static fromJson(json = {}) { - return new TlsStreamSettings.Settings( - json.allowInsecure, - json.fingerprint, - json.servername, - ); - } - toJson() { - return { - allowInsecure: this.allowInsecure, - fingerprint: this.fingerprint, - serverName: this.serverName, - }; - } + constructor(allowInsecure = false, fingerprint = '', serverName = '') { + super(); + this.allowInsecure = allowInsecure; + this.fingerprint = fingerprint; + this.serverName = serverName; + } + static fromJson(json = {}) { + return new TlsStreamSettings.Settings( + json.allowInsecure, + json.fingerprint, + json.servername, + ); + } + toJson() { + return { + allowInsecure: this.allowInsecure, + fingerprint: this.fingerprint, + serverName: this.serverName, + }; + } }; - class StreamSettings extends XrayCommonClass { constructor(network='tcp', - security='none', - tlsSettings=new TlsStreamSettings(), - tcpSettings=new TcpStreamSettings(), - kcpSettings=new KcpStreamSettings(), - wsSettings=new WsStreamSettings(), - httpSettings=new HttpStreamSettings(), - quicSettings=new QuicStreamSettings(), - grpcSettings=new GrpcStreamSettings(), - ) { + security='none', + tlsSettings=new TlsStreamSettings(), + tcpSettings=new TcpStreamSettings(), + kcpSettings=new KcpStreamSettings(), + wsSettings=new WsStreamSettings(), + httpSettings=new HttpStreamSettings(), + quicSettings=new QuicStreamSettings(), + grpcSettings=new GrpcStreamSettings(), + ) { super(); this.network = network; this.security = security; @@ -728,14 +732,15 @@ class Inbound extends XrayCommonClass { get protocol() { return this._protocol; } - + set protocol(protocol) { this._protocol = protocol; this.settings = Inbound.Settings.getSettings(protocol); if (protocol === Protocols.TROJAN) { - this.tls = false; + this.tls = true; } } + get tls() { return this.stream.security === 'tls'; } @@ -918,16 +923,16 @@ class Inbound extends XrayCommonClass { isExpiry(index) { switch (this.protocol) { case Protocols.VMESS: - if(this.settings.vmesses[index]._expiryTime != null) - return this.settings.vmesses[index]._expiryTime < new Date().getTime(); + if(this.settings.vmesses[index].expiryTime > 0) + return this.settings.vmesses[index].expiryTime < new Date().getTime(); return false case Protocols.VLESS: - if(this.settings.vlesses[index]._expiryTime != null) - return this.settings.vlesses[index]._expiryTime < new Date().getTime(); + if(this.settings.vlesses[index].expiryTime > 0) + return this.settings.vlesses[index].expiryTime < new Date().getTime(); return false case Protocols.TROJAN: - if(this.settings.trojans[index]._expiryTime != null) - return this.settings.trojans[index]._expiryTime < new Date().getTime(); + if(this.settings.trojans[index].expiryTime > 0) + return this.settings.trojans[index].expiryTime < new Date().getTime(); return false default: return false; @@ -955,7 +960,7 @@ class Inbound extends XrayCommonClass { return false; } } - + //this is used for xtls-rprx-vision canEnableTlsFlow() { if ((this.stream.security === 'tls') && (this.network === "tcp")) { @@ -968,11 +973,10 @@ class Inbound extends XrayCommonClass { } return false; } - + canSetTls() { return this.canEnableTls(); } - canEnableXTLS() { switch (this.protocol) { @@ -989,7 +993,7 @@ class Inbound extends XrayCommonClass { switch (this.protocol) { case Protocols.VMESS: case Protocols.VLESS: - case Protocols.TROJAN: + case Protocols.TROJAN: return true; default: return false; @@ -1065,7 +1069,7 @@ class Inbound extends XrayCommonClass { address = this.stream.tls.server; } } - + let obj = { v: '2', ps: remark, @@ -1078,7 +1082,7 @@ class Inbound extends XrayCommonClass { host: host, path: path, tls: this.stream.security, - sni: this.stream.tls.settings[0]['serverName'], + sni: this.stream.tls.settings[0]['serverName'], fp: this.stream.tls.settings[0]['fingerprint'], alpn: this.stream.tls.alpn.join(','), allowInsecure: this.stream.tls.settings[0].allowInsecure, @@ -1148,25 +1152,25 @@ class Inbound extends XrayCommonClass { if (!ObjectUtil.isEmpty(this.stream.tls.server)) { address = this.stream.tls.server; } - if (this.stream.tls.settings[0]['serverName'] !== ''){ + if (this.stream.tls.settings[0]['serverName'] !== ''){ params.set("sni", this.stream.tls.settings[0]['serverName']); } if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) { params.set("flow", this.settings.vlesses[clientIndex].flow); } } - - if (this.XTLS) { + + if (this.XTLS) { params.set("security", "xtls"); params.set("alpn", this.stream.tls.alpn); if(this.stream.tls.settings[0].allowInsecure){ params.set("allowInsecure", "1"); } - if (!ObjectUtil.isEmpty(this.stream.tls.server)) { - address = this.stream.tls.server; - } - params.set("flow", this.settings.vlesses[clientIndex].flow); - } + if (!ObjectUtil.isEmpty(this.stream.tls.server)) { + address = this.stream.tls.server; + } + params.set("flow", this.settings.vlesses[clientIndex].flow); + } const link = `vless://${uuid}@${address}:${port}`; const url = new URL(link); @@ -1177,13 +1181,13 @@ class Inbound extends XrayCommonClass { return url.toString(); } - genSSLink(address = '', remark = '') { + genSSLink(address='', remark='') { let settings = this.settings; const server = this.stream.tls.server; if (!ObjectUtil.isEmpty(server)) { address = server; } - return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`; + return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`; } genTrojanLink(address = '', remark = '', clientIndex = 0) { @@ -1191,7 +1195,7 @@ class Inbound extends XrayCommonClass { const port = this.port; const type = this.stream.network; const params = new Map(); - params.set("type", this.stream.network); + params.set("type", this.stream.network); switch (type) { case "tcp": const tcp = this.stream.tcp; @@ -1246,12 +1250,12 @@ class Inbound extends XrayCommonClass { } if (!ObjectUtil.isEmpty(this.stream.tls.server)) { address = this.stream.tls.server; - } - if (this.stream.tls.settings[0]['serverName'] !== ''){ - params.set("sni", this.stream.tls.settings[0]['serverName']); } + if (this.stream.tls.settings[0]['serverName'] !== ''){ + params.set("sni", this.stream.tls.settings[0]['serverName']); + } } - + if (this.XTLS) { params.set("security", "xtls"); params.set("alpn", this.stream.tls.alpn); @@ -1259,11 +1263,11 @@ class Inbound extends XrayCommonClass { params.set("allowInsecure", "1"); } if (!ObjectUtil.isEmpty(this.stream.tls.server)) { - address = this.stream.tls.server; - } - params.set("flow", this.settings.trojans[clientIndex].flow); - } - + address = this.stream.tls.server; + } + params.set("flow", this.settings.trojans[clientIndex].flow); + } + const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}#${encodeURIComponent(remark)}`; const url = new URL(link); for (const [key, value] of params) { @@ -1294,8 +1298,9 @@ class Inbound extends XrayCommonClass { default: return ''; } } + genInboundLinks(address = '', remark = '') { - let link = ''; + let link = ''; switch (this.protocol) { case Protocols.VMESS: case Protocols.VLESS: @@ -1308,7 +1313,7 @@ class Inbound extends XrayCommonClass { return (this.genSSLink(address, remark) + '\r\n'); default: return ''; } -} + } static fromJson(json={}) { return new Inbound( @@ -1423,7 +1428,7 @@ Inbound.VmessSettings = class extends Inbound.Settings { } }; Inbound.VmessSettings.Vmess = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { + constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') { super(); this.id = id; this.alterId = alterId; @@ -1431,6 +1436,9 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { this.limitIp = limitIp; this.totalGB = totalGB; this.expiryTime = expiryTime; + this.enable = enable; + this.tgId = tgId; + this.subId = subId; } static fromJson(json={}) { @@ -1441,13 +1449,18 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { json.limitIp, json.totalGB, json.expiryTime, - + json.enable, + json.tgId, + json.subId, ); } get _expiryTime() { if (this.expiryTime === 0 || this.expiryTime === "") { return null; } + if (this.expiryTime < 0){ + return this.expiryTime / -86400000; + } return moment(this.expiryTime); } @@ -1475,7 +1488,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { fallbacks=[],) { super(protocol); this.vlesses = vlesses; - this.decryption = 'none'; + this.decryption = 'none'; // Using decryption is not implemented here this.fallbacks = fallbacks; } @@ -1487,6 +1500,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { this.fallbacks.splice(index, 1); } + // decryption should be set to static value static fromJson(json={}) { return new Inbound.VLESSSettings( Protocols.VLESS, @@ -1506,8 +1520,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { }; Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { - - constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { + constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') { super(); this.id = id; this.flow = flow; @@ -1515,7 +1528,9 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { this.limitIp = limitIp; this.totalGB = totalGB; this.expiryTime = expiryTime; - + this.enable = enable; + this.tgId = tgId; + this.subId = subId; } static fromJson(json={}) { @@ -1526,14 +1541,19 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { json.limitIp, json.totalGB, json.expiryTime, - + json.enable, + json.tgId, + json.subId, ); - } + } get _expiryTime() { if (this.expiryTime === 0 || this.expiryTime === "") { return null; } + if (this.expiryTime < 0){ + return this.expiryTime / -86400000; + } return moment(this.expiryTime); } @@ -1553,7 +1573,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { } }; Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { - constructor(name="", alpn='', path='', dest='', xver=0) { + constructor(name="", alpn=[], path='', dest='', xver=0) { super(); this.name = name; this.alpn = alpn; @@ -1593,8 +1613,8 @@ Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { Inbound.TrojanSettings = class extends Inbound.Settings { constructor(protocol, - trojans=[new Inbound.TrojanSettings.Trojan()], - fallbacks=[],) { + trojans=[new Inbound.TrojanSettings.Trojan()], + fallbacks=[],) { super(protocol); this.trojans = trojans; this.fallbacks = fallbacks; @@ -1623,7 +1643,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings { } }; Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { - constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { + constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') { super(); this.password = password; this.flow = flow; @@ -1631,6 +1651,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { this.limitIp = limitIp; this.totalGB = totalGB; this.expiryTime = expiryTime; + this.enable = enable; + this.tgId = tgId; + this.subId = subId; } toJson() { @@ -1641,10 +1664,13 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { limitIp: this.limitIp, totalGB: this.totalGB, expiryTime: this.expiryTime, + enable: this.enable, + tgId: this.tgId, + subId: this.subId, }; } - static fromJson(json={}) { + static fromJson(json = {}) { return new Inbound.TrojanSettings.Trojan( json.password, json.flow, @@ -1652,7 +1678,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { json.limitIp, json.totalGB, json.expiryTime, - + json.enable, + json.tgId, + json.subId, ); } @@ -1660,6 +1688,9 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { if (this.expiryTime === 0 || this.expiryTime === "") { return null; } + if (this.expiryTime < 0){ + return this.expiryTime / -86400000; + } return moment(this.expiryTime); } @@ -1681,7 +1712,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { }; Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { - constructor(name="", alpn='', path='', dest='', xver=0) { + constructor(name="", alpn=[], path='', dest='', xver=0) { super(); this.name = name; this.alpn = alpn; @@ -1721,9 +1752,9 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { Inbound.ShadowsocksSettings = class extends Inbound.Settings { constructor(protocol, - method = SSMethods.BLAKE3_AES_256_GCM, - password = RandomUtil.randomSeq(44), - network = 'tcp,udp' + method=SSMethods.BLAKE3_AES_256_GCM, + password=RandomUtil.randomSeq(44), + network='tcp,udp' ) { super(protocol); this.method = method; @@ -1731,7 +1762,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { this.network = network; } - static fromJson(json = {}) { + static fromJson(json={}) { return new Inbound.ShadowsocksSettings( Protocols.SHADOWSOCKS, json.method, @@ -1755,7 +1786,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { this.address = address; this.port = port; this.network = network; - this.followRedirect = followRedirect; + this.followRedirect = followRedirect; } static fromJson(json={}) { @@ -1764,7 +1795,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { json.address, json.port, json.network, - json.followRedirect, + json.followRedirect, ); } @@ -1773,7 +1804,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings { address: this.address, port: this.port, network: this.network, - followRedirect: this.followRedirect, + followRedirect: this.followRedirect, }; } }; diff --git a/web/controller/api.go b/web/controller/api.go index 843ac7e5..f3021ea4 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -3,77 +3,74 @@ package controller import "github.com/gin-gonic/gin" type APIController struct { - BaseController - inboundController *InboundController - settingController *SettingController + BaseController + inboundController *InboundController } func NewAPIController(g *gin.RouterGroup) *APIController { - a := &APIController{} - a.initRouter(g) - return a + a := &APIController{} + a.initRouter(g) + return a } func (a *APIController) initRouter(g *gin.RouterGroup) { - g = g.Group("/xui/API/inbounds") - g.Use(a.checkLogin) + g = g.Group("/xui/API/inbounds") + g.Use(a.checkLogin) - g.POST("/list", a.getAllInbounds) - g.GET("/get/:id", a.getSingleInbound) - g.POST("/add", a.addInbound) - g.POST("/del/:id", a.delInbound) - g.POST("/update/:id", a.updateInbound) - g.POST("/clientIps/:email", a.getClientIps) - g.POST("/clearClientIps/:email", a.clearClientIps) - g.POST("/addClient/", a.addInboundClient) - g.POST("/delClient/:email", a.delInboundClient) - g.POST("/updateClient/:index", a.updateInboundClient) - g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) + g.POST("/list", a.getAllInbounds) + g.GET("/get/:id", a.getSingleInbound) + g.POST("/add", a.addInbound) + g.POST("/del/:id", a.delInbound) + g.POST("/update/:id", a.updateInbound) + g.POST("/clientIps/:email", a.getClientIps) + g.POST("/clearClientIps/:email", a.clearClientIps) + g.POST("/addClient/", a.addInboundClient) + g.POST("/delClient/:email", a.delInboundClient) + g.POST("/updateClient/:index", a.updateInboundClient) + g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) + g.POST("/resetAllTraffics", a.resetAllTraffics) + g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) - a.inboundController = NewInboundController(g) + a.inboundController = NewInboundController(g) } - - func (a *APIController) getAllInbounds(c *gin.Context) { - a.inboundController.getInbounds(c) + a.inboundController.getInbounds(c) } - func (a *APIController) getSingleInbound(c *gin.Context) { - a.inboundController.getInbound(c) + a.inboundController.getInbound(c) } - func (a *APIController) addInbound(c *gin.Context) { - a.inboundController.addInbound(c) + a.inboundController.addInbound(c) } - func (a *APIController) delInbound(c *gin.Context) { - a.inboundController.delInbound(c) + a.inboundController.delInbound(c) } - func (a *APIController) updateInbound(c *gin.Context) { - a.inboundController.updateInbound(c) + a.inboundController.updateInbound(c) } func (a *APIController) getClientIps(c *gin.Context) { - a.inboundController.getClientIps(c) + a.inboundController.getClientIps(c) } func (a *APIController) clearClientIps(c *gin.Context) { - a.inboundController.clearClientIps(c) + a.inboundController.clearClientIps(c) } - func (a *APIController) addInboundClient(c *gin.Context) { - a.inboundController.addInboundClient(c) + a.inboundController.addInboundClient(c) } - func (a *APIController) delInboundClient(c *gin.Context) { - a.inboundController.delInboundClient(c) + a.inboundController.delInboundClient(c) } - func (a *APIController) updateInboundClient(c *gin.Context) { - a.inboundController.updateInboundClient(c) + a.inboundController.updateInboundClient(c) } - func (a *APIController) resetClientTraffic(c *gin.Context) { - a.inboundController.resetClientTraffic(c) + a.inboundController.resetClientTraffic(c) +} +func (a *APIController) resetAllTraffics(c *gin.Context) { + a.inboundController.resetAllTraffics(c) +} +func (a *APIController) resetAllClientTraffics(c *gin.Context) { + a.inboundController.resetAllClientTraffics(c) } diff --git a/web/controller/inbound.go b/web/controller/inbound.go index b567af8c..f7ea35eb 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -37,6 +37,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/delClient/:email", a.delInboundClient) g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) + g.POST("/resetAllTraffics", a.resetAllTraffics) + g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) } @@ -131,7 +133,7 @@ func (a *InboundController) updateInbound(c *gin.Context) { func (a *InboundController) getClientIps(c *gin.Context) { email := c.Param("email") - ips , err := a.inboundService.GetInboundClientIps(email) + ips, err := a.inboundService.GetInboundClientIps(email) if err != nil { jsonObj(c, "No IP Record", nil) return @@ -230,3 +232,27 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) { a.xrayService.SetToNeedRestart() } } + +func (a *InboundController) resetAllTraffics(c *gin.Context) { + err := a.inboundService.ResetAllTraffics() + if err != nil { + jsonMsg(c, "something worng!", err) + return + } + jsonMsg(c, "All traffics reseted", nil) +} + +func (a *InboundController) resetAllClientTraffics(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) + return + } + + err = a.inboundService.ResetAllClientTraffics(id) + if err != nil { + jsonMsg(c, "something worng!", err) + return + } + jsonMsg(c, "All traffics of client reseted", nil) +} diff --git a/web/controller/server.go b/web/controller/server.go index 7b7239d5..43a1fadc 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -38,7 +38,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/stopXrayService", a.stopXrayService) g.POST("/restartXrayService", a.restartXrayService) g.POST("/installXray/:version", a.installXray) - g.POST("/logs", a.getLogs) + g.POST("/logs/:count", a.getLogs) } func (a *ServerController) refreshStatus() { @@ -109,7 +109,8 @@ func (a *ServerController) restartXrayService(c *gin.Context) { } func (a *ServerController) getLogs(c *gin.Context) { - logs, err := a.serverService.GetLogs() + count := c.Param("count") + logs, err := a.serverService.GetLogs(count) if err != nil { jsonMsg(c, I18n(c, "getLogs"), err) return diff --git a/web/controller/setting.go b/web/controller/setting.go index 0456bca3..261eeec8 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -33,6 +33,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) { g = g.Group("/setting") g.POST("/all", a.getAllSetting) + g.POST("/defaultSettings", a.getDefaultSettings) g.POST("/update", a.updateSetting) g.POST("/updateUser", a.updateUser) g.POST("/restartPanel", a.restartPanel) @@ -47,6 +48,36 @@ func (a *SettingController) getAllSetting(c *gin.Context) { jsonObj(c, allSetting, nil) } +func (a *SettingController) getDefaultSettings(c *gin.Context) { + expireDiff, err := a.settingService.GetExpireDiff() + if err != nil { + jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err) + return + } + trafficDiff, err := a.settingService.GetTrafficDiff() + if err != nil { + jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err) + return + } + defaultCert, err := a.settingService.GetCertFile() + if err != nil { + jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err) + return + } + defaultKey, err := a.settingService.GetKeyFile() + if err != nil { + jsonMsg(c, I18n(c, "pages.setting.toasts.getSetting"), err) + return + } + result := map[string]interface{}{ + "expireDiff": expireDiff, + "trafficDiff": trafficDiff, + "defaultCert": defaultCert, + "defaultKey": defaultKey, + } + jsonObj(c, result, nil) +} + func (a *SettingController) updateSetting(c *gin.Context) { allSetting := &entity.AllSetting{} err := c.ShouldBind(allSetting) diff --git a/web/controller/sub.go b/web/controller/sub.go new file mode 100644 index 00000000..5695f032 --- /dev/null +++ b/web/controller/sub.go @@ -0,0 +1,42 @@ +package controller + +import ( + "encoding/base64" + "strings" + "x-ui/web/service" + + "github.com/gin-gonic/gin" +) + +type SUBController struct { + BaseController + + subService service.SubService +} + +func NewSUBController(g *gin.RouterGroup) *SUBController { + a := &SUBController{} + a.initRouter(g) + return a +} + +func (a *SUBController) initRouter(g *gin.RouterGroup) { + g = g.Group("/sub") + + g.GET("/:subid", a.subs) +} + +func (a *SUBController) subs(c *gin.Context) { + subId := c.Param("subid") + host := strings.Split(c.Request.Host, ":")[0] + subs, err := a.subService.GetSubs(subId, host) + if err != nil { + c.String(400, "Error!") + } else { + result := "" + for _, sub := range subs { + result += sub + "\n" + } + c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) + } +} diff --git a/web/entity/entity.go b/web/entity/entity.go index 372f5caa..b464de00 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -32,13 +32,13 @@ type AllSetting struct { WebCertFile string `json:"webCertFile" form:"webCertFile"` WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` WebBasePath string `json:"webBasePath" form:"webBasePath"` + ExpireDiff int `json:"expireDiff" form:"expireDiff"` + TrafficDiff int `json:"trafficDiff" form:"trafficDiff"` TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` TgBotToken string `json:"tgBotToken" form:"tgBotToken"` TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` TgRunTime string `json:"tgRunTime" form:"tgRunTime"` TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` - TgExpireDiff int `json:"tgExpireDiff" form:"tgExpireDiff"` - TgTrafficDiff int `json:"tgTrafficDiff" form:"tgTrafficDiff"` TgCpu int `json:"tgCpu" form:"tgCpu"` XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"` TimeLocation string `json:"timeLocation" form:"timeLocation"` diff --git a/web/html/common/head.html b/web/html/common/head.html index f34ce62f..5e8b1fef 100644 --- a/web/html/common/head.html +++ b/web/html/common/head.html @@ -7,6 +7,7 @@ +