Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d03e049320 | ||
|
|
957d9e24fb | ||
|
|
865e47e9a6 | ||
|
|
607c5d3598 | ||
|
|
8879541999 | ||
|
|
0b896d9c31 | ||
|
|
6f4a2809e2 | ||
|
|
103a26edb6 | ||
|
|
7419592626 | ||
|
|
edabfad559 | ||
|
|
2849cc0b2e | ||
|
|
46eb174af1 | ||
|
|
f8878208ca | ||
|
|
e126095949 | ||
|
|
df2f292b68 | ||
|
|
9f5ba0cf93 | ||
|
|
b5c5539501 | ||
|
|
252afe47c0 | ||
|
|
379451135d | ||
|
|
bc06dbab21 | ||
|
|
6a71ea7f5e | ||
|
|
942b9862d8 | ||
|
|
ae55fdc38a | ||
|
|
cc3ff61ae2 | ||
|
|
045717010a | ||
|
|
aae32d9211 | ||
|
|
cd5ad78f56 | ||
|
|
b450aacebd | ||
|
|
640068b279 |
13
README.md
13
README.md
@@ -20,10 +20,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||
|
||||
## Install custom version
|
||||
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.2.6`:
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.3.3`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.2.6
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.3.3
|
||||
```
|
||||
|
||||
# SSL
|
||||
@@ -33,6 +33,8 @@ apt-get install certbot -y
|
||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||
certbot renew --dry-run
|
||||
```
|
||||
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
||||
|
||||
|
||||
# Default settings
|
||||
|
||||
@@ -134,6 +136,7 @@ Reference syntax:
|
||||
- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
|
||||
- @hourly // hourly notification
|
||||
- @daily // Daily notification (00:00 in the morning)
|
||||
- @weekly // weekly notification
|
||||
- @every 8h // notify every 8 hours
|
||||
|
||||
# Telegram Bot Features
|
||||
@@ -160,17 +163,19 @@ Reference syntax:
|
||||
| :----: | ---------------------------------- | ------------------------------------------- |
|
||||
| `GET` | `"/list"` | Get all inbounds |
|
||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
||||
| `POST` | `"/add"` | Add inbound |
|
||||
| `POST` | `"/del/:id"` | Delete Inbound |
|
||||
| `POST` | `"/update/:id"` | Update Inbound |
|
||||
| `POST` | `"/clientIps/:email"` | Client Ip address |
|
||||
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
|
||||
| `POST` | `"/addClient/"` | Add Client to inbound |
|
||||
| `POST` | `"/addClient"` | Add Client to inbound |
|
||||
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by UID/Password as clientId |
|
||||
| `POST` | `"/updateClient/:index"` | Update Client |
|
||||
| `POST` | `"/updateClient/:clientId"` | Update Client by UID/Password as clientId |
|
||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
|
||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||
|
||||
# A Special Thanks To
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.2.8
|
||||
1.3.3
|
||||
|
||||
2
go.mod
2
go.mod
@@ -24,7 +24,7 @@ require (
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.7 // indirect
|
||||
github.com/bytedance/sonic v1.8.8 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -9,8 +9,8 @@ github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.7 h1:d3sry5vGgVq/OpgozRUNP6xBsSo0mtNdwliApw+SAMQ=
|
||||
github.com/bytedance/sonic v1.8.7/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q=
|
||||
github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"github.com/op/go-logging"
|
||||
"os"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
var logger *logging.Logger
|
||||
|
||||
@@ -172,6 +172,7 @@ class AllSetting {
|
||||
this.webCertFile = "";
|
||||
this.webKeyFile = "";
|
||||
this.webBasePath = "/";
|
||||
this.sessionMaxAge = "";
|
||||
this.expireDiff = "";
|
||||
this.trafficDiff = "";
|
||||
this.tgBotEnable = false;
|
||||
|
||||
@@ -28,20 +28,6 @@ const SSMethods = {
|
||||
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||
};
|
||||
|
||||
const RULE_IP = {
|
||||
PRIVATE: 'geoip:private',
|
||||
CN: 'geoip:cn',
|
||||
};
|
||||
|
||||
const RULE_DOMAIN = {
|
||||
ADS: 'geosite:category-ads',
|
||||
ADS_ALL: 'geosite:category-ads-all',
|
||||
CN: 'geosite:cn',
|
||||
GOOGLE: 'geosite:google',
|
||||
FACEBOOK: 'geosite:facebook',
|
||||
SPEEDTEST: 'geosite:speedtest',
|
||||
};
|
||||
|
||||
const XTLS_FLOW_CONTROL = {
|
||||
ORIGIN: "xtls-rprx-origin",
|
||||
DIRECT: "xtls-rprx-direct",
|
||||
@@ -101,8 +87,6 @@ const ALPN_OPTION = {
|
||||
Object.freeze(Protocols);
|
||||
Object.freeze(VmessMethods);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(RULE_IP);
|
||||
Object.freeze(RULE_DOMAIN);
|
||||
Object.freeze(XTLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(TLS_VERSION_OPTION);
|
||||
@@ -456,18 +440,26 @@ class QuicStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends XrayCommonClass {
|
||||
constructor(serviceName="") {
|
||||
constructor(
|
||||
serviceName="",
|
||||
multiMode=false
|
||||
) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.multiMode = multiMode;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new GrpcStreamSettings(json.serviceName);
|
||||
return new GrpcStreamSettings(
|
||||
json.serviceName,
|
||||
json.multiMode
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
serviceName: this.serviceName,
|
||||
multiMode: this.multiMode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1160,6 +1152,7 @@ class Inbound extends XrayCommonClass {
|
||||
case Protocols.VMESS:
|
||||
case Protocols.VLESS:
|
||||
case Protocols.TROJAN:
|
||||
case Protocols.SHADOWSOCKS:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -1261,50 +1254,6 @@ class Inbound extends XrayCommonClass {
|
||||
if (this.protocol !== Protocols.VMESS) {
|
||||
return '';
|
||||
}
|
||||
let network = this.stream.network;
|
||||
let type = 'none';
|
||||
let host = '';
|
||||
let path = '';
|
||||
if (network === 'tcp') {
|
||||
let tcp = this.stream.tcp;
|
||||
type = tcp.type;
|
||||
if (type === 'http') {
|
||||
let request = tcp.request;
|
||||
path = request.path.join(',');
|
||||
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
host = request.headers[index].value;
|
||||
}
|
||||
}
|
||||
} else if (network === 'kcp') {
|
||||
let kcp = this.stream.kcp;
|
||||
type = kcp.type;
|
||||
path = kcp.seed;
|
||||
} else if (network === 'ws') {
|
||||
let ws = this.stream.ws;
|
||||
path = ws.path;
|
||||
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
host = ws.headers[index].value;
|
||||
}
|
||||
} else if (network === 'http') {
|
||||
network = 'h2';
|
||||
path = this.stream.http.path;
|
||||
host = this.stream.http.host.join(',');
|
||||
} else if (network === 'quic') {
|
||||
type = this.stream.quic.type;
|
||||
host = this.stream.quic.security;
|
||||
path = this.stream.quic.key;
|
||||
} else if (network === 'grpc') {
|
||||
path = this.stream.grpc.serviceName;
|
||||
}
|
||||
|
||||
if (this.stream.security === 'tls') {
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
address = this.stream.tls.server;
|
||||
}
|
||||
}
|
||||
|
||||
let obj = {
|
||||
v: '2',
|
||||
ps: remark,
|
||||
@@ -1312,16 +1261,66 @@ class Inbound extends XrayCommonClass {
|
||||
port: this.port,
|
||||
id: this.settings.vmesses[clientIndex].id,
|
||||
aid: this.settings.vmesses[clientIndex].alterId,
|
||||
net: network,
|
||||
type: type,
|
||||
host: host,
|
||||
path: path,
|
||||
net: this.stream.network,
|
||||
type: 'none',
|
||||
tls: this.stream.security,
|
||||
sni: this.stream.tls.settings.serverName,
|
||||
fp: this.stream.tls.settings.fingerprint,
|
||||
alpn: this.stream.tls.alpn.join(','),
|
||||
allowInsecure: this.stream.tls.settings.allowInsecure,
|
||||
};
|
||||
let network = this.stream.network;
|
||||
if (network === 'tcp') {
|
||||
let tcp = this.stream.tcp;
|
||||
obj.type = tcp.type;
|
||||
if (tcp.type === 'http') {
|
||||
let request = tcp.request;
|
||||
obj.path = request.path.join(',');
|
||||
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
obj.host = request.headers[index].value;
|
||||
}
|
||||
}
|
||||
} else if (network === 'kcp') {
|
||||
let kcp = this.stream.kcp;
|
||||
obj.type = kcp.type;
|
||||
obj.path = kcp.seed;
|
||||
} else if (network === 'ws') {
|
||||
let ws = this.stream.ws;
|
||||
obj.path = ws.path;
|
||||
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
obj.host = ws.headers[index].value;
|
||||
}
|
||||
} else if (network === 'http') {
|
||||
obj.net = 'h2';
|
||||
obj.path = this.stream.http.path;
|
||||
obj.host = this.stream.http.host.join(',');
|
||||
} else if (network === 'quic') {
|
||||
obj.type = this.stream.quic.type;
|
||||
obj.host = this.stream.quic.security;
|
||||
obj.path = this.stream.quic.key;
|
||||
} else if (network === 'grpc') {
|
||||
obj.path = this.stream.grpc.serviceName;
|
||||
if (this.stream.grpc.multiMode){
|
||||
obj.type = 'multi'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stream.security === 'tls') {
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
||||
obj.add = this.stream.tls.server;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
|
||||
obj.sni = this.stream.tls.settings.serverName;
|
||||
}
|
||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
||||
obj.fp = this.stream.tls.settings.fingerprint;
|
||||
}
|
||||
if (this.stream.tls.alpn.length>0){
|
||||
obj.alpn = this.stream.tls.alpn.join(',');
|
||||
}
|
||||
if (this.stream.tls.settings.allowInsecure){
|
||||
obj.allowInsecure = this.stream.tls.settings.allowInsecure;
|
||||
}
|
||||
}
|
||||
|
||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
@@ -1374,6 +1373,9 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1491,6 +1493,9 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,13 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
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("/addClient", a.addInboundClient)
|
||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||
|
||||
a.inboundController = NewInboundController(g)
|
||||
}
|
||||
@@ -78,3 +79,6 @@ func (a *APIController) resetAllTraffics(c *gin.Context) {
|
||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||
a.inboundController.resetAllClientTraffics(c)
|
||||
}
|
||||
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||
a.inboundController.delDepletedClients(c)
|
||||
}
|
||||
|
||||
@@ -35,10 +35,11 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||
g.POST("/updateClient/:index", a.updateInboundClient)
|
||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
|
||||
err = a.inboundService.AddInboundClient(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
@@ -189,7 +190,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
|
||||
err = a.inboundService.DelInboundClient(id, clientId)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client deleted", nil)
|
||||
@@ -199,22 +200,18 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
index, err := strconv.Atoi(c.Param("index"))
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
clientId := c.Param("clientId")
|
||||
|
||||
inbound := &model.Inbound{}
|
||||
err = c.ShouldBind(inbound)
|
||||
err := c.ShouldBind(inbound)
|
||||
if err != nil {
|
||||
jsonMsg(c, I18n(c, "pages.inbounds.revise"), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.inboundService.UpdateInboundClient(inbound, index)
|
||||
err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client updated", nil)
|
||||
@@ -233,7 +230,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
|
||||
err = a.inboundService.ResetClientTraffic(id, email)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "traffic reseted", nil)
|
||||
@@ -245,7 +242,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||
err := a.inboundService.ResetAllTraffics()
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "All traffics reseted", nil)
|
||||
@@ -260,8 +257,22 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||
|
||||
err = a.inboundService.ResetAllClientTraffics(id)
|
||||
if err != nil {
|
||||
jsonMsg(c, "something worng!", err)
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "All traffics of client reseted", nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) delDepletedClients(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.DelDepletedClients(id)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||
}
|
||||
|
||||
@@ -72,6 +72,16 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||
}
|
||||
|
||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||
if err != nil {
|
||||
logger.Infof("Unable to get session's max age from DB")
|
||||
}
|
||||
|
||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||
if err != nil {
|
||||
logger.Infof("Unable to set session's max age")
|
||||
}
|
||||
|
||||
err = session.SetLoginUser(c, user)
|
||||
logger.Info("user", user.Id, "login success")
|
||||
jsonMsg(c, I18n(c, "pages.login.toasts.successLogin"), err)
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/entity"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func getUriId(c *gin.Context) int64 {
|
||||
s := struct {
|
||||
Id int64 `uri:"id"`
|
||||
}{}
|
||||
|
||||
_ = c.BindUri(&s)
|
||||
return s.Id
|
||||
}
|
||||
|
||||
func getRemoteIp(c *gin.Context) string {
|
||||
value := c.GetHeader("X-Forwarded-For")
|
||||
if value != "" {
|
||||
@@ -75,6 +67,7 @@ func html(c *gin.Context, name string, title string, data gin.H) {
|
||||
data = gin.H{}
|
||||
}
|
||||
data["title"] = title
|
||||
data["host"] = strings.Split(c.Request.Host, ":")[0]
|
||||
data["request_uri"] = c.Request.RequestURI
|
||||
data["base_path"] = c.GetString("base_path")
|
||||
c.HTML(http.StatusOK, name, getContext(data))
|
||||
@@ -84,10 +77,8 @@ func getContext(h gin.H) gin.H {
|
||||
a := gin.H{
|
||||
"cur_ver": config.GetVersion(),
|
||||
}
|
||||
if h != nil {
|
||||
for key, value := range h {
|
||||
a[key] = value
|
||||
}
|
||||
for key, value := range h {
|
||||
a[key] = value
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type AllSetting struct {
|
||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||
|
||||
@@ -2,8 +2,9 @@ package global
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/robfig/cron/v3"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var webServer WebServer
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<title>{{ i18n .title}}</title>
|
||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||
</head>
|
||||
{{end}}
|
||||
@@ -17,13 +17,14 @@
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
clientStats: [],
|
||||
oldClientId: "",
|
||||
index: null,
|
||||
clientIps: null,
|
||||
isExpired: false,
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
if(clientModal.isEdit){
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.index);
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
@@ -39,12 +40,13 @@
|
||||
this.index = index === null ? this.clients.length : index;
|
||||
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
||||
this.delayedStart = false;
|
||||
if (!isEdit){
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
} else {
|
||||
if (isEdit){
|
||||
if (this.clients[index].expiryTime < 0){
|
||||
this.delayedStart = true;
|
||||
}
|
||||
this.oldClientId = this.dbInbound.protocol == "trojan" ? this.clients[index].password : this.clients[index].id;
|
||||
} else {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
}
|
||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||
this.confirm = confirm;
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
const siderDrawer = {
|
||||
visible: false,
|
||||
collapsed: false,
|
||||
isDarkTheme: localStorage.getItem("dark-mode") === 'true' ? true : false,
|
||||
isDarkTheme: localStorage.getItem("dark-mode") === 'false' ? false : true,
|
||||
show() {
|
||||
this.visible = true;
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||
</template>
|
||||
<template v-else-if="type === 'number'">
|
||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
||||
<a-input type="number" :value="value" @input="$emit('input', $event.target.value)" :min="min"></a-input>
|
||||
</template>
|
||||
<template v-else-if="type === 'textarea'">
|
||||
<a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 10 }"></a-textarea>
|
||||
@@ -25,7 +25,7 @@
|
||||
{{define "component/setting"}}
|
||||
<script>
|
||||
Vue.component('setting-list-item', {
|
||||
props: ["type", "title", "desc", "value"],
|
||||
props: ["type", "title", "desc", "value", "min"],
|
||||
template: `{{template "component/settingListItem"}}`,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
<a-form-item label="ServiceName">
|
||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Multi Mode">
|
||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
<template v-if="inbound.isGrpc">
|
||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||
</template>
|
||||
</table>
|
||||
</td></tr>
|
||||
|
||||
@@ -67,8 +67,27 @@
|
||||
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
|
||||
<div slot="title">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
||||
<a-button type="primary" icon="export" @click="exportAllLinks">{{ i18n "pages.inbounds.export" }}</a-button>
|
||||
<a-button type="primary" icon="reload" @click="resetAllTraffic">{{ i18n "pages.inbounds.resetAllTraffic" }}</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="siderDrawer.theme">
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<a-input v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||
@@ -78,7 +97,7 @@
|
||||
style="margin-top: 20px"
|
||||
@change="() => getDBInbounds()">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
<a-icon type="edit" style="font-size: 25px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme">
|
||||
@@ -101,12 +120,16 @@
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics"}}
|
||||
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item key="showInfo">
|
||||
@@ -390,6 +413,22 @@
|
||||
});
|
||||
}
|
||||
},
|
||||
generalActions(action){
|
||||
switch (action.key) {
|
||||
case "export":
|
||||
this.exportAllLinks();
|
||||
break;
|
||||
case "resetInbounds":
|
||||
this.resetAllTraffic();
|
||||
break;
|
||||
case "resetClients":
|
||||
this.resetAllClientTraffics(-1);
|
||||
break;
|
||||
case "delDepletedClients":
|
||||
this.delDepletedClients(-1)
|
||||
break;
|
||||
}
|
||||
},
|
||||
clickAction(action, dbInbound) {
|
||||
switch (action.key) {
|
||||
case "qrcode":
|
||||
@@ -422,6 +461,9 @@
|
||||
case "delete":
|
||||
this.delInbound(dbInbound.id);
|
||||
break;
|
||||
case "delDepletedClients":
|
||||
this.delDepletedClients(dbInbound.id)
|
||||
break;
|
||||
}
|
||||
},
|
||||
openCloneInbound(dbInbound) {
|
||||
@@ -561,9 +603,9 @@
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
dbInbound: dbInbound,
|
||||
index: index,
|
||||
confirm: async (client, dbInboundId, index) => {
|
||||
confirm: async (client, dbInboundId, clientId) => {
|
||||
clientModal.loading();
|
||||
await this.updateClient(client, dbInboundId, index);
|
||||
await this.updateClient(client, dbInboundId, clientId);
|
||||
clientModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
@@ -580,12 +622,12 @@
|
||||
};
|
||||
await this.submit(`/xui/inbound/addClient`, data);
|
||||
},
|
||||
async updateClient(client, dbInboundId, index) {
|
||||
async updateClient(client, dbInboundId, clientId) {
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() +']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/updateClient/${index}`, data);
|
||||
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
||||
},
|
||||
resetTraffic(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -651,7 +693,8 @@
|
||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||
index = this.findIndexOfClient(clients, client);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
await this.updateClient(clients[index],dbInboundId, index);
|
||||
clientId = dbInbound.protocol == "trojan" ? clients[index].password : clients[index].id;
|
||||
await this.updateClient(clients[index],dbInboundId, clientId);
|
||||
this.loading(false);
|
||||
},
|
||||
async submit(url, data) {
|
||||
@@ -691,14 +734,24 @@
|
||||
},
|
||||
resetAllClientTraffics(dbInboundId) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||
title: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||
content: dbInboundId>0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||
})
|
||||
},
|
||||
delDepletedClients(dbInboundId) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||
class: siderDrawer.isDarkTheme ? darkClass : '',
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||
})
|
||||
},
|
||||
isExpiry(dbInbound, index) {
|
||||
return dbInbound.toInbound().isExpiry(index)
|
||||
},
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<setting-list-item type="text" title='{{ i18n "pages.setting.publicKeyPath"}}' desc='{{ i18n "pages.setting.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.setting.privateKeyPath"}}' desc='{{ i18n "pages.setting.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.setting.panelUrlPath"}}' desc='{{ i18n "pages.setting.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.setting.sessionMaxAge" }}' desc='{{ i18n "pages.setting.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="0"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.setting.expireTimeDiff" }}' desc='{{ i18n "pages.setting.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.setting.trafficDiff" }}' desc='{{ i18n "pages.setting.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||
<a-list-item>
|
||||
|
||||
@@ -4,23 +4,22 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"regexp"
|
||||
ss "strings"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
// "strconv"
|
||||
"github.com/go-cmd/cmd"
|
||||
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-cmd/cmd"
|
||||
)
|
||||
|
||||
type CheckClientIpJob struct {
|
||||
xrayService service.XrayService
|
||||
inboundService service.InboundService
|
||||
xrayService service.XrayService
|
||||
}
|
||||
|
||||
var job *CheckClientIpJob
|
||||
@@ -36,7 +35,7 @@ func (j *CheckClientIpJob) Run() {
|
||||
processLogFile()
|
||||
|
||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
||||
blockedIps := []byte(ss.Join(disAllowedIps, ","))
|
||||
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
||||
checkError(err)
|
||||
|
||||
@@ -58,7 +57,7 @@ func processLogFile() {
|
||||
checkError(err)
|
||||
}
|
||||
|
||||
lines := ss.Split(string(data), "\n")
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
||||
emailRegx, _ := regexp.Compile(`email:.+`)
|
||||
@@ -74,7 +73,7 @@ func processLogFile() {
|
||||
if matchesEmail == "" {
|
||||
continue
|
||||
}
|
||||
matchesEmail = ss.Split(matchesEmail, "email: ")[1]
|
||||
matchesEmail = strings.Split(matchesEmail, "email: ")[1]
|
||||
|
||||
if InboundClientIps[matchesEmail] != nil {
|
||||
if contains(InboundClientIps[matchesEmail], ip) {
|
||||
@@ -92,14 +91,12 @@ func processLogFile() {
|
||||
|
||||
for clientEmail, ips := range InboundClientIps {
|
||||
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
||||
sort.Sort(sort.StringSlice(ips))
|
||||
sort.Strings(ips)
|
||||
if err != nil {
|
||||
addInboundClientIps(clientEmail, ips)
|
||||
|
||||
} else {
|
||||
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if inbound connection is more than limited ip and drop connection
|
||||
@@ -202,6 +199,8 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
|
||||
var disAllowedIps []string // initialize the slice
|
||||
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
|
||||
@@ -214,7 +213,7 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
||||
}
|
||||
}
|
||||
logger.Debug("disAllowedIps ", disAllowedIps)
|
||||
sort.Sort(sort.StringSlice(disAllowedIps))
|
||||
sort.Strings(disAllowedIps)
|
||||
|
||||
db := database.GetDB()
|
||||
err = db.Save(inboundClientIps).Error
|
||||
@@ -223,6 +222,7 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DisableInbound(id int) error {
|
||||
db := database.GetDB()
|
||||
result := db.Model(model.Inbound{}).
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
@@ -367,7 +368,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error
|
||||
return db.Save(oldInbound).Error
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error {
|
||||
func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId string) error {
|
||||
clients, err := s.getClients(data)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -391,7 +392,23 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
||||
return err
|
||||
}
|
||||
|
||||
if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email {
|
||||
oldEmail := ""
|
||||
clientIndex := 0
|
||||
for index, oldClient := range oldClients {
|
||||
oldClientId := ""
|
||||
if oldInbound.Protocol == "trojan" {
|
||||
oldClientId = oldClient.Password
|
||||
} else {
|
||||
oldClientId = oldClient.ID
|
||||
}
|
||||
if clientId == oldClientId {
|
||||
oldEmail = oldClient.Email
|
||||
clientIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -406,10 +423,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settingsClients := oldSettings["clients"].([]interface{})
|
||||
settingsClients[index] = inerfaceClients[0]
|
||||
|
||||
settingsClients[clientIndex] = inerfaceClients[0]
|
||||
oldSettings["clients"] = settingsClients
|
||||
|
||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||
@@ -421,12 +436,12 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
||||
db := database.GetDB()
|
||||
|
||||
if len(clients[0].Email) > 0 {
|
||||
if len(oldClients[index].Email) > 0 {
|
||||
err = s.UpdateClientStat(oldClients[index].Email, &clients[0])
|
||||
if len(oldEmail) > 0 {
|
||||
err = s.UpdateClientStat(oldEmail, &clients[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.UpdateClientIPs(db, oldClients[index].Email, clients[0].Email)
|
||||
err = s.UpdateClientIPs(db, oldEmail, clients[0].Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -434,11 +449,11 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) err
|
||||
s.AddClientStat(data.Id, &clients[0])
|
||||
}
|
||||
} else {
|
||||
err = s.DelClientStat(db, oldClients[index].Email)
|
||||
err = s.DelClientStat(db, oldEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.DelClientIPs(db, oldClients[index].Email)
|
||||
err = s.DelClientIPs(db, oldEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -541,7 +556,7 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
for traffic_index := range dbClientTraffics {
|
||||
if c["email"] == dbClientTraffics[traffic_index].Email {
|
||||
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||
oldExpiryTime := c["expiryTime"].(float64)
|
||||
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
||||
c["expiryTime"] = newExpiryTime
|
||||
@@ -667,8 +682,15 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) error {
|
||||
func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||
db := database.GetDB()
|
||||
|
||||
whereText := "inbound_id "
|
||||
if id == -1 {
|
||||
whereText += " > ?"
|
||||
} else {
|
||||
whereText += " = ?"
|
||||
}
|
||||
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
Where("inbound_id = ?", id).
|
||||
Where(whereText, id).
|
||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
@@ -694,6 +716,84 @@ func (s *InboundService) ResetAllTraffics() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
whereText := "inbound_id "
|
||||
if id < 0 {
|
||||
whereText += "> ?"
|
||||
} else {
|
||||
whereText += "= ?"
|
||||
}
|
||||
|
||||
depletedClients := []xray.ClientTraffic{}
|
||||
err = db.Model(xray.ClientTraffic{}).Where(whereText+" and enable = ?", id, false).Select("inbound_id, GROUP_CONCAT(email) as email").Group("inbound_id").Find(&depletedClients).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, depletedClient := range depletedClients {
|
||||
emails := strings.Split(depletedClient.Email, ",")
|
||||
oldInbound, err := s.GetInbound(depletedClient.InboundId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oldSettings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldClients := oldSettings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
for _, client := range oldClients {
|
||||
deplete := false
|
||||
c := client.(map[string]interface{})
|
||||
for _, email := range emails {
|
||||
if email == c["email"].(string) {
|
||||
deplete = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !deplete {
|
||||
newClients = append(newClients, client)
|
||||
}
|
||||
}
|
||||
if len(newClients) > 0 {
|
||||
oldSettings["clients"] = newClients
|
||||
|
||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldInbound.Settings = string(newSettings)
|
||||
err = tx.Save(oldInbound).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Delete inbound if no client remains
|
||||
s.DelInbound(depletedClient.InboundId)
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Where(whereText+" and enable = ?", id, false).Delete(xray.ClientTraffic{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
@@ -724,7 +824,7 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr
|
||||
return traffics, err
|
||||
}
|
||||
|
||||
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic []*xray.ClientTraffic, err error) {
|
||||
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
|
||||
db := database.GetDB()
|
||||
var traffics []*xray.ClientTraffic
|
||||
|
||||
@@ -735,7 +835,7 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic []*xray.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return traffics, err
|
||||
return traffics[0], err
|
||||
}
|
||||
|
||||
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
||||
@@ -809,8 +909,11 @@ func (s *InboundService) SearchInbounds(query string) ([]*model.Inbound, error)
|
||||
}
|
||||
return inbounds, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) MigrationRequirements() {
|
||||
db := database.GetDB()
|
||||
|
||||
// Fix inbounds based problems
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
@@ -821,6 +924,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
// Fix Clinet configuration problems
|
||||
var newClients []interface{}
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
@@ -846,6 +950,23 @@ func (s *InboundService) MigrationRequirements() {
|
||||
|
||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||
}
|
||||
// Add client traffic row for all clients which has email
|
||||
modelClients, err := s.getClients(inbounds[inbound_index])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, modelClient := range modelClients {
|
||||
if len(modelClient.Email) > 0 {
|
||||
var count int64
|
||||
db.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||
if count == 0 {
|
||||
s.AddClientStat(inbounds[inbound_index].Id, &modelClient)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
db.Save(inbounds)
|
||||
|
||||
// Remove orphaned traffics
|
||||
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ var defaultValueMap = map[string]string{
|
||||
"webKeyFile": "",
|
||||
"secret": random.Seq(32),
|
||||
"webBasePath": "/",
|
||||
"sessionMaxAge": "0",
|
||||
"expireDiff": "0",
|
||||
"trafficDiff": "0",
|
||||
"timeLocation": "Asia/Tehran",
|
||||
@@ -251,18 +252,10 @@ func (s *SettingService) GetTgBotBackup() (bool, error) {
|
||||
return s.getBool("tgBotBackup")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetTgBotBackup(value bool) error {
|
||||
return s.setBool("tgBotBackup", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTgCpu() (int, error) {
|
||||
return s.getInt("tgCpu")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetTgCpu(value int) error {
|
||||
return s.setInt("tgCpu", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetPort() (int, error) {
|
||||
return s.getInt("webPort")
|
||||
}
|
||||
@@ -283,16 +276,12 @@ func (s *SettingService) GetExpireDiff() (int, error) {
|
||||
return s.getInt("expireDiff")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetExpireDiff(value int) error {
|
||||
return s.setInt("expireDiff", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTrafficDiff() (int, error) {
|
||||
return s.getInt("trafficDiff")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetgetTrafficDiff(value int) error {
|
||||
return s.setInt("trafficDiff", value)
|
||||
func (s *SettingService) GetSessionMaxAge() (int, error) {
|
||||
return s.getInt("sessionMaxAge")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSecretStatus() (bool, error) {
|
||||
|
||||
@@ -102,80 +102,89 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||
}
|
||||
|
||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
address := s.address
|
||||
if inbound.Protocol != model.VMess {
|
||||
return ""
|
||||
}
|
||||
obj := map[string]interface{}{
|
||||
"v": "2",
|
||||
"ps": email,
|
||||
"add": s.address,
|
||||
"port": inbound.Port,
|
||||
"type": "none",
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
network, _ := stream["network"].(string)
|
||||
typeStr := "none"
|
||||
host := ""
|
||||
path := ""
|
||||
sni := ""
|
||||
fp := ""
|
||||
var alpn []string
|
||||
allowInsecure := false
|
||||
obj["net"] = network
|
||||
switch network {
|
||||
case "tcp":
|
||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||
header, _ := tcp["header"].(map[string]interface{})
|
||||
typeStr, _ = header["type"].(string)
|
||||
typeStr, _ := header["type"].(string)
|
||||
obj["type"] = typeStr
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]interface{})
|
||||
requestPath, _ := request["path"].([]interface{})
|
||||
path = requestPath[0].(string)
|
||||
obj["path"] = requestPath[0].(string)
|
||||
headers, _ := request["headers"].(map[string]interface{})
|
||||
host = searchHost(headers)
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "kcp":
|
||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||
header, _ := kcp["header"].(map[string]interface{})
|
||||
typeStr, _ = header["type"].(string)
|
||||
path, _ = kcp["seed"].(string)
|
||||
obj["type"], _ = header["type"].(string)
|
||||
obj["path"], _ = kcp["seed"].(string)
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
path = ws["path"].(string)
|
||||
obj["path"] = ws["path"].(string)
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
host = searchHost(headers)
|
||||
obj["host"] = searchHost(headers)
|
||||
case "http":
|
||||
network = "h2"
|
||||
obj["net"] = "h2"
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
path, _ = http["path"].(string)
|
||||
host = searchHost(http)
|
||||
obj["path"], _ = http["path"].(string)
|
||||
obj["host"] = searchHost(http)
|
||||
case "quic":
|
||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||
header := quic["header"].(map[string]interface{})
|
||||
typeStr, _ = header["type"].(string)
|
||||
host, _ = quic["security"].(string)
|
||||
path, _ = quic["key"].(string)
|
||||
obj["type"], _ = header["type"].(string)
|
||||
obj["host"], _ = quic["security"].(string)
|
||||
obj["path"], _ = quic["key"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
path = grpc["serviceName"].(string)
|
||||
obj["path"] = grpc["serviceName"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
obj["type"] = "multi"
|
||||
}
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
obj["tls"] = security
|
||||
if security == "tls" {
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
if len(alpns) > 0 {
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
}
|
||||
obj["alpn"] = strings.Join(alpn, ",")
|
||||
}
|
||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||
if tlsSetting != nil {
|
||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
||||
sni, _ = sniValue.(string)
|
||||
obj["sni"], _ = sniValue.(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||
fp, _ = fpValue.(string)
|
||||
obj["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||
allowInsecure, _ = insecure.(bool)
|
||||
obj["allowInsecure"], _ = insecure.(bool)
|
||||
}
|
||||
}
|
||||
serverName, _ := tlsSetting["serverName"].(string)
|
||||
if serverName != "" {
|
||||
address = serverName
|
||||
obj["add"] = serverName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,24 +196,9 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
obj["id"] = clients[clientIndex].ID
|
||||
obj["aid"] = clients[clientIndex].AlterIds
|
||||
|
||||
obj := map[string]interface{}{
|
||||
"v": "2",
|
||||
"ps": email,
|
||||
"add": address,
|
||||
"port": inbound.Port,
|
||||
"id": clients[clientIndex].ID,
|
||||
"aid": clients[clientIndex].AlterIds,
|
||||
"net": network,
|
||||
"type": typeStr,
|
||||
"host": host,
|
||||
"path": path,
|
||||
"tls": security,
|
||||
"sni": sni,
|
||||
"fp": fp,
|
||||
"alpn": strings.Join(alpn, ","),
|
||||
"allowInsecure": allowInsecure,
|
||||
}
|
||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||
}
|
||||
@@ -266,6 +260,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -444,6 +441,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -590,7 +590,11 @@ func searchHost(headers interface{}) string {
|
||||
switch v.(type) {
|
||||
case []interface{}:
|
||||
hosts, _ := v.([]interface{})
|
||||
return hosts[0].(string)
|
||||
if len(hosts) > 0 {
|
||||
return hosts[0].(string)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
case interface{}:
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
@@ -404,38 +404,36 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserName string) {
|
||||
}
|
||||
|
||||
func (t *Tgbot) searchClient(chatId int64, email string) {
|
||||
traffics, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
msg := "❌ Something went wrong!"
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
if len(traffics) == 0 {
|
||||
if traffic == nil {
|
||||
msg := "No result!"
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
for _, traffic := range traffics {
|
||||
expiryTime := ""
|
||||
if traffic.ExpiryTime == 0 {
|
||||
expiryTime = "♾Unlimited"
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
} else {
|
||||
total = common.FormatTraffic((traffic.Total))
|
||||
}
|
||||
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
||||
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||
total, expiryTime)
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
expiryTime := ""
|
||||
if traffic.ExpiryTime == 0 {
|
||||
expiryTime = "♾Unlimited"
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d days", traffic.ExpiryTime/-86400000)
|
||||
} else {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
total := ""
|
||||
if traffic.Total == 0 {
|
||||
total = "♾Unlimited"
|
||||
} else {
|
||||
total = common.FormatTraffic((traffic.Total))
|
||||
}
|
||||
output := fmt.Sprintf("💡 Active: %t\r\n📧 Email: %s\r\n🔼 Upload↑: %s\r\n🔽 Download↓: %s\r\n🔄 Total: %s / %s\r\n📅 Expire in: %s\r\n",
|
||||
traffic.Enable, traffic.Email, common.FormatTraffic(traffic.Up), common.FormatTraffic(traffic.Down), common.FormatTraffic((traffic.Up + traffic.Down)),
|
||||
total, expiryTime)
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
}
|
||||
|
||||
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||
|
||||
@@ -118,6 +118,9 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
||||
delete(c, key)
|
||||
}
|
||||
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||
c["flow"] = "xtls-rprx-vision"
|
||||
}
|
||||
}
|
||||
final_clients = append(final_clients, interface{}(c))
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package session
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"x-ui/database/model"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"x-ui/database/model"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,6 +22,15 @@ func SetLoginUser(c *gin.Context, user *model.User) error {
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func SetMaxAge(c *gin.Context, maxAge int) error {
|
||||
s := sessions.Default(c)
|
||||
s.Options(sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: maxAge,
|
||||
})
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func GetLoginUser(c *gin.Context) *model.User {
|
||||
s := sessions.Default(c)
|
||||
obj := s.Get(loginUser)
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"totalDownUp" = "Total uploads/downloads"
|
||||
"totalUsage" = "Total usage"
|
||||
"inboundCount" = "Number of inbound"
|
||||
"operate" = "Actions"
|
||||
"operate" = "Menu"
|
||||
"enable" = "Enable"
|
||||
"remark" = "Remark"
|
||||
"protocol" = "Protocol"
|
||||
@@ -107,6 +107,7 @@
|
||||
"expireDate" = "Expire date"
|
||||
"resetTraffic" = "Reset traffic"
|
||||
"addInbound" = "Add Inbound"
|
||||
"generalActions" = "General Actions"
|
||||
"addTo" = "Create"
|
||||
"revise" = "Update"
|
||||
"modifyInbound" = "Modify InBound"
|
||||
@@ -144,16 +145,22 @@
|
||||
"resetAllTrafficCancelText" = "Cancel"
|
||||
"IPLimit" = "IP Limit"
|
||||
"IPLimitDesc" = "Disable inbound if the count exceeds the entered value (Enter 0 to disable IP limit)"
|
||||
"resetAllClientTraffics" = "Reset Clients Traffic"
|
||||
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||
"resetInboundClientTrafficTitle" = "Reset all clients traffic"
|
||||
"resetInboundClientTrafficContent" = "Are you sure to reset all traffics of this inbound's clients ?"
|
||||
"resetAllClientTraffics" = "Reset All Clients Traffic"
|
||||
"resetAllClientTrafficTitle" = "Reset all clients traffic"
|
||||
"resetAllClientTrafficContent" = "Confirm reset of all traffic for clients of this inbound?"
|
||||
"resetAllClientTrafficContent" = "Are you sure to reset all traffics of all clients ?"
|
||||
"delDepletedClients" = "Delete depleted clients"
|
||||
"delDepletedClientsTitle" = "Delete depleted clients"
|
||||
"delDepletedClientsContent" = "Are you sure to delete all depleted clients ?"
|
||||
"Email" = "Email"
|
||||
"EmailDesc" = "Please provide a unique email address"
|
||||
"IPLimitlog" = "IP Log"
|
||||
"IPLimitlogDesc" = "IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log)"
|
||||
"IPLimitlogclear" = "Clear The Log"
|
||||
"setDefaultCert" = "Set cert from panel"
|
||||
"XTLSdec" = "Xray core needs to be 1.7.5 and below"
|
||||
"XTLSdec" = "Xray core needs to be 1.7.5"
|
||||
"Realitydec" = "Xray core needs to be 1.8.0 and above"
|
||||
|
||||
[pages.client]
|
||||
@@ -281,6 +288,8 @@
|
||||
"telegramNotifyTimeDesc" = "Using Crontab timing format. Restart the panel to take effect"
|
||||
"tgNotifyBackup" = "Database backup"
|
||||
"tgNotifyBackupDesc" = "Sending database backup file with report notification. Restart the panel to take effect"
|
||||
"sessionMaxAge" = "Session maximum age"
|
||||
"sessionMaxAgeDesc" = "The time that you can stay login (unit: minute)"
|
||||
"expireTimeDiff" = "Exhaustion time threshold"
|
||||
"expireTimeDiffDesc" = "Detect exhaustion before expiration (unit:day)"
|
||||
"trafficDiff" = "Exhaustion traffic threshold"
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"totalDownUp" = "جمع آپلود/دانلود"
|
||||
"totalUsage" = "جمع کل"
|
||||
"inboundCount" = "تعداد سرویس ها"
|
||||
"operate" = "عملیات"
|
||||
"operate" = "فهرست"
|
||||
"enable" = "فعال"
|
||||
"remark" = "نام"
|
||||
"protocol" = "پروتکل"
|
||||
@@ -107,6 +107,7 @@
|
||||
"expireDate" = "تاریخ انقضا"
|
||||
"resetTraffic" = "ریست ترافیک"
|
||||
"addInbound" = "اضافه کردن سرویس"
|
||||
"generalActions" = "عملیات کلی"
|
||||
"addTo" = "اضافه کردن"
|
||||
"revise" = "ویرایش"
|
||||
"modifyInbound" = "ویرایش سرویس"
|
||||
@@ -140,9 +141,15 @@
|
||||
"resetAllTraffic" = "ریست ترافیک کل سرویس ها"
|
||||
"resetAllTrafficTitle" = "ریست ترافیک کل سرویس ها"
|
||||
"resetAllTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک سرویس ها را ریست کنید؟"
|
||||
"resetInboundClientTraffics" = "ریست ترافیک کاربران"
|
||||
"resetInboundClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||
"resetInboundClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
||||
"resetAllClientTraffics" = "ریست ترافیک کاربران"
|
||||
"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران"
|
||||
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران این سرویس را ریست کنید؟"
|
||||
"resetAllClientTrafficContent" = "آیا مطمئن هستید که میخواهید تمام ترافیک کاربران را ریست کنید؟"
|
||||
"delDepletedClients" = "حذف کاربران منقضی"
|
||||
"delDepletedClientsTitle" = "حذف کاربران منقضی"
|
||||
"delDepletedClientsContent" = "آیا مطمئن هستید مه میخواهید تمامی کاربران منقضی شده را حذف کنید؟"
|
||||
"IPLimit" = "محدودیت ای پی"
|
||||
"IPLimitDesc" = "غیرفعال کردن ورودی در صورت بیش از تعداد وارد شده (0 برای غیرفعال کردن محدودیت ای پی )"
|
||||
"Email" = "ایمیل"
|
||||
@@ -151,7 +158,7 @@
|
||||
"IPLimitlogDesc" = "گزارش سابقه ای پی (قبل از فعال کردن ورودی پس از غیرفعال شدن توسط محدودیت ای پی، باید گزارش را پاک کنید)"
|
||||
"IPLimitlogclear" = "پاک کردن گزارش ها"
|
||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||
"XTLSdec" = "هسته Xray باید 1.7.5 و کمتر باشد"
|
||||
"XTLSdec" = "هسته Xray باید 1.7.5 باشد"
|
||||
"Realitydec" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||
|
||||
[pages.client]
|
||||
@@ -279,6 +286,8 @@
|
||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
||||
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||
"expireTimeDiffDesc" = "فاصله زمانی هشدار تا رسیدن به زمان انقضا (واحد: روز)"
|
||||
"trafficDiff" = "آستانه ترافیک باقی مانده"
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"totalDownUp" = "总上传 / 下载"
|
||||
"totalUsage" = "总用量"
|
||||
"inboundCount" = "入站数量"
|
||||
"operate" = "操作"
|
||||
"operate" = "菜单"
|
||||
"enable" = "启用"
|
||||
"remark" = "备注"
|
||||
"protocol" = "协议"
|
||||
@@ -107,6 +107,7 @@
|
||||
"expireDate" = "到期时间"
|
||||
"resetTraffic" = "重置流量"
|
||||
"addInbound" = "添加入"
|
||||
"generalActions" = "通用操作"
|
||||
"addTo" = "添加"
|
||||
"revise" = "修改"
|
||||
"modifyInbound" = "修改入站"
|
||||
@@ -140,9 +141,15 @@
|
||||
"resetAllTraffic" = "重置所有入站流量"
|
||||
"resetAllTrafficTitle" = "重置所有入站流量"
|
||||
"resetAllTrafficContent" = "您确定要重置所有入站流量吗?"
|
||||
"resetAllClientTraffics" = "重置客户端流量"
|
||||
"resetInboundClientTraffics" = "重置客户端流量"
|
||||
"resetInboundClientTrafficTitle" = "重置所有客户端流量"
|
||||
"resetInboundClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
||||
"resetAllClientTraffics" = "重置所有客户端流量"
|
||||
"resetAllClientTrafficTitle" = "重置所有客户端流量"
|
||||
"resetAllClientTrafficContent" = "您确定要重置此入站客户端的所有流量吗?"
|
||||
"resetAllClientTrafficContent" = "你确定要重置所有客户端的所有流量吗?"
|
||||
"delDepletedClients" = "删除耗尽的客户端"
|
||||
"delDepletedClientsTitle" = "删除耗尽的客户"
|
||||
"delDepletedClientsContent" = "你确定要删除所有耗尽的客户端吗?"
|
||||
"IPLimit" = "IP限制"
|
||||
"IPLimitDesc" = "如果超过输入的计数则禁用入站(0 表示禁用限制 ip)"
|
||||
"Email" = "电子邮件"
|
||||
@@ -151,7 +158,7 @@
|
||||
"IPLimitlogDesc" = "IP 历史日志 (通过IP限制禁用inbound之前,需要清空日志)"
|
||||
"IPLimitlogclear" = "清除日志"
|
||||
"setDefaultCert" = "从面板设置证书"
|
||||
"XTLSdec" = "Xray核心需要1.7.5及以下版本"
|
||||
"XTLSdec" = "Xray核心需要1.7.5"
|
||||
"Realitydec" = "Xray核心需要1.8.0及以上版本"
|
||||
|
||||
[pages.client]
|
||||
@@ -279,6 +286,8 @@
|
||||
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效"
|
||||
"tgNotifyBackup" = "数据库备份"
|
||||
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知。重启面板生效"
|
||||
"sessionMaxAge" = "会话最大年龄"
|
||||
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)"
|
||||
"expireTimeDiff" = "耗尽时间阈值"
|
||||
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)"
|
||||
"trafficDiff" = "耗尽流量阈值"
|
||||
|
||||
Reference in New Issue
Block a user