{{ i18n "comment" }} |
diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html
index b77e74e2..60af57cb 100644
--- a/web/html/modals/inbound_modal.html
+++ b/web/html/modals/inbound_modal.html
@@ -1,9 +1,7 @@
{{define "modals/inboundModal"}}
-
+
{{template "form/inbound"}}
-{{end}}
+{{end}}
\ No newline at end of file
diff --git a/web/html/modals/xray_rule_modal.html b/web/html/modals/xray_rule_modal.html
index 336a9d81..32a3f80c 100644
--- a/web/html/modals/xray_rule_modal.html
+++ b/web/html/modals/xray_rule_modal.html
@@ -1,11 +1,6 @@
{{define "modals/ruleModal"}}
-
-
- [[ dm ]]
-
-
@@ -123,7 +118,6 @@
confirm: null,
rule: {
type: "field",
- domainMatcher: "",
domain: "",
ip: "",
port: "",
@@ -157,7 +151,6 @@
this.confirm = confirm;
this.visible = true;
if (isEdit) {
- this.rule.domainMatcher = rule.domainMatcher;
this.rule.domain = rule.domain ? rule.domain.join(',') : [];
this.rule.ip = rule.ip ? rule.ip.join(',') : [];
this.rule.port = rule.port;
@@ -172,7 +165,6 @@
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
} else {
this.rule = {
- domainMatcher: "",
domain: "",
ip: "",
port: "",
@@ -214,7 +206,6 @@
rule = {};
newRule = {};
rule.type = "field";
- rule.domainMatcher = value.domainMatcher;
rule.domain = value.domain.length > 0 ? value.domain.split(',') : [];
rule.ip = value.ip.length > 0 ? value.ip.split(',') : [];
rule.port = value.port;
diff --git a/web/html/settings.html b/web/html/settings.html
index c7fa9bd8..3b6e81a1 100644
--- a/web/html/settings.html
+++ b/web/html/settings.html
@@ -207,7 +207,7 @@
settings: {
domainStrategy: "AsIs",
noises: [
- { type: "rand", packet: "10-20", delay: "10-16" },
+ { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" },
],
},
},
@@ -397,7 +397,7 @@
}
},
addNoise() {
- const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
+ const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
this.noisesArray = [...this.noisesArray, newNoise];
},
removeNoise(index) {
@@ -420,6 +420,11 @@
updatedNoises[index] = { ...updatedNoises[index], delay: value };
this.noisesArray = updatedNoises;
},
+ updateNoiseApplyTo(index, value) {
+ const updatedNoises = [...this.noisesArray];
+ updatedNoises[index] = { ...updatedNoises[index], applyTo: value };
+ this.noisesArray = updatedNoises;
+ },
},
computed: {
fragment: {
diff --git a/web/html/settings/panel/subscription/json.html b/web/html/settings/panel/subscription/json.html
index a3729984..c8575e38 100644
--- a/web/html/settings/panel/subscription/json.html
+++ b/web/html/settings/panel/subscription/json.html
@@ -90,6 +90,18 @@
placeholder="10-20">
+
+ ApplyTo
+
+ updateNoiseApplyTo(index, value)">
+
+ [[ p ]]
+
+
+
+
Remove
diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go
index b95c8ee2..5a30b616 100644
--- a/web/job/check_client_ip_job.go
+++ b/web/job/check_client_ip_job.go
@@ -11,7 +11,6 @@ import (
"sort"
"time"
- "slices"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
@@ -58,21 +57,21 @@ func (j *CheckClientIpJob) Run() {
func (j *CheckClientIpJob) clearAccessLog() {
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
j.checkError(err)
+ defer logAccessP.Close()
accessLogPath, err := xray.GetAccessLogPath()
j.checkError(err)
file, err := os.Open(accessLogPath)
j.checkError(err)
+ defer file.Close()
_, err = io.Copy(logAccessP, file)
j.checkError(err)
- logAccessP.Close()
- file.Close()
-
err = os.Truncate(accessLogPath, 0)
j.checkError(err)
+
j.lastClear = time.Now().Unix()
}
@@ -193,10 +192,6 @@ func (j *CheckClientIpJob) checkError(e error) {
}
}
-func (j *CheckClientIpJob) contains(s []string, str string) bool {
- return slices.Contains(s, str)
-}
-
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
db := database.GetDB()
InboundClientIps := &model.InboundClientIps{}
diff --git a/web/service/inbound.go b/web/service/inbound.go
index 06280ff8..7f464578 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -178,17 +178,42 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
return inbound, false, err
}
+ // Ensure created_at and updated_at on clients in settings
+ if len(clients) > 0 {
+ var settings map[string]any
+ if err2 := json.Unmarshal([]byte(inbound.Settings), &settings); err2 == nil && settings != nil {
+ now := time.Now().Unix() * 1000
+ updatedClients := make([]model.Client, 0, len(clients))
+ for _, c := range clients {
+ if c.CreatedAt == 0 {
+ c.CreatedAt = now
+ }
+ c.UpdatedAt = now
+ updatedClients = append(updatedClients, c)
+ }
+ settings["clients"] = updatedClients
+ if bs, err3 := json.MarshalIndent(settings, "", " "); err3 == nil {
+ inbound.Settings = string(bs)
+ } else {
+ logger.Debug("Unable to marshal inbound settings with timestamps:", err3)
+ }
+ } else if err2 != nil {
+ logger.Debug("Unable to parse inbound settings for timestamps:", err2)
+ }
+ }
+
// Secure client ID
for _, client := range clients {
- if inbound.Protocol == "trojan" {
+ switch inbound.Protocol {
+ case "trojan":
if client.Password == "" {
return inbound, false, common.NewError("empty client ID")
}
- } else if inbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
if client.Email == "" {
return inbound, false, common.NewError("empty client ID")
}
- } else {
+ default:
if client.ID == "" {
return inbound, false, common.NewError("empty client ID")
}
@@ -322,6 +347,65 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
return inbound, false, err
}
+ // Ensure created_at and updated_at exist in inbound.Settings clients
+ {
+ var oldSettings map[string]any
+ _ = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
+ emailToCreated := map[string]int64{}
+ emailToUpdated := map[string]int64{}
+ if oldSettings != nil {
+ if oc, ok := oldSettings["clients"].([]any); ok {
+ for _, it := range oc {
+ if m, ok2 := it.(map[string]any); ok2 {
+ if email, ok3 := m["email"].(string); ok3 {
+ switch v := m["created_at"].(type) {
+ case float64:
+ emailToCreated[email] = int64(v)
+ case int64:
+ emailToCreated[email] = v
+ }
+ switch v := m["updated_at"].(type) {
+ case float64:
+ emailToUpdated[email] = int64(v)
+ case int64:
+ emailToUpdated[email] = v
+ }
+ }
+ }
+ }
+ }
+ }
+ var newSettings map[string]any
+ if err2 := json.Unmarshal([]byte(inbound.Settings), &newSettings); err2 == nil && newSettings != nil {
+ now := time.Now().Unix() * 1000
+ if nSlice, ok := newSettings["clients"].([]any); ok {
+ for i := range nSlice {
+ if m, ok2 := nSlice[i].(map[string]any); ok2 {
+ email, _ := m["email"].(string)
+ if _, ok3 := m["created_at"]; !ok3 {
+ if v, ok4 := emailToCreated[email]; ok4 && v > 0 {
+ m["created_at"] = v
+ } else {
+ m["created_at"] = now
+ }
+ }
+ // Preserve client's updated_at if present; do not bump on parent inbound update
+ if _, hasUpdated := m["updated_at"]; !hasUpdated {
+ if v, ok4 := emailToUpdated[email]; ok4 && v > 0 {
+ m["updated_at"] = v
+ }
+ }
+ nSlice[i] = m
+ }
+ }
+ newSettings["clients"] = nSlice
+ if bs, err3 := json.MarshalIndent(newSettings, "", " "); err3 == nil {
+ inbound.Settings = string(bs)
+ }
+ }
+ }
+ }
+
oldInbound.Up = inbound.Up
oldInbound.Down = inbound.Down
oldInbound.Total = inbound.Total
@@ -334,7 +418,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing
- oldInbound.Allocate = inbound.Allocate
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
} else {
@@ -424,6 +507,17 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
}
interfaceClients := settings["clients"].([]any)
+ // Add timestamps for new clients being appended
+ nowTs := time.Now().Unix() * 1000
+ for i := range interfaceClients {
+ if cm, ok := interfaceClients[i].(map[string]any); ok {
+ if _, ok2 := cm["created_at"]; !ok2 {
+ cm["created_at"] = nowTs
+ }
+ cm["updated_at"] = nowTs
+ interfaceClients[i] = cm
+ }
+ }
existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil {
return false, err
@@ -439,15 +533,16 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
// Secure client ID
for _, client := range clients {
- if oldInbound.Protocol == "trojan" {
+ switch oldInbound.Protocol {
+ case "trojan":
if client.Password == "" {
return false, common.NewError("empty client ID")
}
- } else if oldInbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
if client.Email == "" {
return false, common.NewError("empty client ID")
}
- } else {
+ default:
if client.ID == "" {
return false, common.NewError("empty client ID")
}
@@ -644,13 +739,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
clientIndex := -1
for index, oldClient := range oldClients {
oldClientId := ""
- if oldInbound.Protocol == "trojan" {
+ switch oldInbound.Protocol {
+ case "trojan":
oldClientId = oldClient.Password
newClientId = clients[0].Password
- } else if oldInbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
oldClientId = oldClient.Email
newClientId = clients[0].Email
- } else {
+ default:
oldClientId = oldClient.ID
newClientId = clients[0].ID
}
@@ -682,6 +778,25 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err
}
settingsClients := oldSettings["clients"].([]any)
+ // Preserve created_at and set updated_at for the replacing client
+ var preservedCreated any
+ if clientIndex >= 0 && clientIndex < len(settingsClients) {
+ if oldMap, ok := settingsClients[clientIndex].(map[string]any); ok {
+ if v, ok2 := oldMap["created_at"]; ok2 {
+ preservedCreated = v
+ }
+ }
+ }
+ if len(interfaceClients) > 0 {
+ if newMap, ok := interfaceClients[0].(map[string]any); ok {
+ if preservedCreated == nil {
+ preservedCreated = time.Now().Unix() * 1000
+ }
+ newMap["created_at"] = preservedCreated
+ newMap["updated_at"] = time.Now().Unix() * 1000
+ interfaceClients[0] = newMap
+ }
+ }
settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients
@@ -830,8 +945,9 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
if traffic.IsInbound {
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
Updates(map[string]any{
- "up": gorm.Expr("up + ?", traffic.Up),
- "down": gorm.Expr("down + ?", traffic.Down),
+ "up": gorm.Expr("up + ?", traffic.Up),
+ "down": gorm.Expr("down + ?", traffic.Down),
+ "all_time": gorm.Expr("COALESCE(all_time, 0) + ?", traffic.Up+traffic.Down),
}).Error
if err != nil {
return err
@@ -877,10 +993,12 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
+ dbClientTraffics[dbTraffic_index].AllTime += (traffics[traffic_index].Up + traffics[traffic_index].Down)
// Add user in onlineUsers array on traffic
if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 {
onlineClients = append(onlineClients, traffics[traffic_index].Email)
+ dbClientTraffics[dbTraffic_index].LastOnline = time.Now().UnixMilli()
}
break
}
@@ -925,10 +1043,16 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
oldExpiryTime := c["expiryTime"].(float64)
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
c["expiryTime"] = newExpiryTime
+ c["updated_at"] = time.Now().Unix() * 1000
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
break
}
}
+ // Backfill created_at and updated_at
+ if _, ok := c["created_at"]; !ok {
+ c["created_at"] = time.Now().Unix() * 1000
+ }
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
settings["clients"] = newClients
@@ -1263,11 +1387,12 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
for _, oldClient := range oldClients {
if oldClient.Email == clientEmail {
- if inbound.Protocol == "trojan" {
+ switch inbound.Protocol {
+ case "trojan":
clientId = oldClient.Password
- } else if inbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
clientId = oldClient.Email
- } else {
+ default:
clientId = oldClient.ID
}
break
@@ -1289,6 +1414,7 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
c := clients[client_index].(map[string]any)
if c["email"] == clientEmail {
c["tgId"] = tgId
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
}
@@ -1347,11 +1473,12 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
for _, oldClient := range oldClients {
if oldClient.Email == clientEmail {
- if inbound.Protocol == "trojan" {
+ switch inbound.Protocol {
+ case "trojan":
clientId = oldClient.Password
- } else if inbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
clientId = oldClient.Email
- } else {
+ default:
clientId = oldClient.ID
}
clientOldEnabled = oldClient.Enable
@@ -1374,6 +1501,7 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
c := clients[client_index].(map[string]any)
if c["email"] == clientEmail {
c["enable"] = !clientOldEnabled
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
}
@@ -1410,11 +1538,12 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
for _, oldClient := range oldClients {
if oldClient.Email == clientEmail {
- if inbound.Protocol == "trojan" {
+ switch inbound.Protocol {
+ case "trojan":
clientId = oldClient.Password
- } else if inbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
clientId = oldClient.Email
- } else {
+ default:
clientId = oldClient.ID
}
break
@@ -1436,6 +1565,7 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
c := clients[client_index].(map[string]any)
if c["email"] == clientEmail {
c["limitIp"] = count
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
}
@@ -1467,11 +1597,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
for _, oldClient := range oldClients {
if oldClient.Email == clientEmail {
- if inbound.Protocol == "trojan" {
+ switch inbound.Protocol {
+ case "trojan":
clientId = oldClient.Password
- } else if inbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
clientId = oldClient.Email
- } else {
+ default:
clientId = oldClient.ID
}
break
@@ -1493,6 +1624,7 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
c := clients[client_index].(map[string]any)
if c["email"] == clientEmail {
c["expiryTime"] = expiry_time
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
}
@@ -1527,11 +1659,12 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
for _, oldClient := range oldClients {
if oldClient.Email == clientEmail {
- if inbound.Protocol == "trojan" {
+ switch inbound.Protocol {
+ case "trojan":
clientId = oldClient.Password
- } else if inbound.Protocol == "shadowsocks" {
+ case "shadowsocks":
clientId = oldClient.Email
- } else {
+ default:
clientId = oldClient.ID
}
break
@@ -1553,6 +1686,7 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
c := clients[client_index].(map[string]any)
if c["email"] == clientEmail {
c["totalGB"] = totalGB * 1024 * 1024 * 1024
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
}
@@ -1935,6 +2069,25 @@ func (s *InboundService) MigrationRequirements() {
}
}()
+ // Calculate and backfill all_time from up+down for inbounds and clients
+ err = tx.Exec(`
+ UPDATE inbounds
+ SET all_time = IFNULL(up, 0) + IFNULL(down, 0)
+ WHERE IFNULL(all_time, 0) = 0 AND (IFNULL(up, 0) + IFNULL(down, 0)) > 0
+ `).Error
+ if err != nil {
+ return
+ }
+ err = tx.Exec(`
+ UPDATE client_traffics
+ SET all_time = IFNULL(up, 0) + IFNULL(down, 0)
+ WHERE IFNULL(all_time, 0) = 0 AND (IFNULL(up, 0) + IFNULL(down, 0)) > 0
+ `).Error
+
+ if err != nil {
+ return
+ }
+
// Fix inbounds based problems
var inbounds []*model.Inbound
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
@@ -1973,6 +2126,11 @@ func (s *InboundService) MigrationRequirements() {
c["flow"] = ""
}
}
+ // Backfill created_at and updated_at
+ if _, ok := c["created_at"]; !ok {
+ c["created_at"] = time.Now().Unix() * 1000
+ }
+ c["updated_at"] = time.Now().Unix() * 1000
newClients = append(newClients, any(c))
}
settings["clients"] = newClients
@@ -2061,6 +2219,20 @@ func (s *InboundService) GetOnlineClients() []string {
return p.GetOnlineClients()
}
+func (s *InboundService) GetClientsLastOnline() (map[string]int64, error) {
+ db := database.GetDB()
+ var rows []xray.ClientTraffic
+ err := db.Model(&xray.ClientTraffic{}).Select("email, last_online").Find(&rows).Error
+ if err != nil && err != gorm.ErrRecordNotFound {
+ return nil, err
+ }
+ result := make(map[string]int64, len(rows))
+ for _, r := range rows {
+ result[r.Email] = r.LastOnline
+ }
+ return result, nil
+}
+
func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, []string, error) {
db := database.GetDB()
diff --git a/web/service/server.go b/web/service/server.go
index 6b4e5d63..3078e88b 100644
--- a/web/service/server.go
+++ b/web/service/server.go
@@ -235,8 +235,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
}
// IP fetching with caching
- showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"}
- showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"}
+ showIp4ServiceLists := []string{
+ "https://api4.ipify.org",
+ "https://ipv4.icanhazip.com",
+ "https://v4.api.ipinfo.io/ip",
+ "https://ipv4.myexternalip.com/raw",
+ "https://4.ident.me",
+ "https://check-host.net/ip",
+ }
+ showIp6ServiceLists := []string{
+ "https://api6.ipify.org",
+ "https://ipv6.icanhazip.com",
+ "https://v6.api.ipinfo.io/ip",
+ "https://ipv6.myexternalip.com/raw",
+ "https://6.ident.me",
+ }
if s.cachedIPv4 == "" {
for _, ip4Service := range showIp4ServiceLists {
@@ -858,3 +871,53 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
"echConfigList": configList,
}, nil
}
+
+type AuthBlock struct {
+ Label string `json:"label"`
+ Decryption string `json:"decryption"`
+ Encryption string `json:"encryption"`
+}
+
+func (s *ServerService) GetNewVlessEnc() (any, error) {
+ cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ if err := cmd.Run(); err != nil {
+ return nil, err
+ }
+
+ lines := strings.Split(out.String(), "\n")
+
+ var blocks []AuthBlock
+ var current *AuthBlock
+
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if strings.HasPrefix(line, "Authentication:") {
+ if current != nil {
+ blocks = append(blocks, *current)
+ }
+ current = &AuthBlock{Label: strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))}
+ } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
+ parts := strings.SplitN(line, ":", 2)
+ if len(parts) == 2 && current != nil {
+ key := strings.Trim(parts[0], `" `)
+ val := strings.Trim(parts[1], `" `)
+ switch key {
+ case "decryption":
+ current.Decryption = val
+ case "encryption":
+ current.Encryption = val
+ }
+ }
+ }
+ }
+
+ if current != nil {
+ blocks = append(blocks, *current)
+ }
+
+ return map[string]any{
+ "auths": blocks,
+ }, nil
+}
diff --git a/web/service/tgbot.go b/web/service/tgbot.go
index 301f64fd..e957ff9f 100644
--- a/web/service/tgbot.go
+++ b/web/service/tgbot.go
@@ -40,7 +40,6 @@ var (
isRunning bool
hostname string
hashStorage *global.HashStorage
- handler *th.Handler
// clients data to adding new client
receiver_inbound_ID int
@@ -641,13 +640,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3])
if err == nil {
- if num == -2 {
+ switch num {
+ case -2:
inputNumber = 0
- } else if num == -1 {
+ case -1:
if inputNumber > 0 {
inputNumber = (inputNumber / 10)
}
- } else {
+ default:
inputNumber = (inputNumber * 10) + num
}
}
@@ -704,6 +704,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
+ if err != nil {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
+ return
+ }
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
@@ -715,13 +719,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2])
if err == nil {
- if num == -2 {
+ switch num {
+ case -2:
inputNumber = 0
- } else if num == -1 {
+ case -1:
if inputNumber > 0 {
inputNumber = (inputNumber / 10)
}
- } else {
+ default:
inputNumber = (inputNumber * 10) + num
}
}
@@ -844,13 +849,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3])
if err == nil {
- if num == -2 {
+ switch num {
+ case -2:
inputNumber = 0
- } else if num == -1 {
+ case -1:
if inputNumber > 0 {
inputNumber = (inputNumber / 10)
}
- } else {
+ default:
inputNumber = (inputNumber * 10) + num
}
}
@@ -919,6 +925,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
+ if err != nil {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
+ return
+ }
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
@@ -930,13 +940,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2])
if err == nil {
- if num == -2 {
+ switch num {
+ case -2:
inputNumber = 0
- } else if num == -1 {
+ case -1:
if inputNumber > 0 {
inputNumber = (inputNumber / 10)
}
- } else {
+ default:
inputNumber = (inputNumber * 10) + num
}
}
@@ -1035,13 +1046,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 4 {
num, err := strconv.Atoi(dataArray[3])
if err == nil {
- if num == -2 {
+ switch num {
+ case -2:
inputNumber = 0
- } else if num == -1 {
+ case -1:
if inputNumber > 0 {
inputNumber = (inputNumber / 10)
}
- } else {
+ default:
inputNumber = (inputNumber * 10) + num
}
}
@@ -1101,6 +1113,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
+ if err != nil {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
+ return
+ }
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
@@ -1112,13 +1128,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if len(dataArray) == 3 {
num, err := strconv.Atoi(dataArray[2])
if err == nil {
- if num == -2 {
+ switch num {
+ case -2:
inputNumber = 0
- } else if num == -1 {
+ case -1:
if inputNumber > 0 {
inputNumber = (inputNumber / 10)
}
- } else {
+ default:
inputNumber = (inputNumber * 10) + num
}
}
@@ -1288,6 +1305,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
+ if err != nil {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
+ return
+ }
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
}
@@ -1524,6 +1545,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
+ if err != nil {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
+ return
+ }
t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_default_ip_limit":
@@ -1534,6 +1559,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
+ if err != nil {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
+ return
+ }
t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_submit_disable":
@@ -1598,6 +1627,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
return
}
valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
+ if err != nil {
+ t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
+ return
+ }
for _, valid_emails := range valid_emails {
traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
@@ -1760,6 +1793,10 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
}
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
+ if err != nil {
+ logger.Warning("BuildJSONForProtocol run failed:", err)
+ return false, errors.New("failed to build JSON for protocol")
+ }
newInbound := &model.Inbound{
Id: receiver_inbound_ID,
@@ -2008,10 +2045,11 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
}
msg := ""
- if status == LoginSuccess {
+ switch status {
+ case LoginSuccess:
msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
- } else if status == LoginFail {
+ case LoginFail:
msg += t.I18nBot("tgbot.messages.loginFailed")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
@@ -2171,6 +2209,22 @@ func (t *Tgbot) clientInfoMsg(
expiryTime = t.I18nBot("tgbot.unlimited")
} else if diff > 172800 || !traffic.Enable {
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
+ if diff > 0 {
+ days := diff / 86400
+ hours := (diff % 86400) / 3600
+ minutes := (diff % 3600) / 60
+ remainingTime := ""
+ if days > 0 {
+ remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
+ }
+ if hours > 0 {
+ remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
+ }
+ if minutes > 0 {
+ remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
+ }
+ expiryTime += fmt.Sprintf(" (%s)", remainingTime)
+ }
} else if traffic.ExpiryTime < 0 {
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
flag = true
diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml
index 19d451fa..9f11a30c 100644
--- a/web/translation/translate.ar_EG.toml
+++ b/web/translation/translate.ar_EG.toml
@@ -50,6 +50,7 @@
"fail" = "فشل"
"comment" = "تعليق"
"success" = "تم بنجاح"
+"lastOnline" = "آخر متصل"
"getVersion" = "جيب النسخة"
"install" = "تثبيت"
"clients" = "عملاء"
@@ -151,6 +152,8 @@
"getConfigError" = "حدث خطأ أثناء استرجاع ملف الإعدادات"
[pages.inbounds]
+"allTimeTraffic" = "إجمالي حركة المرور"
+"allTimeTrafficUsage" = "إجمالي الاستخدام طوال الوقت"
"title" = "الإدخالات"
"totalDownUp" = "إجمالي المرسل/المستقبل"
"totalUsage" = "إجمالي الاستخدام"
@@ -165,6 +168,8 @@
"details" = "تفاصيل"
"transportConfig" = "نقل"
"expireDate" = "المدة"
+"createdAt" = "تاريخ الإنشاء"
+"updatedAt" = "تاريخ التحديث"
"resetTraffic" = "إعادة ضبط الترافيك"
"addInbound" = "أضف إدخال"
"generalActions" = "إجراءات عامة"
@@ -262,6 +267,7 @@
"trafficGetError" = "خطأ في الحصول على حركات المرور"
"getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519."
"getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65."
+"getNewVlessEncError" = "حدث خطأ أثناء الحصول على VlessEnc."
[pages.inbounds.stream.general]
"request" = "طلب"
@@ -561,24 +567,25 @@
"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة"
[tgbot]
-"keyboardClosed" = "❌ الكيبورد المخصص اتقفلت!"
-"noResult" = "❗ مفيش نتيجة!"
-"noQuery" = "❌ مش لاقي السؤال! استخدم الأمر تاني!"
-"wentWrong" = "❌ حصل خطأ!"
-"noIpRecord" = "❗ مفيش سجل IP!"
-"noInbounds" = "❗ مفيش إدخال متواجد!"
-"unlimited" = "♾ غير محدود (إعادة ضبط)"
-"add" = "أضف"
+"keyboardClosed" = "❌ لوحة المفاتيح مغلقة!"
+"noResult" = "❗ لا يوجد نتائج!"
+"noQuery" = "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!"
+"wentWrong" = "❌ حدث خطأ ما!"
+"noIpRecord" = "❗ لا يوجد سجل IP!"
+"noInbounds" = "❗ لم يتم العثور على أي وارد!"
+"unlimited" = "♾ غير محدود (إعادة تعيين)"
+"add" = "إضافة"
"month" = "شهر"
-"months" = "شهور"
+"months" = "أشهر"
"day" = "يوم"
"days" = "أيام"
"hours" = "ساعات"
-"unknown" = "مش معروف"
-"inbounds" = "الإدخالات"
+"minutes" = "دقائق"
+"unknown" = "غير معروف"
+"inbounds" = "الواردات"
"clients" = "العملاء"
-"offline" = "🔴 أوفلاين"
-"online" = "🟢 أونلاين"
+"offline" = "🔴 غير متصل"
+"online" = "🟢 متصل"
[tgbot.commands]
"unknown" = "❗ أمر مش معروف."
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index e0ce49aa..f802dd6d 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -50,6 +50,7 @@
"fail" = "Failed"
"comment" = "Comment"
"success" = "Successfully"
+"lastOnline" = "Last Online"
"getVersion" = "Get Version"
"install" = "Install"
"clients" = "Clients"
@@ -151,6 +152,8 @@
"getConfigError" = "An error occurred while retrieving the config file."
[pages.inbounds]
+"allTimeTraffic" = "All-time Traffic"
+"allTimeTrafficUsage" = "All Time Total Usage"
"title" = "Inbounds"
"totalDownUp" = "Total Sent/Received"
"totalUsage" = "Total Usage"
@@ -165,6 +168,8 @@
"details" = "Details"
"transportConfig" = "Transport"
"expireDate" = "Duration"
+"createdAt" = "Created"
+"updatedAt" = "Updated"
"resetTraffic" = "Reset Traffic"
"addInbound" = "Add Inbound"
"generalActions" = "General Actions"
@@ -262,6 +267,7 @@
"trafficGetError" = "Error getting traffics."
"getNewX25519CertError" = "Error while obtaining the X25519 certificate."
"getNewmldsa65Error" = "Error while obtaining mldsa65."
+"getNewVlessEncError" = "Error while obtaining VlessEnc."
[pages.inbounds.stream.general]
"request" = "Request"
@@ -574,6 +580,7 @@
"day" = "Day"
"days" = "Days"
"hours" = "Hours"
+"minutes" = "Minutes"
"unknown" = "Unknown"
"inbounds" = "Inbounds"
"clients" = "Clients"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index 73cf110b..80ddd98a 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -50,6 +50,7 @@
"fail" = "Falló"
"comment" = "Comentario"
"success" = "Éxito"
+"lastOnline" = "Última conexión"
"getVersion" = "Obtener versión"
"install" = "Instalar"
"clients" = "Clientes"
@@ -91,7 +92,7 @@
"invalidFormData" = "El formato de los datos de entrada es inválido."
"emptyUsername" = "Por favor ingresa el nombre de usuario."
"emptyPassword" = "Por favor ingresa la contraseña."
-"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
+"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto."
"successLogin" = "Has iniciado sesión en tu cuenta correctamente."
[pages.index]
@@ -151,6 +152,8 @@
"getConfigError" = "Ocurrió un error al obtener el archivo de configuración"
[pages.inbounds]
+"allTimeTraffic" = "Tráfico Total"
+"allTimeTrafficUsage" = "Uso total de todos los tiempos"
"title" = "Entradas"
"totalDownUp" = "Subidas/Descargas Totales"
"totalUsage" = "Uso Total"
@@ -165,6 +168,8 @@
"details" = "Detalles"
"transportConfig" = "Transporte"
"expireDate" = "Fecha de Expiración"
+"createdAt" = "Creado"
+"updatedAt" = "Actualizado"
"resetTraffic" = "Restablecer Tráfico"
"addInbound" = "Agregar Entrada"
"generalActions" = "Acciones Generales"
@@ -262,6 +267,7 @@
"trafficGetError" = "Error al obtener los tráficos"
"getNewX25519CertError" = "Error al obtener el certificado X25519."
"getNewmldsa65Error" = "Error al obtener el certificado mldsa65."
+"getNewVlessEncError" = "Error al obtener el certificado VlessEnc."
[pages.inbounds.stream.general]
"request" = "Pedido"
@@ -535,9 +541,9 @@
[pages.settings.security]
"admin" = "Credenciales de administrador"
-"twoFactor" = "Autenticación de dos factores"
-"twoFactorEnable" = "Habilitar 2FA"
-"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
+"twoFactor" = "Autenticación de dos factores"
+"twoFactorEnable" = "Habilitar 2FA"
+"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad."
"twoFactorModalSetTitle" = "Activar autenticación de dos factores"
"twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores"
"twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:"
@@ -561,23 +567,24 @@
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
[tgbot]
-"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"
-"noResult" = "❗ ¡Sin resultados!"
-"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!"
+"keyboardClosed" = "❌ Teclado cerrado!"
+"noResult" = "❗ ¡No hay resultados!"
+"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando de nuevo!"
"wentWrong" = "❌ ¡Algo salió mal!"
-"noIpRecord" = "❗ ¡Sin Registro de IP!"
+"noIpRecord" = "❗ ¡No hay registro de IP!"
"noInbounds" = "❗ ¡No se encontraron entradas!"
-"unlimited" = "♾ Ilimitado"
-"add" = "Agregar"
+"unlimited" = "♾ Ilimitado (Restablecer)"
+"add" = "Añadir"
"month" = "Mes"
"months" = "Meses"
"day" = "Día"
"days" = "Días"
"hours" = "Horas"
+"minutes" = "Minutos"
"unknown" = "Desconocido"
"inbounds" = "Entradas"
"clients" = "Clientes"
-"offline" = "🔴 Sin conexión"
+"offline" = "🔴 Desconectado"
"online" = "🟢 En línea"
[tgbot.commands]
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 8e2b04a2..778dd528 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -50,6 +50,7 @@
"fail" = "ناموفق"
"comment" = "توضیحات"
"success" = "موفق"
+"lastOnline" = "آخرین فعالیت"
"getVersion" = "دریافت نسخه"
"install" = "نصب"
"clients" = "کاربران"
@@ -151,6 +152,8 @@
"getConfigError" = "خطا در دریافت فایل پیکربندی"
[pages.inbounds]
+"allTimeTraffic" = "کل ترافیک"
+"allTimeTrafficUsage" = "کل استفاده در تمام مدت"
"title" = "کاربران"
"totalDownUp" = "دریافت/ارسال کل"
"totalUsage" = "مصرف کل"
@@ -165,6 +168,8 @@
"details" = "توضیحات"
"transportConfig" = "نحوه اتصال"
"expireDate" = "مدت زمان"
+"createdAt" = "ایجاد"
+"updatedAt" = "بهروزرسانی"
"resetTraffic" = "ریست ترافیک"
"addInbound" = "افزودن ورودی"
"generalActions" = "عملیات کلی"
@@ -262,6 +267,7 @@
"trafficGetError" = "خطا در دریافت ترافیکها"
"getNewX25519CertError" = "خطا در دریافت گواهی X25519."
"getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65."
+"getNewVlessEncError" = "خطا در دریافت گواهی VlessEnc."
[pages.inbounds.stream.general]
"request" = "درخواست"
@@ -561,22 +567,23 @@
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
[tgbot]
-"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
-"noResult" = "❗ نتیجهای یافت نشد!"
-"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
-"wentWrong" = "❌ مشکلی رخ داده است!"
-"noIpRecord" = "❗ رکورد IP یافت نشد!"
+"keyboardClosed" = "❌ صفحه کلید بسته شد!"
+"noResult" = "❗ نتیجه ای یافت نشد!"
+"noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!"
+"wentWrong" = "❌ مشکلی پیش آمد!"
+"noIpRecord" = "❗ رکورد آی پی وجود ندارد!"
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
-"unlimited" = "♾ - نامحدود(ریست)"
-"add" = "اضافه کردن"
+"unlimited" = "♾ نامحدود(ریست)"
+"add" = "افزودن"
"month" = "ماه"
-"months" = "ماه"
+"months" = "ماه"
"day" = "روز"
"days" = "روز"
-"hours" = "ساعت"
+"hours" = "ساعت"
+"minutes" = "دقیقه"
"unknown" = "نامشخص"
-"inbounds" = "ورودیها"
-"clients" = "کلاینتها"
+"inbounds" = "ورودی ها"
+"clients" = "کاربران"
"offline" = "🔴 آفلاین"
"online" = "🟢 آنلاین"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
index bb30ead9..2a5b568b 100644
--- a/web/translation/translate.id_ID.toml
+++ b/web/translation/translate.id_ID.toml
@@ -50,6 +50,7 @@
"fail" = "Gagal"
"comment" = "Komentar"
"success" = "Berhasil"
+"lastOnline" = "Terakhir online"
"getVersion" = "Dapatkan Versi"
"install" = "Instal"
"clients" = "Klien"
@@ -151,6 +152,8 @@
"getConfigError" = "Terjadi kesalahan saat mengambil file konfigurasi"
[pages.inbounds]
+"allTimeTraffic" = "Total Lalu Lintas"
+"allTimeTrafficUsage" = "Total Penggunaan Sepanjang Waktu"
"title" = "Masuk"
"totalDownUp" = "Total Terkirim/Diterima"
"totalUsage" = "Penggunaan Total"
@@ -165,6 +168,8 @@
"details" = "Rincian"
"transportConfig" = "Transport"
"expireDate" = "Durasi"
+"createdAt" = "Dibuat"
+"updatedAt" = "Diperbarui"
"resetTraffic" = "Reset Traffic"
"addInbound" = "Tambahkan Masuk"
"generalActions" = "Tindakan Umum"
@@ -262,6 +267,7 @@
"trafficGetError" = "Gagal mendapatkan data lalu lintas"
"getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519."
"getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65."
+"getNewVlessEncError" = "Terjadi kesalahan saat mendapatkan sertifikat VlessEnc."
[pages.inbounds.stream.general]
"request" = "Permintaan"
@@ -561,21 +567,22 @@
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
[tgbot]
-"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
+"keyboardClosed" = "❌ Keyboard ditutup!"
"noResult" = "❗ Tidak ada hasil!"
-"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
-"wentWrong" = "❌ Ada yang salah!"
+"noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!"
+"wentWrong" = "❌ Terjadi kesalahan!"
"noIpRecord" = "❗ Tidak ada Catatan IP!"
-"noInbounds" = "❗ Tidak ada masuk ditemukan!"
-"unlimited" = "♾ Tak terbatas"
+"noInbounds" = "❗ Tidak ada inbound yang ditemukan!"
+"unlimited" = "♾ Tidak terbatas (Reset)"
"add" = "Tambah"
"month" = "Bulan"
"months" = "Bulan"
"day" = "Hari"
"days" = "Hari"
"hours" = "Jam"
+"minutes" = "Menit"
"unknown" = "Tidak diketahui"
-"inbounds" = "Masuk"
+"inbounds" = "Inbound"
"clients" = "Klien"
"offline" = "🔴 Offline"
"online" = "🟢 Online"
diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml
index eed4cc94..7af0a339 100644
--- a/web/translation/translate.ja_JP.toml
+++ b/web/translation/translate.ja_JP.toml
@@ -50,6 +50,7 @@
"fail" = "失敗"
"comment" = "コメント"
"success" = "成功"
+"lastOnline" = "最終オンライン"
"getVersion" = "バージョン取得"
"install" = "インストール"
"clients" = "クライアント"
@@ -151,6 +152,8 @@
"getConfigError" = "設定ファイルの取得中にエラーが発生しました"
[pages.inbounds]
+"allTimeTraffic" = "総トラフィック"
+"allTimeTrafficUsage" = "これまでの総使用量"
"title" = "インバウンド一覧"
"totalDownUp" = "総アップロード / ダウンロード"
"totalUsage" = "総使用量"
@@ -165,6 +168,8 @@
"details" = "詳細情報"
"transportConfig" = "トランスポート設定"
"expireDate" = "有効期限"
+"createdAt" = "作成"
+"updatedAt" = "更新"
"resetTraffic" = "トラフィックリセット"
"addInbound" = "インバウンド追加"
"generalActions" = "一般操作"
@@ -262,6 +267,7 @@
"trafficGetError" = "トラフィックの取得中にエラーが発生しました"
"getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。"
"getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。"
+"getNewVlessEncError" = "VlessEnc証明書の取得中にエラーが発生しました。"
[pages.inbounds.stream.general]
"request" = "リクエスト"
@@ -561,21 +567,22 @@
"resetOutboundTrafficError" = "送信トラフィックのリセットエラー"
[tgbot]
-"keyboardClosed" = "❌ カスタムキーボードが閉じられました!"
+"keyboardClosed" = "❌ キーボードを閉じました!"
"noResult" = "❗ 結果がありません!"
-"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!"
-"wentWrong" = "❌ 問題が発生しました!"
-"noIpRecord" = "❗ IP記録がありません!"
-"noInbounds" = "❗ インバウンド接続が見つかりません!"
-"unlimited" = "♾ 無制限"
+"noQuery" = "❌ クエリが見つかりません!コマンドを再利用してください!"
+"wentWrong" = "❌ 何かがうまくいかなかった!"
+"noIpRecord" = "❗ IPレコードがありません!"
+"noInbounds" = "❗ インバウンドが見つかりません!"
+"unlimited" = "♾ 無制限(リセット)"
"add" = "追加"
"month" = "月"
-"months" = "月"
+"months" = "ヶ月"
"day" = "日"
-"days" = "日"
+"days" = "日間"
"hours" = "時間"
+"minutes" = "分"
"unknown" = "不明"
-"inbounds" = "インバウンド接続"
+"inbounds" = "インバウンド"
"clients" = "クライアント"
"offline" = "🔴 オフライン"
"online" = "🟢 オンライン"
diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml
index 6f12077d..f61f8995 100644
--- a/web/translation/translate.pt_BR.toml
+++ b/web/translation/translate.pt_BR.toml
@@ -50,6 +50,7 @@
"fail" = "Falhou"
"comment" = "Comentário"
"success" = "Com Sucesso"
+"lastOnline" = "Última vez online"
"getVersion" = "Obter Versão"
"install" = "Instalar"
"clients" = "Clientes"
@@ -151,6 +152,8 @@
"getConfigError" = "Ocorreu um erro ao recuperar o arquivo de configuração"
[pages.inbounds]
+"allTimeTraffic" = "Tráfego Total"
+"allTimeTrafficUsage" = "Uso total de todos os tempos"
"title" = "Inbounds"
"totalDownUp" = "Total Enviado/Recebido"
"totalUsage" = "Uso Total"
@@ -165,6 +168,8 @@
"details" = "Detalhes"
"transportConfig" = "Transporte"
"expireDate" = "Duração"
+"createdAt" = "Criado"
+"updatedAt" = "Atualizado"
"resetTraffic" = "Redefinir Tráfego"
"addInbound" = "Adicionar Inbound"
"generalActions" = "Ações Gerais"
@@ -262,6 +267,7 @@
"trafficGetError" = "Erro ao obter tráfegos"
"getNewX25519CertError" = "Erro ao obter o certificado X25519."
"getNewmldsa65Error" = "Erro ao obter o certificado mldsa65."
+"getNewVlessEncError" = "Erro ao obter o certificado VlessEnc."
[pages.inbounds.stream.general]
"request" = "Requisição"
@@ -561,21 +567,22 @@
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
[tgbot]
-"keyboardClosed" = "❌ Teclado personalizado fechado!"
+"keyboardClosed" = "❌ Teclado fechado!"
"noResult" = "❗ Nenhum resultado!"
"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!"
"wentWrong" = "❌ Algo deu errado!"
"noIpRecord" = "❗ Nenhum registro de IP!"
-"noInbounds" = "❗ Nenhuma entrada encontrada!"
-"unlimited" = "♾ Ilimitado (Reiniciar)"
+"noInbounds" = "❗ Nenhum inbound encontrado!"
+"unlimited" = "♾ Ilimitado (Reset)"
"add" = "Adicionar"
"month" = "Mês"
"months" = "Meses"
"day" = "Dia"
"days" = "Dias"
"hours" = "Horas"
+"minutes" = "Minutos"
"unknown" = "Desconhecido"
-"inbounds" = "Entradas"
+"inbounds" = "Inbounds"
"clients" = "Clientes"
"offline" = "🔴 Offline"
"online" = "🟢 Online"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index f9ea047e..060b7334 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -50,6 +50,7 @@
"fail" = "Ошибка"
"comment" = "Комментарий"
"success" = "Успешно"
+"lastOnline" = "Был(а) в сети"
"getVersion" = "Узнать версию"
"install" = "Установка"
"clients" = "Клиенты"
@@ -151,6 +152,8 @@
"getConfigError" = "Произошла ошибка при получении конфигурационного файла"
[pages.inbounds]
+"allTimeTraffic" = "Общий трафик"
+"allTimeTrafficUsage" = "Общее использование за все время"
"title" = "Инбаунды"
"totalDownUp" = "Объем отправленного/полученного трафика"
"totalUsage" = "Всего трафика"
@@ -165,6 +168,8 @@
"details" = "Подробнее"
"transportConfig" = "Транспорт"
"expireDate" = "Дата окончания"
+"createdAt" = "Создано"
+"updatedAt" = "Обновлено"
"resetTraffic" = "Сброс трафика"
"addInbound" = "Создать инбаунд"
"generalActions" = "Общие действия"
@@ -262,6 +267,7 @@
"trafficGetError" = "Ошибка получения данных о трафике"
"getNewX25519CertError" = "Ошибка при получении сертификата X25519."
"getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65."
+"getNewVlessEncError" = "Ошибка при получении сертификата VlessEnc."
[pages.inbounds.stream.general]
"request" = "Запрос"
@@ -574,6 +580,7 @@
"day" = "День"
"days" = "Дней"
"hours" = "Часов"
+"minutes" = "Минуты"
"unknown" = "Неизвестно"
"inbounds" = "Инбаунды"
"clients" = "Клиенты"
diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml
index 803becd8..ac10bf65 100644
--- a/web/translation/translate.tr_TR.toml
+++ b/web/translation/translate.tr_TR.toml
@@ -50,6 +50,7 @@
"fail" = "Başarısız"
"comment" = "Yorum"
"success" = "Başarılı"
+"lastOnline" = "Son çevrimiçi"
"getVersion" = "Sürümü Al"
"install" = "Yükle"
"clients" = "Müşteriler"
@@ -151,6 +152,8 @@
"getConfigError" = "Yapılandırma dosyası alınırken bir hata oluştu"
[pages.inbounds]
+"allTimeTraffic" = "Toplam Trafik"
+"allTimeTrafficUsage" = "Tüm Zamanların Toplam Kullanımı"
"title" = "Gelenler"
"totalDownUp" = "Toplam Gönderilen/Alınan"
"totalUsage" = "Toplam Kullanım"
@@ -165,6 +168,8 @@
"details" = "Detaylar"
"transportConfig" = "Taşıma"
"expireDate" = "Süre"
+"createdAt" = "Oluşturuldu"
+"updatedAt" = "Güncellendi"
"resetTraffic" = "Trafiği Sıfırla"
"addInbound" = "Gelen Ekle"
"generalActions" = "Genel Eylemler"
@@ -262,6 +267,7 @@
"trafficGetError" = "Trafik bilgisi alınırken hata oluştu"
"getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu."
"getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu."
+"getNewVlessEncError" = "VlessEnc sertifikası alınırken hata oluştu."
[pages.inbounds.stream.general]
"request" = "İstek"
@@ -561,22 +567,23 @@
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
[tgbot]
-"keyboardClosed" = "❌ Özel klavye kapalı!"
+"keyboardClosed" = "❌ Klavye kapatıldı!"
"noResult" = "❗ Sonuç yok!"
"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!"
"wentWrong" = "❌ Bir şeyler yanlış gitti!"
-"noIpRecord" = "❗ IP Kaydı yok!"
-"noInbounds" = "❗ Gelen bulunamadı!"
-"unlimited" = "♾ Sınırsız(Sıfırla)"
+"noIpRecord" = "❗ IP Kaydı Yok!"
+"noInbounds" = "❗ Gelen bağlantı bulunamadı!"
+"unlimited" = "♾ Sınırsız (Sıfırla)"
"add" = "Ekle"
"month" = "Ay"
"months" = "Aylar"
"day" = "Gün"
"days" = "Günler"
"hours" = "Saatler"
-"unknown" = "Bilinmiyor"
+"minutes" = "Dakika"
+"unknown" = "Bilinmeyen"
"inbounds" = "Gelenler"
-"clients" = "Müşteriler"
+"clients" = "İstemciler"
"offline" = "🔴 Çevrimdışı"
"online" = "🟢 Çevrimiçi"
diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml
index d181744e..4005f14f 100644
--- a/web/translation/translate.uk_UA.toml
+++ b/web/translation/translate.uk_UA.toml
@@ -50,6 +50,7 @@
"fail" = "Помилка"
"comment" = "Коментар"
"success" = "Успішно"
+"lastOnline" = "Був(ла) онлайн"
"getVersion" = "Отримати версію"
"install" = "Встановити"
"clients" = "Клієнти"
@@ -151,6 +152,8 @@
"getConfigError" = "Виникла помилка під час отримання файлу конфігурації"
[pages.inbounds]
+"allTimeTraffic" = "Загальний трафік"
+"allTimeTrafficUsage" = "Загальне використання за весь час"
"title" = "Вхідні"
"totalDownUp" = "Всього надісланих/отриманих"
"totalUsage" = "Всього використанно"
@@ -165,6 +168,8 @@
"details" = "Деталі"
"transportConfig" = "Транспорт"
"expireDate" = "Тривалість"
+"createdAt" = "Створено"
+"updatedAt" = "Оновлено"
"resetTraffic" = "Скинути трафік"
"addInbound" = "Додати вхідний"
"generalActions" = "Загальні дії"
@@ -262,6 +267,7 @@
"trafficGetError" = "Помилка отримання даних про трафік"
"getNewX25519CertError" = "Помилка при отриманні сертифіката X25519."
"getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65."
+"getNewVlessEncError" = "Помилка при отриманні сертифіката VlessEnc."
[pages.inbounds.stream.general]
"request" = "Запит"
@@ -561,19 +567,20 @@
"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку"
[tgbot]
-"keyboardClosed" = "❌ Спеціальна клавіатура закрита!"
+"keyboardClosed" = "❌ Клавіатуру закрито!"
"noResult" = "❗ Немає результату!"
-"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!"
+"noQuery" = "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!"
"wentWrong" = "❌ Щось пішло не так!"
-"noIpRecord" = "❗ Немає IP-запису!"
-"noInbounds" = "❗ Вхідних не знайдено!"
-"unlimited" = "♾ Необмежений (скинути)"
+"noIpRecord" = "❗ Немає запису IP!"
+"noInbounds" = "❗ Вхідні не знайдені!"
+"unlimited" = "♾ Необмежено (Скинути)"
"add" = "Додати"
"month" = "Місяць"
"months" = "Місяці"
"day" = "День"
"days" = "Дні"
-"hours" = "Годинник"
+"hours" = "Години"
+"minutes" = "Хвилини"
"unknown" = "Невідомо"
"inbounds" = "Вхідні"
"clients" = "Клієнти"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 0283ae00..66ed38ce 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -50,6 +50,7 @@
"fail" = "Thất bại"
"comment" = "Bình luận"
"success" = "Thành công"
+"lastOnline" = "Lần online gần nhất"
"getVersion" = "Lấy phiên bản"
"install" = "Cài đặt"
"clients" = "Các khách hàng"
@@ -91,7 +92,7 @@
"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ."
"emptyUsername" = "Vui lòng nhập tên người dùng."
"emptyPassword" = "Vui lòng nhập mật khẩu."
-"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
+"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ."
"successLogin" = "Bạn đã đăng nhập vào tài khoản thành công."
[pages.index]
@@ -151,6 +152,8 @@
"getConfigError" = "Lỗi xảy ra khi truy xuất tệp cấu hình"
[pages.inbounds]
+"allTimeTraffic" = "Tổng Lưu Lượng"
+"allTimeTrafficUsage" = "Tổng mức sử dụng mọi lúc"
"title" = "Điểm vào (Inbounds)"
"totalDownUp" = "Tổng tải lên/tải xuống"
"totalUsage" = "Tổng sử dụng"
@@ -165,6 +168,8 @@
"details" = "Chi tiết"
"transportConfig" = "Giao vận"
"expireDate" = "Ngày hết hạn"
+"createdAt" = "Tạo lúc"
+"updatedAt" = "Cập nhật"
"resetTraffic" = "Đặt lại lưu lượng"
"addInbound" = "Thêm điểm vào"
"generalActions" = "Hành động chung"
@@ -261,7 +266,8 @@
"resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng"
"trafficGetError" = "Lỗi khi lấy thông tin lưu lượng"
"getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519."
-"getNewmldsa65Error" = "Lỗi khi lấy chúng tôi mldsa65."
+"getNewmldsa65Error" = "Lỗi khi lấy chứng chỉ mldsa65."
+"getNewVlessEncError" = "Lỗi khi lấy chứng chỉ VlessEnc."
[pages.inbounds.stream.general]
"request" = "Lời yêu cầu"
@@ -535,9 +541,9 @@
[pages.settings.security]
"admin" = "Thông tin đăng nhập quản trị viên"
-"twoFactor" = "Xác thực hai yếu tố"
-"twoFactorEnable" = "Bật 2FA"
-"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
+"twoFactor" = "Xác thực hai yếu tố"
+"twoFactorEnable" = "Bật 2FA"
+"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn."
"twoFactorModalSetTitle" = "Bật xác thực hai yếu tố"
"twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố"
"twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:"
@@ -561,22 +567,23 @@
"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi"
[tgbot]
-"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!"
+"keyboardClosed" = "❌ Bàn phím đã đóng!"
"noResult" = "❗ Không có kết quả!"
-"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!"
+"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!"
"wentWrong" = "❌ Đã xảy ra lỗi!"
"noIpRecord" = "❗ Không có bản ghi IP!"
"noInbounds" = "❗ Không tìm thấy inbound!"
-"unlimited" = "♾ Không giới hạn"
+"unlimited" = "♾ Không giới hạn (Đặt lại)"
"add" = "Thêm"
"month" = "Tháng"
"months" = "Tháng"
"day" = "Ngày"
"days" = "Ngày"
"hours" = "Giờ"
-"unknown" = "Không rõ"
-"inbounds" = "Vào"
-"clients" = "Các người dùng"
+"minutes" = "Phút"
+"unknown" = "Không xác định"
+"inbounds" = "Inbound"
+"clients" = "Client"
"offline" = "🔴 Ngoại tuyến"
"online" = "🟢 Trực tuyến"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml
index ba93e980..5875add9 100644
--- a/web/translation/translate.zh_CN.toml
+++ b/web/translation/translate.zh_CN.toml
@@ -50,6 +50,7 @@
"fail" = "失败"
"comment" = "评论"
"success" = "成功"
+"lastOnline" = "上次在线"
"getVersion" = "获取版本"
"install" = "安装"
"clients" = "客户端"
@@ -151,6 +152,8 @@
"getConfigError" = "检索配置文件时出错"
[pages.inbounds]
+"allTimeTraffic" = "累计总流量"
+"allTimeTrafficUsage" = "所有时间总使用量"
"title" = "入站列表"
"totalDownUp" = "总上传 / 下载"
"totalUsage" = "总用量"
@@ -165,6 +168,8 @@
"details" = "详细信息"
"transportConfig" = "传输配置"
"expireDate" = "到期时间"
+"createdAt" = "创建时间"
+"updatedAt" = "更新时间"
"resetTraffic" = "重置流量"
"addInbound" = "添加入站"
"generalActions" = "通用操作"
@@ -262,6 +267,7 @@
"trafficGetError" = "获取流量数据时出错"
"getNewX25519CertError" = "获取X25519证书时出错。"
"getNewmldsa65Error" = "获取mldsa65证书时出错。"
+"getNewVlessEncError" = "获取VlessEnc证书时出错。"
[pages.inbounds.stream.general]
"request" = "请求"
@@ -563,19 +569,20 @@
[tgbot]
"keyboardClosed" = "❌ 自定义键盘已关闭!"
"noResult" = "❗ 没有结果!"
-"noQuery" = "❌ 未找到查询!请重新使用命令!"
+"noQuery" = "❌ 未找到查询!请再次使用该命令!"
"wentWrong" = "❌ 出了点问题!"
-"noIpRecord" = "❗ 没有 IP 记录!"
-"noInbounds" = "❗ 没有找到入站连接!"
-"unlimited" = "♾ 无限制"
+"noIpRecord" = "❗ 没有IP记录!"
+"noInbounds" = "❗ 未找到入站!"
+"unlimited" = "♾ 无限(重置)"
"add" = "添加"
"month" = "月"
"months" = "月"
"day" = "天"
"days" = "天"
"hours" = "小时"
+"minutes" = "分钟"
"unknown" = "未知"
-"inbounds" = "入站连接"
+"inbounds" = "入站"
"clients" = "客户端"
"offline" = "🔴 离线"
"online" = "🟢 在线"
diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml
index eef39ab6..6843e9ee 100644
--- a/web/translation/translate.zh_TW.toml
+++ b/web/translation/translate.zh_TW.toml
@@ -50,6 +50,7 @@
"fail" = "失敗"
"comment" = "評論"
"success" = "成功"
+"lastOnline" = "上次上線"
"getVersion" = "獲取版本"
"install" = "安裝"
"clients" = "客戶端"
@@ -151,6 +152,8 @@
"getConfigError" = "檢索設定檔時發生錯誤"
[pages.inbounds]
+"allTimeTraffic" = "累計總流量"
+"allTimeTrafficUsage" = "所有时间总使用量"
"title" = "入站列表"
"totalDownUp" = "總上傳 / 下載"
"totalUsage" = "總用量"
@@ -165,6 +168,8 @@
"details" = "詳細資訊"
"transportConfig" = "傳輸配置"
"expireDate" = "到期時間"
+"createdAt" = "建立時間"
+"updatedAt" = "更新時間"
"resetTraffic" = "重置流量"
"addInbound" = "新增入站"
"generalActions" = "通用操作"
@@ -262,6 +267,7 @@
"trafficGetError" = "取得流量資料時發生錯誤"
"getNewX25519CertError" = "取得X25519憑證時發生錯誤。"
"getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。"
+"getNewVlessEncError" = "取得VlessEnc憑證時發生錯誤。"
[pages.inbounds.stream.general]
"request" = "請求"
@@ -563,22 +569,23 @@
[tgbot]
"keyboardClosed" = "❌ 自定義鍵盤已關閉!"
"noResult" = "❗ 沒有結果!"
-"noQuery" = "❌ 未找到查詢!請重新使用命令!"
+"noQuery" = "❌ 未找到查詢!請再次使用該命令!"
"wentWrong" = "❌ 出了點問題!"
-"noIpRecord" = "❗ 沒有 IP 記錄!"
-"noInbounds" = "❗ 沒有找到入站連線!"
-"unlimited" = "♾ 無限制"
-"add" = "新增"
+"noIpRecord" = "❗ 沒有IP記錄!"
+"noInbounds" = "❗ 未找到入站!"
+"unlimited" = "♾ 無限(重置)"
+"add" = "添加"
"month" = "月"
"months" = "月"
"day" = "天"
"days" = "天"
"hours" = "小時"
+"minutes" = "分鐘"
"unknown" = "未知"
-"inbounds" = "入站連線"
+"inbounds" = "入站"
"clients" = "客戶端"
"offline" = "🔴 離線"
-"online" = "🟢 線上"
+"online" = "🟢 在線"
[tgbot.commands]
"unknown" = "❗ 未知命令"
diff --git a/x-ui.service b/x-ui.service
index 29d2a63a..e911ef56 100644
--- a/x-ui.service
+++ b/x-ui.service
@@ -12,4 +12,4 @@ Restart=on-failure
RestartSec=5s
[Install]
-WantedBy=multi-user.target
\ No newline at end of file
+WantedBy=multi-user.target
diff --git a/x-ui.sh b/x-ui.sh
index 97dc9102..cec86ba0 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -398,37 +398,6 @@ show_log() {
esac
}
-show_banlog() {
- local system_log="/var/log/fail2ban.log"
-
- echo -e "${green}Checking ban logs...${plain}\n"
-
- if ! systemctl is-active --quiet fail2ban; then
- echo -e "${red}Fail2ban service is not running!${plain}\n"
- return 1
- fi
-
- if [[ -f "$system_log" ]]; then
- echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
- grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
- echo ""
- fi
-
- if [[ -f "${iplimit_banned_log_path}" ]]; then
- echo -e "${green}3X-IPL ban log entries:${plain}"
- if [[ -s "${iplimit_banned_log_path}" ]]; then
- grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
- else
- echo -e "${yellow}Ban log file is empty${plain}"
- fi
- else
- echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
- fi
-
- echo -e "\n${green}Current jail status:${plain}"
- fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
-}
-
bbr_menu() {
echo -e "${green}\t1.${plain} Enable BBR"
echo -e "${green}\t2.${plain} Disable BBR"
@@ -1005,7 +974,7 @@ ssl_cert_issue() {
# install socat second
case "${release}" in
ubuntu | debian | armbian)
- apt update && apt install socat -y
+ apt-get update && apt-get install socat -y
;;
centos | rhel | almalinux | rocky | ol)
yum -y update && yum -y install socat
@@ -1330,81 +1299,7 @@ run_speedtest() {
speedtest
}
-create_iplimit_jails() {
- # Use default bantime if not passed => 30 minutes
- local bantime="${1:-30}"
- # Uncomment 'allowipv6 = auto' in fail2ban.conf
- sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
-
- # On Debian 12+ fail2ban's default backend should be changed to systemd
- if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
- sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
- fi
-
- cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
-[3x-ipl]
-enabled=true
-backend=auto
-filter=3x-ipl
-action=3x-ipl
-logpath=${iplimit_log_path}
-maxretry=2
-findtime=32
-bantime=${bantime}m
-EOF
-
- cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
-[Definition]
-datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
-failregex = \[LIMIT_IP\]\s*Email\s*=\s*.+\s*\|\|\s*SRC\s*=\s*
-ignoreregex =
-EOF
-
- cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
-[INCLUDES]
-before = iptables-allports.conf
-
-[Definition]
-actionstart = -N f2b-
- -A f2b- -j
- -I -p -j f2b-
-
-actionstop = -D -p -j f2b-
-
- -X f2b-
-
-actioncheck = -n -L | grep -q 'f2b-[ \t]'
-
-actionban = -I f2b- 1 -s -j
- echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> ${iplimit_banned_log_path}
-
-actionunban = -D f2b- -s -j
- echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> ${iplimit_banned_log_path}
-
-[Init]
-name = default
-protocol = tcp
-chain = INPUT
-EOF
-
- echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
-}
-
-iplimit_remove_conflicts() {
- local jail_files=(
- /etc/fail2ban/jail.conf
- /etc/fail2ban/jail.local
- )
-
- for file in "${jail_files[@]}"; do
- # Check for [3x-ipl] config in jail file then remove it
- if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
- sed -i "/\[3x-ipl\]/,/^$/d" ${file}
- echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
- fi
- done
-}
ip_validation() {
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
@@ -1514,14 +1409,22 @@ install_iplimit() {
# Check the OS and install necessary packages
case "${release}" in
ubuntu)
+ apt-get update
if [[ "${os_version}" -ge 24 ]]; then
- apt update && apt install python3-pip -y
+ apt-get install python3-pip -y
python3 -m pip install pyasynchat --break-system-packages
fi
- apt update && apt install fail2ban -y
+ apt-get install fail2ban -y
;;
- debian | armbian)
- apt update && apt install fail2ban -y
+ debian)
+ apt-get update
+ if [ "$os_version" -ge 12 ]; then
+ apt-get install -y python3-systemd
+ fi
+ apt-get install -y fail2ban
+ ;;
+ armbian)
+ apt-get update && apt-get install fail2ban -y
;;
centos | rhel | almalinux | rocky | ol)
yum update -y && yum install epel-release -y
@@ -1632,11 +1535,129 @@ remove_iplimit() {
esac
}
-SSH_port_forwarding() {
- local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
- if [ -z "$server_ip" ]; then
- server_ip=$(curl -s --max-time 3 https://4.ident.me)
+show_banlog() {
+ local system_log="/var/log/fail2ban.log"
+
+ echo -e "${green}Checking ban logs...${plain}\n"
+
+ if ! systemctl is-active --quiet fail2ban; then
+ echo -e "${red}Fail2ban service is not running!${plain}\n"
+ return 1
fi
+
+ if [[ -f "$system_log" ]]; then
+ echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
+ grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
+ echo ""
+ fi
+
+ if [[ -f "${iplimit_banned_log_path}" ]]; then
+ echo -e "${green}3X-IPL ban log entries:${plain}"
+ if [[ -s "${iplimit_banned_log_path}" ]]; then
+ grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
+ else
+ echo -e "${yellow}Ban log file is empty${plain}"
+ fi
+ else
+ echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
+ fi
+
+ echo -e "\n${green}Current jail status:${plain}"
+ fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
+}
+
+create_iplimit_jails() {
+ # Use default bantime if not passed => 30 minutes
+ local bantime="${1:-30}"
+
+ # Uncomment 'allowipv6 = auto' in fail2ban.conf
+ sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
+
+ # On Debian 12+ fail2ban's default backend should be changed to systemd
+ if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
+ sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
+ fi
+
+ cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
+[3x-ipl]
+enabled=true
+backend=auto
+filter=3x-ipl
+action=3x-ipl
+logpath=${iplimit_log_path}
+maxretry=2
+findtime=32
+bantime=${bantime}m
+EOF
+
+ cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
+[Definition]
+datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
+failregex = \[LIMIT_IP\]\s*Email\s*=\s*.+\s*\|\|\s*SRC\s*=\s*
+ignoreregex =
+EOF
+
+ cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
+[INCLUDES]
+before = iptables-allports.conf
+
+[Definition]
+actionstart = -N f2b-
+ -A f2b- -j
+ -I -p -j f2b-
+
+actionstop = -D -p -j f2b-
+
+ -X f2b-
+
+actioncheck = -n -L | grep -q 'f2b-[ \t]'
+
+actionban = -I f2b- 1 -s -j
+ echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = [IP] = banned for seconds." >> ${iplimit_banned_log_path}
+
+actionunban = -D f2b- -s -j
+ echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = [IP] = unbanned." >> ${iplimit_banned_log_path}
+
+[Init]
+name = default
+protocol = tcp
+chain = INPUT
+EOF
+
+ echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
+}
+
+iplimit_remove_conflicts() {
+ local jail_files=(
+ /etc/fail2ban/jail.conf
+ /etc/fail2ban/jail.local
+ )
+
+ for file in "${jail_files[@]}"; do
+ # Check for [3x-ipl] config in jail file then remove it
+ if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
+ sed -i "/\[3x-ipl\]/,/^$/d" ${file}
+ echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
+ fi
+ done
+}
+
+SSH_port_forwarding() {
+ local URL_lists=(
+ "https://api4.ipify.org"
+ "https://ipv4.icanhazip.com"
+ "https://v4.api.ipinfo.io/ip"
+ "https://ipv4.myexternalip.com/raw"
+ "https://4.ident.me"
+ "https://check-host.net/ip"
+ )
+ local server_ip=""
+ for ip_address in "${URL_lists[@]}"; do
+ server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
+ if [[ -n "${server_ip}" ]]; then
+ break
+ fi
+ done
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
diff --git a/xray/client_traffic.go b/xray/client_traffic.go
index 0f2389a0..fe527d55 100644
--- a/xray/client_traffic.go
+++ b/xray/client_traffic.go
@@ -7,7 +7,9 @@ type ClientTraffic struct {
Email string `json:"email" form:"email" gorm:"unique"`
Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"`
+ AllTime int64 `json:"allTime" form:"allTime"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
Total int64 `json:"total" form:"total"`
Reset int `json:"reset" form:"reset" gorm:"default:0"`
+ LastOnline int64 `json:"lastOnline" form:"lastOnline" gorm:"default:0"`
}
diff --git a/xray/inbound.go b/xray/inbound.go
index c74b07ba..ea11449d 100644
--- a/xray/inbound.go
+++ b/xray/inbound.go
@@ -14,7 +14,6 @@ type InboundConfig struct {
StreamSettings json_util.RawMessage `json:"streamSettings"`
Tag string `json:"tag"`
Sniffing json_util.RawMessage `json:"sniffing"`
- Allocate json_util.RawMessage `json:"allocate"`
}
func (c *InboundConfig) Equals(other *InboundConfig) bool {
@@ -39,8 +38,5 @@ func (c *InboundConfig) Equals(other *InboundConfig) bool {
if !bytes.Equal(c.Sniffing, other.Sniffing) {
return false
}
- if !bytes.Equal(c.Allocate, other.Allocate) {
- return false
- }
return true
}
|