diff --git a/web/controller/inbound.go b/web/controller/inbound.go index c22ce192..d572cc40 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -12,6 +12,10 @@ import ( "github.com/gin-gonic/gin" ) +type ClientOnlineIPsResponse struct { + Count int `json:"count"` +} + type InboundController struct { inboundService service.InboundService xrayService service.XrayService @@ -30,6 +34,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/add", a.addInbound) g.POST("/del/:id", a.delInbound) g.POST("/update/:id", a.updateInbound) + g.POST("/clientOnlineIps/:email", a.getClientOnlineIPs) g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/addClient", a.addInboundClient) @@ -146,6 +151,21 @@ func (a *InboundController) updateInbound(c *gin.Context) { } } +func (a *InboundController) getClientOnlineIPs(c *gin.Context) { + email := c.Param("email") + + count, err := a.inboundService.GetClientOnlineIPs(email) + res := &ClientOnlineIPsResponse{ + Count: count, + } + if err != nil { + jsonObj(c, res, err) + return + } + + jsonObj(c, res, nil) +} + func (a *InboundController) getClientIps(c *gin.Context) { email := c.Param("email") diff --git a/web/controller/util.go b/web/controller/util.go index 440de276..04084f41 100644 --- a/web/controller/util.go +++ b/web/controller/util.go @@ -46,8 +46,13 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) { } } else { m.Success = false - m.Msg = msg + " " + I18nWeb(c, "fail") + ": " + err.Error() - logger.Warning(msg+" "+I18nWeb(c, "fail")+": ", err) + if msg != "" { + m.Msg = msg + " " + I18nWeb(c, "fail") + ": " + err.Error() + logger.Warning(msg+" "+I18nWeb(c, "fail")+": ", err) + } else { + m.Msg = I18nWeb(c, "fail") + ": " + err.Error() + logger.Warning(I18nWeb(c, "fail")+": ", err) + } } c.JSON(http.StatusOK, m) } diff --git a/web/service/inbound.go b/web/service/inbound.go index 4f28af21..71c0f132 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1855,6 +1855,18 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client return traffic, nil } +func (s *InboundService) GetClientOnlineIPs(email string) (int, error) { + s.xrayApi.Init(p.GetAPIPort()) + defer s.xrayApi.Close() + + count, err := s.xrayApi.GetClientOnlineIPs(email) + if err != nil { + logger.Debug("Failed to fetch Xray Client Online IPs:", err) + return 0, err + } + return count, nil +} + func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) { db := database.GetDB() InboundClientIps := &model.InboundClientIps{} diff --git a/web/service/server.go b/web/service/server.go index 21ab66f5..4c9731f5 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -61,6 +61,7 @@ type Status struct { State ProcessState `json:"state"` ErrorMsg string `json:"errorMsg"` Version string `json:"version"` + ApiPort string `json:"apiPort"` } `json:"xray"` Uptime uint64 `json:"uptime"` Loads []float64 `json:"loads"` @@ -239,6 +240,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status { status.Xray.ErrorMsg = s.xrayService.GetXrayResult() } status.Xray.Version = s.xrayService.GetXrayVersion() + status.Xray.ApiPort = s.xrayService.GetXrayApiPort() var rtm runtime.MemStats runtime.ReadMemStats(&rtm) diff --git a/web/service/xray.go b/web/service/xray.go index d37c963a..5998b5f0 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "sync" + "strconv" "x-ui/logger" "x-ui/xray" @@ -56,6 +57,13 @@ func (s *XrayService) GetXrayVersion() string { return p.GetVersion() } +func (s *XrayService) GetXrayApiPort() string { + if p == nil { + return "Unknown" + } + return strconv.Itoa(p.GetAPIPort()) +} + func RemoveIndex(s []interface{}, index int) []interface{} { return append(s[:index], s[index+1:]...) } diff --git a/xray/api.go b/xray/api.go index 727ab526..988e3b44 100644 --- a/xray/api.go +++ b/xray/api.go @@ -204,6 +204,35 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) { return mapToSlice(tagTrafficMap), mapToSlice(emailTrafficMap), nil } +func (x *XrayAPI) GetClientOnlineIPs(email string) (int, error) { + if x.grpcClient == nil { + return 0, common.NewError("xray api is not initialized") + } + + statName := "user>>>" + email + ">>>online" + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + if x.StatsServiceClient == nil { + return 0, common.NewError("xray StatusServiceClient is not initialized") + } + + r := &statsService.GetStatsRequest{ + Name: statName, + Reset_: false, + } + resp, err := (*x.StatsServiceClient).GetStatsOnline(ctx, r) + if err != nil { + logger.Debug("Failed to query Xray statsonline:", err) + return 0, err + } + + count := resp.GetStat().Value + + return int(count), nil +} + func processTraffic(matches []string, value int64, trafficMap map[string]*Traffic) { isInbound := matches[1] == "inbound" tag := matches[2]