diff --git a/web/controller/index.go b/web/controller/index.go index bc3c420..fc5480c 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -65,13 +65,13 @@ func (a *IndexController) login(c *gin.Context) { user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret) timeStr := time.Now().Format("2006-01-02 15:04:05") if user == nil { - logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) - a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) + logger.Warningf("wrong username or password or secret: \"%s\" \"%s\" \"%s\"", form.Username, form.Password, form.LoginSecret) + a.tgbot.UserLoginNotify(form.Username, form.Password, getRemoteIp(c), timeStr, 0) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) return } else { - logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c)) - a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1) + logger.Infof("%s Successful Login, Ip Address: %s\n", form.Username, getRemoteIp(c)) + a.tgbot.UserLoginNotify(form.Username, ``, getRemoteIp(c), timeStr, 1) } sessionMaxAge, err := a.settingService.GetSessionMaxAge() @@ -87,14 +87,14 @@ func (a *IndexController) login(c *gin.Context) { } err = session.SetLoginUser(c, user) - logger.Info("user", user.Id, "login success") + logger.Info("user ", user.Id, " login success") jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err) } func (a *IndexController) logout(c *gin.Context) { user := session.GetLoginUser(c) if user != nil { - logger.Info("user", user.Id, "logout") + logger.Info("user ", user.Id, " logout") } session.ClearSession(c) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) diff --git a/web/service/inbound.go b/web/service/inbound.go index 63816f7..b7bdd33 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -595,7 +595,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin return false, err } - inerfaceClients := settings["clients"].([]interface{}) + interfaceClients := settings["clients"].([]interface{}) oldInbound, err := s.GetInbound(data.Id) if err != nil { @@ -650,7 +650,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin return false, err } settingsClients := oldSettings["clients"].([]interface{}) - settingsClients[clientIndex] = inerfaceClients[0] + settingsClients[clientIndex] = interfaceClients[0] oldSettings["clients"] = settingsClients newSettings, err := json.MarshalIndent(oldSettings, "", " ") @@ -1134,7 +1134,6 @@ func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error { } func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error { - logger.Warning(email) return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error } diff --git a/web/service/tgbot.go b/web/service/tgbot.go index ec1e1b5..ddf0039 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -1019,7 +1019,7 @@ func (t *Tgbot) prepareServerUsageInfo() string { return info } -func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) { +func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) { if !t.IsRunning() { return } @@ -1037,11 +1037,12 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status msg := "" if status == LoginSuccess { msg += t.I18nBot("tgbot.messages.loginSuccess") + msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) } else if status == LoginFail { msg += t.I18nBot("tgbot.messages.loginFailed") + msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) + msg += t.I18nBot("tgbot.messages.password", "Password=="+password) } - - msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) msg += t.I18nBot("tgbot.messages.username", "Username=="+username) msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip) msg += t.I18nBot("tgbot.messages.time", "Time=="+time) diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 9c1e34e..5a10174 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -79,7 +79,7 @@ "invalidFormData" = "The Input data format is invalid." "emptyUsername" = "Username is required" "emptyPassword" = "Password is required" -"wrongUsernameOrPassword" = "Invalid username or password." +"wrongUsernameOrPassword" = "Invalid username or password or secret." "successLogin" = "Login" [pages.index] @@ -548,7 +548,7 @@ "selectUserFailed" = "❌ Error in user selection!" "userSaved" = "✅ Telegram User saved." "loginSuccess" = "✅ Logged in to the panel successfully.\r\n" -"loginFailed" = "❗️ Log in to the panel failed.\r\n" +"loginFailed" = "❗️Login attempt to the panel failed.\r\n" "report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n" "datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n" @@ -566,6 +566,7 @@ "traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" "username" = "👤 Username: {{ .Username }}\r\n" +"password" = "👤 Password: {{ .Password }}\r\n" "time" = "⏰ Time: {{ .Time }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n" "port" = "🔌 Port: {{ .Port }}\r\n" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 835f435..0c9c544 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -560,6 +560,7 @@ "traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Estado de Xray: {{ .State }}\r\n" "username" = "👤 Nombre de usuario: {{ .Username }}\r\n" +"password" = "👤 Contraseña: {{ .Password }}\r\n" "time" = "⏰ Hora: {{ .Time }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n" "port" = "🔌 Puerto: {{ .Port }}\r\n" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index ad7f2b2..79635e3 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -562,6 +562,7 @@ "traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ وضعیت‌ایکس‌ری: {{ .State }}\r\n" "username" = "👤 نام‌کاربری: {{ .Username }}\r\n" +"password" = "👤 رمز عبور: {{ .Password }}\r\n" "time" = "⏰ زمان: {{ .Time }}\r\n" "inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n" "port" = "🔌 پورت: {{ .Port }}\r\n" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index 3ec374f..c34a49f 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -562,6 +562,7 @@ "traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" "username" = "👤 Nama Pengguna: {{ .Username }}\r\n" +"password" = "👤 Kata Sandi: {{ .Password }}\r\n" "time" = "⏰ Waktu: {{ .Time }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n" "port" = "🔌 Port: {{ .Port }}\r\n" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 7cd3469..bd2d396 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -562,6 +562,7 @@ "traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Состояние Xray: {{ .State }}\r\n" "username" = "👤 Имя пользователя: {{ .Username }}\r\n" +"password" = "👤 Пароль: {{ .Password }}\r\n" "time" = "⏰ Время: {{ .Time }}\r\n" "inbound" = "📍 Входящий поток: {{ .Remark }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index 9fd4d42..6282751 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -562,6 +562,7 @@ "traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Статус: {{ .State }}\r\n" "username" = "👤 Ім'я користувача: {{ .Username }}\r\n" +"password" = "👤 Пароль: {{ .Password }}\r\n" "time" = "⏰ Час: {{ .Time }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index d8d73c6..e7da632 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -562,6 +562,7 @@ "traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Trạng thái Xray: {{ .State }}\r\n" "username" = "👤 Tên người dùng: {{ .Username }}\r\n" +"password" = "👤 Mật khẩu: {{ .Password }}\r\n" "time" = "⏰ Thời gian: {{ .Time }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n" "port" = "🔌 Cổng: {{ .Port }}\r\n" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 9148f51..6501713 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -580,6 +580,7 @@ "traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "xrayStatus" = "ℹ️ Xray 状态:{{ .State }}\r\n" "username" = "👤 用户名:{{ .Username }}\r\n" +"password" = "👤 密码: {{ .Password }}\r\n" "time" = "⏰ 时间:{{ .Time }}\r\n" "inbound" = "📍 入站:{{ .Remark }}\r\n" "port" = "🔌 端口:{{ .Port }}\r\n" diff --git a/xray/api.go b/xray/api.go index ac384ed..2dce4dc 100644 --- a/xray/api.go +++ b/xray/api.go @@ -31,24 +31,27 @@ type XrayAPI struct { isConnected bool } -func (x *XrayAPI) Init(apiPort int) (err error) { - if apiPort == 0 { - return common.NewError("xray api port wrong:", apiPort) +func (x *XrayAPI) Init(apiPort int) error { + if apiPort <= 0 { + return fmt.Errorf("invalid Xray API port: %d", apiPort) } - conn, err := grpc.NewClient(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials())) + + addr := fmt.Sprintf("127.0.0.1:%d", apiPort) + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - return err + return fmt.Errorf("failed to connect to Xray API: %w", err) } + x.grpcClient = conn x.isConnected = true - hsClient := command.NewHandlerServiceClient(x.grpcClient) - ssClient := statsService.NewStatsServiceClient(x.grpcClient) + hsClient := command.NewHandlerServiceClient(conn) + ssClient := statsService.NewStatsServiceClient(conn) x.HandlerServiceClient = &hsClient x.StatsServiceClient = &ssClient - return + return nil } func (x *XrayAPI) Close() { @@ -149,94 +152,101 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in return err } -func (x *XrayAPI) RemoveUser(inboundTag string, email string) error { - client := *x.HandlerServiceClient - _, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{ - Tag: inboundTag, - Operation: serial.ToTypedMessage(&command.RemoveUserOperation{ - Email: email, - }), - }) - return err +func (x *XrayAPI) RemoveUser(inboundTag, email string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + op := &command.RemoveUserOperation{Email: email} + req := &command.AlterInboundRequest{ + Tag: inboundTag, + Operation: serial.ToTypedMessage(op), + } + + _, err := (*x.HandlerServiceClient).AlterInbound(ctx, req) + if err != nil { + return fmt.Errorf("failed to remove user: %w", err) + } + + return nil } func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) { if x.grpcClient == nil { return nil, nil, common.NewError("xray api is not initialized") } - trafficRegex := regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)") - ClientTrafficRegex := regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)") - client := *x.StatsServiceClient + trafficRegex := regexp.MustCompile(`(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)`) + clientTrafficRegex := regexp.MustCompile(`user>>>([^>]+)>>>traffic>>>(downlink|uplink)`) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - request := &statsService.QueryStatsRequest{ - Reset_: reset, - } - resp, err := client.QueryStats(ctx, request) + + resp, err := (*x.StatsServiceClient).QueryStats(ctx, &statsService.QueryStatsRequest{Reset_: reset}) if err != nil { return nil, nil, err } - tagTrafficMap := map[string]*Traffic{} - emailTrafficMap := map[string]*ClientTraffic{} - clientTraffics := make([]*ClientTraffic, 0) - traffics := make([]*Traffic, 0) + tagTrafficMap := make(map[string]*Traffic) + emailTrafficMap := make(map[string]*ClientTraffic) + for _, stat := range resp.GetStat() { - matchs := trafficRegex.FindStringSubmatch(stat.Name) - if len(matchs) < 3 { - - matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name) - if len(matchs) < 3 { - continue - } else { - - isUser := matchs[1] == "user" - email := matchs[2] - isDown := matchs[3] == "downlink" - if !isUser { - continue - } - traffic, ok := emailTrafficMap[email] - if !ok { - traffic = &ClientTraffic{ - Email: email, - } - emailTrafficMap[email] = traffic - clientTraffics = append(clientTraffics, traffic) - } - if isDown { - traffic.Down = stat.Value - } else { - traffic.Up = stat.Value - } - - } - continue - } - isInbound := matchs[1] == "inbound" - isOutbound := matchs[1] == "outbound" - tag := matchs[2] - isDown := matchs[3] == "downlink" - if tag == "api" { - continue - } - traffic, ok := tagTrafficMap[tag] - if !ok { - traffic = &Traffic{ - IsInbound: isInbound, - IsOutbound: isOutbound, - Tag: tag, - } - tagTrafficMap[tag] = traffic - traffics = append(traffics, traffic) - } - if isDown { - traffic.Down = stat.Value - } else { - traffic.Up = stat.Value + if matches := trafficRegex.FindStringSubmatch(stat.Name); len(matches) == 4 { + processTraffic(matches, stat.Value, tagTrafficMap) + } else if matches := clientTrafficRegex.FindStringSubmatch(stat.Name); len(matches) == 3 { + processClientTraffic(matches, stat.Value, emailTrafficMap) } } - return traffics, clientTraffics, nil + return mapToSlice(tagTrafficMap), mapToSlice(emailTrafficMap), nil +} + +func processTraffic(matches []string, value int64, trafficMap map[string]*Traffic) { + isInbound := matches[1] == "inbound" + tag := matches[2] + isDown := matches[3] == "downlink" + + if tag == "api" { + return + } + + traffic, ok := trafficMap[tag] + if !ok { + traffic = &Traffic{ + IsInbound: isInbound, + IsOutbound: !isInbound, + Tag: tag, + } + trafficMap[tag] = traffic + } + + if isDown { + traffic.Down = value + } else { + traffic.Up = value + } +} + +func processClientTraffic(matches []string, value int64, clientTrafficMap map[string]*ClientTraffic) { + email := matches[1] + isDown := matches[2] == "downlink" + + traffic, ok := clientTrafficMap[email] + if !ok { + traffic = &ClientTraffic{Email: email} + clientTrafficMap[email] = traffic + } + + if isDown { + traffic.Down = value + } else { + traffic.Up = value + } +} + +func mapToSlice[T any](m map[string]*T) []*T { + result := make([]*T, 0, len(m)) + for _, v := range m { + result = append(result, v) + } + return result }