mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 13:10:26 +08:00 
			
		
		
		
	对服务增加基础的数据统计
This commit is contained in:
		@@ -14,3 +14,6 @@
 | 
			
		||||
 | 
			
		||||
## 联系我们
 | 
			
		||||
有什么问题和建议都可以加入QQ群 `659832182`。
 | 
			
		||||
 | 
			
		||||
## 感谢
 | 
			
		||||
* 感谢[JetBrains公司](https://www.jetbrains.com/)提供免费的IDE开发Licence。
 | 
			
		||||
@@ -91,6 +91,30 @@ func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
 | 
			
		||||
	return pb.NewServerServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
 | 
			
		||||
	return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerClientBrowserMonthlyStatRPC() pb.ServerClientBrowserMonthlyStatServiceClient {
 | 
			
		||||
	return pb.NewServerClientBrowserMonthlyStatServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerRegionCountryMonthlyStatRPC() pb.ServerRegionCountryMonthlyStatServiceClient {
 | 
			
		||||
	return pb.NewServerRegionCountryMonthlyStatServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerRegionProvinceMonthlyStatRPC() pb.ServerRegionProvinceMonthlyStatServiceClient {
 | 
			
		||||
	return pb.NewServerRegionProvinceMonthlyStatServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerRegionCityMonthlyStatRPC() pb.ServerRegionCityMonthlyStatServiceClient {
 | 
			
		||||
	return pb.NewServerRegionCityMonthlyStatServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerRegionProviderMonthlyStatRPC() pb.ServerRegionProviderMonthlyStatServiceClient {
 | 
			
		||||
	return pb.NewServerRegionProviderMonthlyStatServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
 | 
			
		||||
	return pb.NewServerGroupServiceClient(this.pickConn())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,10 +70,10 @@ func (this *UpdateSchedulingPopupAction) RunGet(params struct {
 | 
			
		||||
		if !types.IsSlice(networks) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if (serverConfig.IsHTTP() && lists.Contains(networks, "http")) ||
 | 
			
		||||
			(serverConfig.IsTCP() && lists.Contains(networks, "tcp")) ||
 | 
			
		||||
			(serverConfig.IsUDP() && lists.Contains(networks, "udp")) ||
 | 
			
		||||
			(serverConfig.IsUnix() && lists.Contains(networks, "unix")) {
 | 
			
		||||
		if (serverConfig.IsHTTPFamily() && lists.Contains(networks, "http")) ||
 | 
			
		||||
			(serverConfig.IsTCPFamily() && lists.Contains(networks, "tcp")) ||
 | 
			
		||||
			(serverConfig.IsUDPFamily() && lists.Contains(networks, "udp")) ||
 | 
			
		||||
			(serverConfig.IsUnixFamily() && lists.Contains(networks, "unix")) {
 | 
			
		||||
			schedulingTypes = append(schedulingTypes, m)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								internal/web/actions/default/servers/server/stat/clients.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								internal/web/actions/default/servers/server/stat/clients.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
package stat
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	timeutil "github.com/iwind/TeaGo/utils/time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ClientsAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *ClientsAction) Init() {
 | 
			
		||||
	this.Nav("", "stat", "")
 | 
			
		||||
	this.SecondMenu("client")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *ClientsAction) RunGet(params struct {
 | 
			
		||||
	ServerId int64
 | 
			
		||||
	Month    string
 | 
			
		||||
}) {
 | 
			
		||||
	month := params.Month
 | 
			
		||||
	if len(month) != 6 {
 | 
			
		||||
		month = timeutil.Format("Ym")
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["month"] = month
 | 
			
		||||
 | 
			
		||||
	serverTypeResp, err := this.RPC().ServerRPC().FindEnabledServerType(this.AdminContext(), &pb.FindEnabledServerTypeRequest{ServerId: params.ServerId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	serverType := serverTypeResp.Type
 | 
			
		||||
 | 
			
		||||
	statIsOn := false
 | 
			
		||||
 | 
			
		||||
	// 是否已开启
 | 
			
		||||
	if serverconfigs.IsHTTPServerType(serverType) {
 | 
			
		||||
		webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if webConfig != nil && webConfig.StatRef != nil {
 | 
			
		||||
			statIsOn = webConfig.StatRef.IsOn
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		this.WriteString("此类型服务暂不支持统计")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["statIsOn"] = statIsOn
 | 
			
		||||
 | 
			
		||||
	// 统计数据
 | 
			
		||||
	systemMaps := []maps.Map{}
 | 
			
		||||
	browserMaps := []maps.Map{}
 | 
			
		||||
 | 
			
		||||
	if statIsOn {
 | 
			
		||||
		{
 | 
			
		||||
			resp, err := this.RPC().ServerClientSystemMonthlyStatRPC().FindTopServerClientSystemMonthlyStats(this.AdminContext(), &pb.FindTopServerClientSystemMonthlyStatsRequest{
 | 
			
		||||
				ServerId: params.ServerId,
 | 
			
		||||
				Month:    month,
 | 
			
		||||
				Offset:   0,
 | 
			
		||||
				Size:     10,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, stat := range resp.Stats {
 | 
			
		||||
				systemMaps = append(systemMaps, maps.Map{
 | 
			
		||||
					"count": stat.Count,
 | 
			
		||||
					"system": maps.Map{
 | 
			
		||||
						"id":   stat.ClientSystem.Id,
 | 
			
		||||
						"name": stat.ClientSystem.Name,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			resp, err := this.RPC().ServerClientBrowserMonthlyStatRPC().FindTopServerClientBrowserMonthlyStats(this.AdminContext(), &pb.FindTopServerClientBrowserMonthlyStatsRequest{
 | 
			
		||||
				ServerId: params.ServerId,
 | 
			
		||||
				Month:    month,
 | 
			
		||||
				Offset:   0,
 | 
			
		||||
				Size:     10,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, stat := range resp.Stats {
 | 
			
		||||
				browserMaps = append(browserMaps, maps.Map{
 | 
			
		||||
					"count": stat.Count,
 | 
			
		||||
					"browser": maps.Map{
 | 
			
		||||
						"id":   stat.ClientBrowser.Id,
 | 
			
		||||
						"name": stat.ClientBrowser.Name,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["systemStats"] = systemMaps
 | 
			
		||||
	this.Data["browserStats"] = browserMaps
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,13 @@
 | 
			
		||||
package stat
 | 
			
		||||
 | 
			
		||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	timeutil "github.com/iwind/TeaGo/utils/time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type IndexAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
@@ -11,6 +18,133 @@ func (this *IndexAction) Init() {
 | 
			
		||||
	this.SecondMenu("index")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *IndexAction) RunGet(params struct{}) {
 | 
			
		||||
func (this *IndexAction) RunGet(params struct {
 | 
			
		||||
	ServerId int64
 | 
			
		||||
	Month    string
 | 
			
		||||
}) {
 | 
			
		||||
	month := params.Month
 | 
			
		||||
	if len(month) != 6 {
 | 
			
		||||
		month = timeutil.Format("Ym")
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["month"] = month
 | 
			
		||||
 | 
			
		||||
	serverTypeResp, err := this.RPC().ServerRPC().FindEnabledServerType(this.AdminContext(), &pb.FindEnabledServerTypeRequest{ServerId: params.ServerId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	serverType := serverTypeResp.Type
 | 
			
		||||
 | 
			
		||||
	statIsOn := false
 | 
			
		||||
 | 
			
		||||
	// 是否已开启
 | 
			
		||||
	if serverconfigs.IsHTTPServerType(serverType) {
 | 
			
		||||
		webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if webConfig != nil && webConfig.StatRef != nil {
 | 
			
		||||
			statIsOn = webConfig.StatRef.IsOn
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		this.WriteString("此类型服务暂不支持统计")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["statIsOn"] = statIsOn
 | 
			
		||||
 | 
			
		||||
	// 统计数据
 | 
			
		||||
	countryStatMaps := []maps.Map{}
 | 
			
		||||
	provinceStatMaps := []maps.Map{}
 | 
			
		||||
	cityStatMaps := []maps.Map{}
 | 
			
		||||
 | 
			
		||||
	if statIsOn {
 | 
			
		||||
		// 地区
 | 
			
		||||
		{
 | 
			
		||||
			resp, err := this.RPC().ServerRegionCountryMonthlyStatRPC().FindTopServerRegionCountryMonthlyStats(this.AdminContext(), &pb.FindTopServerRegionCountryMonthlyStatsRequest{
 | 
			
		||||
				Month:    month,
 | 
			
		||||
				ServerId: params.ServerId,
 | 
			
		||||
				Offset:   0,
 | 
			
		||||
				Size:     10,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, stat := range resp.Stats {
 | 
			
		||||
				countryStatMaps = append(countryStatMaps, maps.Map{
 | 
			
		||||
					"count": stat.Count,
 | 
			
		||||
					"country": maps.Map{
 | 
			
		||||
						"id":   stat.RegionCountry.Id,
 | 
			
		||||
						"name": stat.RegionCountry.Name,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 省份
 | 
			
		||||
		{
 | 
			
		||||
			resp, err := this.RPC().ServerRegionProvinceMonthlyStatRPC().FindTopServerRegionProvinceMonthlyStats(this.AdminContext(), &pb.FindTopServerRegionProvinceMonthlyStatsRequest{
 | 
			
		||||
				Month:    month,
 | 
			
		||||
				ServerId: params.ServerId,
 | 
			
		||||
				Offset:   0,
 | 
			
		||||
				Size:     10,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, stat := range resp.Stats {
 | 
			
		||||
				provinceStatMaps = append(provinceStatMaps, maps.Map{
 | 
			
		||||
					"count": stat.Count,
 | 
			
		||||
					"country": maps.Map{
 | 
			
		||||
						"id":   stat.RegionCountry.Id,
 | 
			
		||||
						"name": stat.RegionCountry.Name,
 | 
			
		||||
					},
 | 
			
		||||
					"province": maps.Map{
 | 
			
		||||
						"id":   stat.RegionProvince.Id,
 | 
			
		||||
						"name": stat.RegionProvince.Name,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 城市
 | 
			
		||||
		{
 | 
			
		||||
			resp, err := this.RPC().ServerRegionCityMonthlyStatRPC().FindTopServerRegionCityMonthlyStats(this.AdminContext(), &pb.FindTopServerRegionCityMonthlyStatsRequest{
 | 
			
		||||
				Month:    month,
 | 
			
		||||
				ServerId: params.ServerId,
 | 
			
		||||
				Offset:   0,
 | 
			
		||||
				Size:     10,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, stat := range resp.Stats {
 | 
			
		||||
				cityStatMaps = append(cityStatMaps, maps.Map{
 | 
			
		||||
					"count": stat.Count,
 | 
			
		||||
					"country": maps.Map{
 | 
			
		||||
						"id":   stat.RegionCountry.Id,
 | 
			
		||||
						"name": stat.RegionCountry.Name,
 | 
			
		||||
					},
 | 
			
		||||
					"province": maps.Map{
 | 
			
		||||
						"id":   stat.RegionProvince.Id,
 | 
			
		||||
						"name": stat.RegionProvince.Name,
 | 
			
		||||
					},
 | 
			
		||||
					"city": maps.Map{
 | 
			
		||||
						"id":   stat.RegionCity.Id,
 | 
			
		||||
						"name": stat.RegionCity.Name,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["countryStats"] = countryStatMaps
 | 
			
		||||
	this.Data["provinceStats"] = provinceStatMaps
 | 
			
		||||
	this.Data["cityStats"] = cityStatMaps
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ func init() {
 | 
			
		||||
			Helper(serverutils.NewServerHelper()).
 | 
			
		||||
			Prefix("/servers/server/stat").
 | 
			
		||||
			Get("", new(IndexAction)).
 | 
			
		||||
			Get("/providers", new(ProvidersAction)).
 | 
			
		||||
			Get("/clients", new(ClientsAction)).
 | 
			
		||||
			EndAll()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,86 @@
 | 
			
		||||
package stat
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	timeutil "github.com/iwind/TeaGo/utils/time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ProvidersAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *ProvidersAction) Init() {
 | 
			
		||||
	this.Nav("", "stat", "")
 | 
			
		||||
	this.SecondMenu("provider")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *ProvidersAction) RunGet(params struct {
 | 
			
		||||
	ServerId int64
 | 
			
		||||
	Month    string
 | 
			
		||||
}) {
 | 
			
		||||
	month := params.Month
 | 
			
		||||
	if len(month) != 6 {
 | 
			
		||||
		month = timeutil.Format("Ym")
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["month"] = month
 | 
			
		||||
 | 
			
		||||
	serverTypeResp, err := this.RPC().ServerRPC().FindEnabledServerType(this.AdminContext(), &pb.FindEnabledServerTypeRequest{ServerId: params.ServerId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	serverType := serverTypeResp.Type
 | 
			
		||||
 | 
			
		||||
	statIsOn := false
 | 
			
		||||
 | 
			
		||||
	// 是否已开启
 | 
			
		||||
	if serverconfigs.IsHTTPServerType(serverType) {
 | 
			
		||||
		webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if webConfig != nil && webConfig.StatRef != nil {
 | 
			
		||||
			statIsOn = webConfig.StatRef.IsOn
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		this.WriteString("此类型服务暂不支持统计")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Data["statIsOn"] = statIsOn
 | 
			
		||||
 | 
			
		||||
	// 统计数据
 | 
			
		||||
	providerMaps := []maps.Map{}
 | 
			
		||||
 | 
			
		||||
	if statIsOn {
 | 
			
		||||
		{
 | 
			
		||||
			resp, err := this.RPC().ServerRegionProviderMonthlyStatRPC().FindTopServerRegionProviderMonthlyStats(this.AdminContext(), &pb.FindTopServerRegionProviderMonthlyStatsRequest{
 | 
			
		||||
				Month:    month,
 | 
			
		||||
				ServerId: params.ServerId,
 | 
			
		||||
				Offset:   0,
 | 
			
		||||
				Size:     10,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				this.ErrorPage(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			for _, stat := range resp.Stats {
 | 
			
		||||
				providerMaps = append(providerMaps, maps.Map{
 | 
			
		||||
					"count": stat.Count,
 | 
			
		||||
					"provider": maps.Map{
 | 
			
		||||
						"id":   stat.RegionProvider.Id,
 | 
			
		||||
						"name": stat.RegionProvider.Name,
 | 
			
		||||
					},
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["providerStats"] = providerMaps
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
@@ -77,13 +77,13 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
 | 
			
		||||
 | 
			
		||||
	// 协议簇
 | 
			
		||||
	family := ""
 | 
			
		||||
	if serverConfig.IsHTTP() {
 | 
			
		||||
	if serverConfig.IsHTTPFamily() {
 | 
			
		||||
		family = "http"
 | 
			
		||||
	} else if serverConfig.IsTCP() {
 | 
			
		||||
	} else if serverConfig.IsTCPFamily() {
 | 
			
		||||
		family = "tcp"
 | 
			
		||||
	} else if serverConfig.IsUnix() {
 | 
			
		||||
	} else if serverConfig.IsUnixFamily() {
 | 
			
		||||
		family = "unix"
 | 
			
		||||
	} else if serverConfig.IsUDP() {
 | 
			
		||||
	} else if serverConfig.IsUDPFamily() {
 | 
			
		||||
		family = "udp"
 | 
			
		||||
	}
 | 
			
		||||
	action.Data["serverFamily"] = family
 | 
			
		||||
@@ -94,7 +94,9 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
 | 
			
		||||
	tabbar.Add("服务列表", "", "/servers", "", false)
 | 
			
		||||
	//tabbar.Add("看板", "", "/servers/server/board?serverId="+serverIdString, "dashboard", selectedTabbar == "board")
 | 
			
		||||
	tabbar.Add("日志", "", "/servers/server/log?serverId="+serverIdString, "history", selectedTabbar == "log")
 | 
			
		||||
	//tabbar.Add("统计", "", "/servers/server/stat?serverId="+serverIdString, "chart area", selectedTabbar == "stat")
 | 
			
		||||
	if family == "http" {
 | 
			
		||||
		tabbar.Add("统计", "", "/servers/server/stat?serverId="+serverIdString, "chart area", selectedTabbar == "stat")
 | 
			
		||||
	}
 | 
			
		||||
	tabbar.Add("设置", "", "/servers/server/settings?serverId="+serverIdString, "setting", selectedTabbar == "setting")
 | 
			
		||||
	tabbar.Add("删除", "", "/servers/server/delete?serverId="+serverIdString, "trash", selectedTabbar == "delete")
 | 
			
		||||
	{
 | 
			
		||||
@@ -155,10 +157,20 @@ func (this *ServerHelper) createLogMenu(secondMenuItem string, serverIdString st
 | 
			
		||||
func (this *ServerHelper) createStatMenu(secondMenuItem string, serverIdString string, serverConfig *serverconfigs.ServerConfig) []maps.Map {
 | 
			
		||||
	menuItems := []maps.Map{}
 | 
			
		||||
	menuItems = append(menuItems, maps.Map{
 | 
			
		||||
		"name":     "统计",
 | 
			
		||||
		"name":     "地域分布",
 | 
			
		||||
		"url":      "/servers/server/stat?serverId=" + serverIdString,
 | 
			
		||||
		"isActive": secondMenuItem == "index",
 | 
			
		||||
	})
 | 
			
		||||
	menuItems = append(menuItems, maps.Map{
 | 
			
		||||
		"name":     "运营商",
 | 
			
		||||
		"url":      "/servers/server/stat/providers?serverId=" + serverIdString,
 | 
			
		||||
		"isActive": secondMenuItem == "provider",
 | 
			
		||||
	})
 | 
			
		||||
	menuItems = append(menuItems, maps.Map{
 | 
			
		||||
		"name":     "终端",
 | 
			
		||||
		"url":      "/servers/server/stat/clients?serverId=" + serverIdString,
 | 
			
		||||
		"isActive": secondMenuItem == "client",
 | 
			
		||||
	})
 | 
			
		||||
	return menuItems
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +191,7 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// HTTP
 | 
			
		||||
	if serverConfig.IsHTTP() {
 | 
			
		||||
	if serverConfig.IsHTTPFamily() {
 | 
			
		||||
		menuItems = append(menuItems, maps.Map{
 | 
			
		||||
			"name":     "域名",
 | 
			
		||||
			"url":      "/servers/server/settings/serverNames?serverId=" + serverIdString,
 | 
			
		||||
@@ -294,7 +306,7 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
 | 
			
		||||
			"isActive": secondMenuItem == "websocket",
 | 
			
		||||
			"isOn":     serverConfig.Web != nil && serverConfig.Web.WebsocketRef != nil && serverConfig.Web.WebsocketRef.IsOn,
 | 
			
		||||
		})
 | 
			
		||||
	} else if serverConfig.IsTCP() {
 | 
			
		||||
	} else if serverConfig.IsTCPFamily() {
 | 
			
		||||
		menuItems = append(menuItems, maps.Map{
 | 
			
		||||
			"name":     "TCP",
 | 
			
		||||
			"url":      "/servers/server/settings/tcp?serverId=" + serverIdString,
 | 
			
		||||
@@ -313,14 +325,14 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
 | 
			
		||||
			"isActive": secondMenuItem == "reverseProxy",
 | 
			
		||||
			"isOn":     serverConfig.ReverseProxyRef != nil && serverConfig.ReverseProxyRef.IsOn,
 | 
			
		||||
		})
 | 
			
		||||
	} else if serverConfig.IsUnix() {
 | 
			
		||||
	} else if serverConfig.IsUnixFamily() {
 | 
			
		||||
		menuItems = append(menuItems, maps.Map{
 | 
			
		||||
			"name":     "Unix",
 | 
			
		||||
			"url":      "/servers/server/settings/unix?serverId=" + serverIdString,
 | 
			
		||||
			"isActive": secondMenuItem == "unix",
 | 
			
		||||
			"isOn":     serverConfig.Unix != nil && serverConfig.Unix.IsOn && len(serverConfig.Unix.Listen) > 0,
 | 
			
		||||
		})
 | 
			
		||||
	} else if serverConfig.IsUDP() {
 | 
			
		||||
	} else if serverConfig.IsUDPFamily() {
 | 
			
		||||
		menuItems = append(menuItems, maps.Map{
 | 
			
		||||
			"name":     "UDP",
 | 
			
		||||
			"url":      "/servers/server/settings/udp?serverId=" + serverIdString,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ Vue.component("http-stat-config-box", {
 | 
			
		||||
		if (stat == null) {
 | 
			
		||||
			stat = {
 | 
			
		||||
				isPrior: false,
 | 
			
		||||
				isOn: true
 | 
			
		||||
				isOn: false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ Vue.component("http-websocket-box", {
 | 
			
		||||
		if (websocketConfig == null) {
 | 
			
		||||
			websocketConfig = {
 | 
			
		||||
				id: 0,
 | 
			
		||||
				isOn: true,
 | 
			
		||||
				isOn: false,
 | 
			
		||||
				handshakeTimeout: {
 | 
			
		||||
					count: 30,
 | 
			
		||||
					unit: "second"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,261 +1,263 @@
 | 
			
		||||
window.teaweb = {
 | 
			
		||||
	set: function (key, value) {
 | 
			
		||||
		localStorage.setItem(key, JSON.stringify(value));
 | 
			
		||||
	},
 | 
			
		||||
	get: function (key) {
 | 
			
		||||
		var item = localStorage.getItem(key);
 | 
			
		||||
		if (item == null || item.length == 0) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
    set: function (key, value) {
 | 
			
		||||
        localStorage.setItem(key, JSON.stringify(value));
 | 
			
		||||
    },
 | 
			
		||||
    get: function (key) {
 | 
			
		||||
        var item = localStorage.getItem(key);
 | 
			
		||||
        if (item == null || item.length == 0) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		return JSON.parse(item);
 | 
			
		||||
	},
 | 
			
		||||
	getString: function (key) {
 | 
			
		||||
		var value = this.get(key);
 | 
			
		||||
		if (typeof (value) == "string") {
 | 
			
		||||
			return value;
 | 
			
		||||
		}
 | 
			
		||||
		return "";
 | 
			
		||||
	},
 | 
			
		||||
	getBool: function (key) {
 | 
			
		||||
		return Boolean(this.get(key));
 | 
			
		||||
	},
 | 
			
		||||
	remove: function (key) {
 | 
			
		||||
		localStorage.removeItem(key)
 | 
			
		||||
	},
 | 
			
		||||
	match: function (source, keyword) {
 | 
			
		||||
		if (source == null) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		if (keyword == null) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		source = source.trim();
 | 
			
		||||
		keyword = keyword.trim();
 | 
			
		||||
		if (keyword.length == 0) {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
		if (source.length == 0) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		var pieces = keyword.split(/\s+/);
 | 
			
		||||
		for (var i = 0; i < pieces.length; i++) {
 | 
			
		||||
			var pattern = pieces[i];
 | 
			
		||||
			pattern = pattern.replace(/(\+|\*|\?|[|]|{|}|\||\\|\(|\)|\.)/g, "\\$1");
 | 
			
		||||
			var reg = new RegExp(pattern, "i");
 | 
			
		||||
			if (!reg.test(source)) {
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true;
 | 
			
		||||
	},
 | 
			
		||||
        return JSON.parse(item);
 | 
			
		||||
    },
 | 
			
		||||
    getString: function (key) {
 | 
			
		||||
        var value = this.get(key);
 | 
			
		||||
        if (typeof (value) == "string") {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
    },
 | 
			
		||||
    getBool: function (key) {
 | 
			
		||||
        return Boolean(this.get(key));
 | 
			
		||||
    },
 | 
			
		||||
    remove: function (key) {
 | 
			
		||||
        localStorage.removeItem(key)
 | 
			
		||||
    },
 | 
			
		||||
    match: function (source, keyword) {
 | 
			
		||||
        if (source == null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (keyword == null) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        source = source.trim();
 | 
			
		||||
        keyword = keyword.trim();
 | 
			
		||||
        if (keyword.length == 0) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (source.length == 0) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        var pieces = keyword.split(/\s+/);
 | 
			
		||||
        for (var i = 0; i < pieces.length; i++) {
 | 
			
		||||
            var pattern = pieces[i];
 | 
			
		||||
            pattern = pattern.replace(/(\+|\*|\?|[|]|{|}|\||\\|\(|\)|\.)/g, "\\$1");
 | 
			
		||||
            var reg = new RegExp(pattern, "i");
 | 
			
		||||
            if (!reg.test(source)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
	datepicker: function (element, callback) {
 | 
			
		||||
		if (typeof (element) == "string") {
 | 
			
		||||
			element = document.getElementById(element);
 | 
			
		||||
		}
 | 
			
		||||
		var year = new Date().getFullYear();
 | 
			
		||||
		var picker = new Pikaday({
 | 
			
		||||
			field: element,
 | 
			
		||||
			firstDay: 1,
 | 
			
		||||
			minDate: new Date(year - 1, 0, 1),
 | 
			
		||||
			maxDate: new Date(year + 10, 11, 31),
 | 
			
		||||
			yearRange: [year - 1, year + 10],
 | 
			
		||||
			format: "YYYY-MM-DD",
 | 
			
		||||
			i18n: {
 | 
			
		||||
				previousMonth: '上月',
 | 
			
		||||
				nextMonth: '下月',
 | 
			
		||||
				months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
 | 
			
		||||
				weekdays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
 | 
			
		||||
				weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
 | 
			
		||||
			},
 | 
			
		||||
			theme: 'triangle-theme',
 | 
			
		||||
			onSelect: function () {
 | 
			
		||||
				if (typeof (callback) == "function") {
 | 
			
		||||
					callback.call(Tea.Vue, picker.toString());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
    datepicker: function (element, callback) {
 | 
			
		||||
        if (typeof (element) == "string") {
 | 
			
		||||
            element = document.getElementById(element);
 | 
			
		||||
        }
 | 
			
		||||
        var year = new Date().getFullYear();
 | 
			
		||||
        var picker = new Pikaday({
 | 
			
		||||
            field: element,
 | 
			
		||||
            firstDay: 1,
 | 
			
		||||
            minDate: new Date(year - 1, 0, 1),
 | 
			
		||||
            maxDate: new Date(year + 10, 11, 31),
 | 
			
		||||
            yearRange: [year - 1, year + 10],
 | 
			
		||||
            format: "YYYY-MM-DD",
 | 
			
		||||
            i18n: {
 | 
			
		||||
                previousMonth: '上月',
 | 
			
		||||
                nextMonth: '下月',
 | 
			
		||||
                months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
 | 
			
		||||
                weekdays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
 | 
			
		||||
                weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
 | 
			
		||||
            },
 | 
			
		||||
            theme: 'triangle-theme',
 | 
			
		||||
            onSelect: function () {
 | 
			
		||||
                if (typeof (callback) == "function") {
 | 
			
		||||
                    callback.call(Tea.Vue, picker.toString());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
	formatBytes: function (bytes) {
 | 
			
		||||
		bytes = Math.ceil(bytes);
 | 
			
		||||
		if (bytes < 1024) {
 | 
			
		||||
			return bytes + " bytes";
 | 
			
		||||
		}
 | 
			
		||||
		if (bytes < 1024 * 1024) {
 | 
			
		||||
			return (Math.ceil(bytes * 100 / 1024) / 100) + " k";
 | 
			
		||||
		}
 | 
			
		||||
		return (Math.ceil(bytes * 100 / 1024 / 1024) / 100) + " m";
 | 
			
		||||
	},
 | 
			
		||||
    formatBytes: function (bytes) {
 | 
			
		||||
        bytes = Math.ceil(bytes);
 | 
			
		||||
        if (bytes < 1024) {
 | 
			
		||||
            return bytes + " bytes";
 | 
			
		||||
        }
 | 
			
		||||
        if (bytes < 1024 * 1024) {
 | 
			
		||||
            return (Math.ceil(bytes * 100 / 1024) / 100) + " k";
 | 
			
		||||
        }
 | 
			
		||||
        return (Math.ceil(bytes * 100 / 1024 / 1024) / 100) + " m";
 | 
			
		||||
    },
 | 
			
		||||
    formatNumber: function (x) {
 | 
			
		||||
        return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ", ");
 | 
			
		||||
    },
 | 
			
		||||
    popup: function (url, options) {
 | 
			
		||||
        if (options == null) {
 | 
			
		||||
            options = {};
 | 
			
		||||
        }
 | 
			
		||||
        var width = "40em";
 | 
			
		||||
        var height = "20em";
 | 
			
		||||
        window.POPUP_CALLBACK = function () {
 | 
			
		||||
            Swal.close();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
	popup: function (url, options) {
 | 
			
		||||
		if (options == null) {
 | 
			
		||||
			options = {};
 | 
			
		||||
		}
 | 
			
		||||
		var width = "40em";
 | 
			
		||||
		var height = "20em";
 | 
			
		||||
		window.POPUP_CALLBACK = function () {
 | 
			
		||||
			Swal.close();
 | 
			
		||||
		};
 | 
			
		||||
        if (options["width"] != null) {
 | 
			
		||||
            width = options["width"];
 | 
			
		||||
        }
 | 
			
		||||
        if (options["height"] != null) {
 | 
			
		||||
            height = options["height"];
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof (options["callback"]) == "function") {
 | 
			
		||||
            window.POPUP_CALLBACK = function () {
 | 
			
		||||
                Swal.close();
 | 
			
		||||
                options["callback"].apply(Tea.Vue, arguments);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		if (options["width"] != null) {
 | 
			
		||||
			width = options["width"];
 | 
			
		||||
		}
 | 
			
		||||
		if (options["height"] != null) {
 | 
			
		||||
			height = options["height"];
 | 
			
		||||
		}
 | 
			
		||||
		if (typeof (options["callback"]) == "function") {
 | 
			
		||||
			window.POPUP_CALLBACK = function () {
 | 
			
		||||
				Swal.close();
 | 
			
		||||
				options["callback"].apply(Tea.Vue, arguments);
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
        Swal.fire({
 | 
			
		||||
            html: '<iframe src="' + url + '#popup-' + width + '" style="border:0; width: 100%; height:' + height + '"></iframe>',
 | 
			
		||||
            width: width,
 | 
			
		||||
            padding: "0.5em",
 | 
			
		||||
            showConfirmButton: false,
 | 
			
		||||
            showCloseButton: true,
 | 
			
		||||
            focusConfirm: false,
 | 
			
		||||
            onClose: function (popup) {
 | 
			
		||||
                if (typeof (options["onClose"]) == "function") {
 | 
			
		||||
                    options["onClose"].apply(Tea.Vue, arguments)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    popupFinish: function () {
 | 
			
		||||
        if (window.POPUP_CALLBACK != null) {
 | 
			
		||||
            window.POPUP_CALLBACK.apply(window, arguments);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    popupTip: function (html) {
 | 
			
		||||
        Swal.fire({
 | 
			
		||||
            html: '<i class="icon question circle"></i><span style="line-height: 1.7">' + html + "</span>",
 | 
			
		||||
            width: "30em",
 | 
			
		||||
            padding: "5em",
 | 
			
		||||
            showConfirmButton: false,
 | 
			
		||||
            showCloseButton: true,
 | 
			
		||||
            focusConfirm: false
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    isPopup: function () {
 | 
			
		||||
        var hash = window.location.hash;
 | 
			
		||||
        return hash != null && hash.startsWith("#popup");
 | 
			
		||||
    },
 | 
			
		||||
    Swal: function () {
 | 
			
		||||
        return this.isPopup() ? window.parent.Swal : window.Swal;
 | 
			
		||||
    },
 | 
			
		||||
    success: function (message, callback) {
 | 
			
		||||
        var width = "20em";
 | 
			
		||||
        if (message.length > 30) {
 | 
			
		||||
            width = "30em";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		Swal.fire({
 | 
			
		||||
			html: '<iframe src="' + url + '#popup-' + width + '" style="border:0; width: 100%; height:' + height + '"></iframe>',
 | 
			
		||||
			width: width,
 | 
			
		||||
			padding: "0.5em",
 | 
			
		||||
			showConfirmButton: false,
 | 
			
		||||
			showCloseButton: true,
 | 
			
		||||
			focusConfirm: false,
 | 
			
		||||
			onClose: function (popup) {
 | 
			
		||||
				if (typeof (options["onClose"]) == "function") {
 | 
			
		||||
					options["onClose"].apply(Tea.Vue, arguments)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	popupFinish: function () {
 | 
			
		||||
		if (window.POPUP_CALLBACK != null) {
 | 
			
		||||
			window.POPUP_CALLBACK.apply(window, arguments);
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	popupTip: function (html) {
 | 
			
		||||
		Swal.fire({
 | 
			
		||||
			html: '<i class="icon question circle"></i><span style="line-height: 1.7">' + html + "</span>",
 | 
			
		||||
			width: "30em",
 | 
			
		||||
			padding: "5em",
 | 
			
		||||
			showConfirmButton: false,
 | 
			
		||||
			showCloseButton: true,
 | 
			
		||||
			focusConfirm: false
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	isPopup: function () {
 | 
			
		||||
		var hash = window.location.hash;
 | 
			
		||||
		return hash != null && hash.startsWith("#popup");
 | 
			
		||||
	},
 | 
			
		||||
	Swal: function () {
 | 
			
		||||
		return this.isPopup() ? window.parent.Swal : window.Swal;
 | 
			
		||||
	},
 | 
			
		||||
	success: function (message, callback) {
 | 
			
		||||
		var width = "20em";
 | 
			
		||||
		if (message.length > 30) {
 | 
			
		||||
			width = "30em";
 | 
			
		||||
		}
 | 
			
		||||
        let config = {
 | 
			
		||||
            confirmButtonText: "确定",
 | 
			
		||||
            buttonsStyling: false,
 | 
			
		||||
            icon: "success",
 | 
			
		||||
            customClass: {
 | 
			
		||||
                closeButton: "ui button",
 | 
			
		||||
                cancelButton: "ui button",
 | 
			
		||||
                confirmButton: "ui button primary"
 | 
			
		||||
            },
 | 
			
		||||
            width: width,
 | 
			
		||||
            onAfterClose: function () {
 | 
			
		||||
                if (typeof (callback) == "function") {
 | 
			
		||||
                    setTimeout(function () {
 | 
			
		||||
                        callback();
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (typeof (callback) == "string") {
 | 
			
		||||
                    window.location = callback
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		let config = {
 | 
			
		||||
			confirmButtonText: "确定",
 | 
			
		||||
			buttonsStyling: false,
 | 
			
		||||
			icon: "success",
 | 
			
		||||
			customClass: {
 | 
			
		||||
				closeButton: "ui button",
 | 
			
		||||
				cancelButton: "ui button",
 | 
			
		||||
				confirmButton: "ui button primary"
 | 
			
		||||
			},
 | 
			
		||||
			width: width,
 | 
			
		||||
			onAfterClose: function () {
 | 
			
		||||
				if (typeof (callback) == "function") {
 | 
			
		||||
					setTimeout(function () {
 | 
			
		||||
						callback();
 | 
			
		||||
					});
 | 
			
		||||
				} else if (typeof (callback) == "string") {
 | 
			
		||||
					window.location = callback
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
        if (message.startsWith("html:")) {
 | 
			
		||||
            config.html = message.substring(5)
 | 
			
		||||
        } else {
 | 
			
		||||
            config.text = message
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		if (message.startsWith("html:")) {
 | 
			
		||||
			config.html = message.substring(5)
 | 
			
		||||
		} else {
 | 
			
		||||
			config.text = message
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Swal.fire(config);
 | 
			
		||||
	},
 | 
			
		||||
	successToast: function (message, timeout) {
 | 
			
		||||
		if (timeout == null) {
 | 
			
		||||
			timeout = 2000
 | 
			
		||||
		}
 | 
			
		||||
		var width = "20em";
 | 
			
		||||
		if (message.length > 30) {
 | 
			
		||||
			width = "30em";
 | 
			
		||||
		}
 | 
			
		||||
		Swal.fire({
 | 
			
		||||
			text: message,
 | 
			
		||||
			icon: "success",
 | 
			
		||||
			width: width,
 | 
			
		||||
			timer: timeout,
 | 
			
		||||
			showConfirmButton: false
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	warn: function (message, callback) {
 | 
			
		||||
		var width = "20em";
 | 
			
		||||
		if (message.length > 30) {
 | 
			
		||||
			width = "30em";
 | 
			
		||||
		}
 | 
			
		||||
		Swal.fire({
 | 
			
		||||
			text: message,
 | 
			
		||||
			confirmButtonText: "确定",
 | 
			
		||||
			buttonsStyling: false,
 | 
			
		||||
			customClass: {
 | 
			
		||||
				closeButton: "ui button",
 | 
			
		||||
				cancelButton: "ui button",
 | 
			
		||||
				confirmButton: "ui button primary"
 | 
			
		||||
			},
 | 
			
		||||
			icon: "warning",
 | 
			
		||||
			width: width,
 | 
			
		||||
			onAfterClose: function () {
 | 
			
		||||
				if (typeof (callback) == "function") {
 | 
			
		||||
					setTimeout(function () {
 | 
			
		||||
						callback();
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	confirm: function (message, callback) {
 | 
			
		||||
		let width = "20em";
 | 
			
		||||
		if (message.length > 30) {
 | 
			
		||||
			width = "30em";
 | 
			
		||||
		}
 | 
			
		||||
		let config = {
 | 
			
		||||
			confirmButtonText: "确定",
 | 
			
		||||
			cancelButtonText: "取消",
 | 
			
		||||
			showCancelButton: true,
 | 
			
		||||
			showCloseButton: false,
 | 
			
		||||
			buttonsStyling: false,
 | 
			
		||||
			customClass: {
 | 
			
		||||
				closeButton: "ui button",
 | 
			
		||||
				cancelButton: "ui button",
 | 
			
		||||
				confirmButton: "ui button primary"
 | 
			
		||||
			},
 | 
			
		||||
			icon: "warning",
 | 
			
		||||
			width: width,
 | 
			
		||||
			preConfirm: function () {
 | 
			
		||||
				if (typeof (callback) == "function") {
 | 
			
		||||
					callback.call(Tea.Vue);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (message.startsWith("html:")) {
 | 
			
		||||
			config.html = message.substring(5)
 | 
			
		||||
		} else {
 | 
			
		||||
			config.text = message
 | 
			
		||||
		}
 | 
			
		||||
		Swal.fire(config);
 | 
			
		||||
	},
 | 
			
		||||
	reload: function () {
 | 
			
		||||
		window.location.reload()
 | 
			
		||||
	}
 | 
			
		||||
        Swal.fire(config);
 | 
			
		||||
    },
 | 
			
		||||
    successToast: function (message, timeout) {
 | 
			
		||||
        if (timeout == null) {
 | 
			
		||||
            timeout = 2000
 | 
			
		||||
        }
 | 
			
		||||
        var width = "20em";
 | 
			
		||||
        if (message.length > 30) {
 | 
			
		||||
            width = "30em";
 | 
			
		||||
        }
 | 
			
		||||
        Swal.fire({
 | 
			
		||||
            text: message,
 | 
			
		||||
            icon: "success",
 | 
			
		||||
            width: width,
 | 
			
		||||
            timer: timeout,
 | 
			
		||||
            showConfirmButton: false
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    warn: function (message, callback) {
 | 
			
		||||
        var width = "20em";
 | 
			
		||||
        if (message.length > 30) {
 | 
			
		||||
            width = "30em";
 | 
			
		||||
        }
 | 
			
		||||
        Swal.fire({
 | 
			
		||||
            text: message,
 | 
			
		||||
            confirmButtonText: "确定",
 | 
			
		||||
            buttonsStyling: false,
 | 
			
		||||
            customClass: {
 | 
			
		||||
                closeButton: "ui button",
 | 
			
		||||
                cancelButton: "ui button",
 | 
			
		||||
                confirmButton: "ui button primary"
 | 
			
		||||
            },
 | 
			
		||||
            icon: "warning",
 | 
			
		||||
            width: width,
 | 
			
		||||
            onAfterClose: function () {
 | 
			
		||||
                if (typeof (callback) == "function") {
 | 
			
		||||
                    setTimeout(function () {
 | 
			
		||||
                        callback();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    confirm: function (message, callback) {
 | 
			
		||||
        let width = "20em";
 | 
			
		||||
        if (message.length > 30) {
 | 
			
		||||
            width = "30em";
 | 
			
		||||
        }
 | 
			
		||||
        let config = {
 | 
			
		||||
            confirmButtonText: "确定",
 | 
			
		||||
            cancelButtonText: "取消",
 | 
			
		||||
            showCancelButton: true,
 | 
			
		||||
            showCloseButton: false,
 | 
			
		||||
            buttonsStyling: false,
 | 
			
		||||
            customClass: {
 | 
			
		||||
                closeButton: "ui button",
 | 
			
		||||
                cancelButton: "ui button",
 | 
			
		||||
                confirmButton: "ui button primary"
 | 
			
		||||
            },
 | 
			
		||||
            icon: "warning",
 | 
			
		||||
            width: width,
 | 
			
		||||
            preConfirm: function () {
 | 
			
		||||
                if (typeof (callback) == "function") {
 | 
			
		||||
                    callback.call(Tea.Vue);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (message.startsWith("html:")) {
 | 
			
		||||
            config.html = message.substring(5)
 | 
			
		||||
        } else {
 | 
			
		||||
            config.text = message
 | 
			
		||||
        }
 | 
			
		||||
        Swal.fire(config);
 | 
			
		||||
    },
 | 
			
		||||
    reload: function () {
 | 
			
		||||
        window.location.reload()
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
{$layout}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{$var "header"}
 | 
			
		||||
<!-- echart -->
 | 
			
		||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								web/views/@default/servers/server/stat/clients.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								web/views/@default/servers/server/stat/clients.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
.chart-box {
 | 
			
		||||
  height: 20em;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=clients.css.map */
 | 
			
		||||
							
								
								
									
										1
									
								
								web/views/@default/servers/server/stat/clients.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/views/@default/servers/server/stat/clients.css.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"sources":["clients.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"clients.css"}
 | 
			
		||||
							
								
								
									
										21
									
								
								web/views/@default/servers/server/stat/clients.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								web/views/@default/servers/server/stat/clients.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
{$layout}
 | 
			
		||||
 | 
			
		||||
{$var "header"}
 | 
			
		||||
<!-- echart -->
 | 
			
		||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
 | 
			
		||||
{$end}
 | 
			
		||||
 | 
			
		||||
{$template "/left_menu"}
 | 
			
		||||
<div class="right-box">
 | 
			
		||||
    {$ if eq .statIsOn false}
 | 
			
		||||
	<p class="ui message">
 | 
			
		||||
        要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
 | 
			
		||||
    </p>
 | 
			
		||||
    {$else}
 | 
			
		||||
    <h4>操作系统排行</h4>
 | 
			
		||||
    <div class="chart-box" id="system-chart"></div>
 | 
			
		||||
 | 
			
		||||
    <h4>浏览器排行</h4>
 | 
			
		||||
    <div class="chart-box" id="browser-chart"></div>
 | 
			
		||||
    {$end}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										108
									
								
								web/views/@default/servers/server/stat/clients.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								web/views/@default/servers/server/stat/clients.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
    this.$delay(function () {
 | 
			
		||||
        let that = this
 | 
			
		||||
 | 
			
		||||
        let systemUnit = this.processMaxUnit(this.systemStats)
 | 
			
		||||
        this.reloadChart("system-chart", "操作系统", this.systemStats, function (v) {
 | 
			
		||||
            return v.system.name
 | 
			
		||||
        }, function (args) {
 | 
			
		||||
            return that.systemStats[args.dataIndex].system.name + ": " + teaweb.formatNumber(that.systemStats[args.dataIndex].rawCount)
 | 
			
		||||
        }, systemUnit)
 | 
			
		||||
        window.addEventListener("resize", function () {
 | 
			
		||||
            that.resizeChart("system-chart")
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        let browserUnit = this.processMaxUnit(this.browserStats)
 | 
			
		||||
        this.reloadChart("browser-chart", "浏览器", this.browserStats, function (v) {
 | 
			
		||||
            return v.browser.name
 | 
			
		||||
        }, function (args) {
 | 
			
		||||
            return that.browserStats[args.dataIndex].browser.name + ": " + teaweb.formatNumber(that.browserStats[args.dataIndex].rawCount)
 | 
			
		||||
        }, browserUnit)
 | 
			
		||||
 | 
			
		||||
        window.addEventListener("resize", function () {
 | 
			
		||||
            that.resizeChart("system-chart")
 | 
			
		||||
            that.resizeChart("browser-chart")
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    this.reloadChart = function (chartId, name, stats, xFunc, tooltipFunc, unit) {
 | 
			
		||||
        let chartBox = document.getElementById(chartId)
 | 
			
		||||
        if (chartBox == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let chart = echarts.init(chartBox)
 | 
			
		||||
        let option = {
 | 
			
		||||
            xAxis: {
 | 
			
		||||
                data: stats.map(xFunc)
 | 
			
		||||
            },
 | 
			
		||||
            yAxis: {
 | 
			
		||||
                axisLabel: {
 | 
			
		||||
                    formatter: function (value) {
 | 
			
		||||
                        return value + unit
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            tooltip: {
 | 
			
		||||
                show: true,
 | 
			
		||||
                trigger: "item",
 | 
			
		||||
                formatter: tooltipFunc
 | 
			
		||||
            },
 | 
			
		||||
            grid: {
 | 
			
		||||
                left: 40,
 | 
			
		||||
                top: 10,
 | 
			
		||||
                right: 20,
 | 
			
		||||
                bottom: 20
 | 
			
		||||
            },
 | 
			
		||||
            series: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: name,
 | 
			
		||||
                    type: "bar",
 | 
			
		||||
                    data: stats.map(function (v) {
 | 
			
		||||
                        return v.count;
 | 
			
		||||
                    }),
 | 
			
		||||
                    itemStyle: {
 | 
			
		||||
                        color: "#9DD3E8"
 | 
			
		||||
                    },
 | 
			
		||||
                    barWidth: "20em"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            animation: true
 | 
			
		||||
        }
 | 
			
		||||
        chart.setOption(option)
 | 
			
		||||
        chart.resize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.resizeChart = function (chartId) {
 | 
			
		||||
        let chartBox = document.getElementById(chartId)
 | 
			
		||||
        if (chartBox == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let chart = echarts.init(chartBox)
 | 
			
		||||
        chart.resize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.processMaxUnit = function (stats) {
 | 
			
		||||
        let max = stats.$map(function (k, v) {
 | 
			
		||||
            return v.count
 | 
			
		||||
        }).$max()
 | 
			
		||||
        let divider = 0
 | 
			
		||||
        let unit = ""
 | 
			
		||||
        if (max >= 1000 * 1000 * 1000) {
 | 
			
		||||
            unit = "B"
 | 
			
		||||
            divider = 1000 * 1000 * 1000
 | 
			
		||||
        } else if (max >= 1000 * 1000) {
 | 
			
		||||
            unit = "M"
 | 
			
		||||
            divider = 1000 * 1000
 | 
			
		||||
        } else if (max >= 1000) {
 | 
			
		||||
            unit = "K"
 | 
			
		||||
            divider = 1000
 | 
			
		||||
        }
 | 
			
		||||
        stats.forEach(function (v) {
 | 
			
		||||
            v.rawCount = v.count
 | 
			
		||||
            if (divider > 0) {
 | 
			
		||||
                v.count /= divider
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return unit
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										3
									
								
								web/views/@default/servers/server/stat/clients.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/views/@default/servers/server/stat/clients.less
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
.chart-box {
 | 
			
		||||
	height: 20em;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
.chart-box {
 | 
			
		||||
  height: 20em;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=index.css.map */
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
undefined
 | 
			
		||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"index.css"}
 | 
			
		||||
@@ -1,6 +1,30 @@
 | 
			
		||||
{$layout}
 | 
			
		||||
 | 
			
		||||
{$var "header"}
 | 
			
		||||
<!-- echart -->
 | 
			
		||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
 | 
			
		||||
{$end}
 | 
			
		||||
 | 
			
		||||
{$template "/left_menu"}
 | 
			
		||||
<div class="right-box">
 | 
			
		||||
	<div class="ui message">此功能暂未开放,敬请期待。</div>
 | 
			
		||||
    {$ if eq .statIsOn false}
 | 
			
		||||
	<p class="ui message">
 | 
			
		||||
        要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
 | 
			
		||||
    </p>
 | 
			
		||||
    {$else}
 | 
			
		||||
        <h4>地区排行</h4>
 | 
			
		||||
        <div class="chart-box" id="country-chart">
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <h4>省市排行</h4>
 | 
			
		||||
        <div class="chart-box" id="province-chart">
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <h4>城市排行</h4>
 | 
			
		||||
        <div class="chart-box" id="city-chart">
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
    {$end}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										113
									
								
								web/views/@default/servers/server/stat/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								web/views/@default/servers/server/stat/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
    this.$delay(function () {
 | 
			
		||||
        let that = this
 | 
			
		||||
 | 
			
		||||
        let countryUnit = this.processMaxUnit(this.countryStats)
 | 
			
		||||
        this.reloadChart("country-chart", "地区", this.countryStats, function (v) {
 | 
			
		||||
            return v.country.name
 | 
			
		||||
        }, function (args) {
 | 
			
		||||
            return that.countryStats[args.dataIndex].country.name + ": " + teaweb.formatNumber(that.countryStats[args.dataIndex].rawCount)
 | 
			
		||||
        }, countryUnit)
 | 
			
		||||
 | 
			
		||||
        let provinceUnit = this.processMaxUnit(this.provinceStats)
 | 
			
		||||
        this.reloadChart("province-chart", "省市", this.provinceStats, function (v) {
 | 
			
		||||
            return v.province.name
 | 
			
		||||
        }, function (args) {
 | 
			
		||||
            return that.provinceStats[args.dataIndex].country.name + ": " + that.provinceStats[args.dataIndex].province.name + " " + teaweb.formatNumber(that.provinceStats[args.dataIndex].rawCount)
 | 
			
		||||
        }, provinceUnit)
 | 
			
		||||
 | 
			
		||||
        let cityUnit = this.processMaxUnit(this.cityStats)
 | 
			
		||||
        this.reloadChart("city-chart", "城市", this.cityStats, function (v) {
 | 
			
		||||
            return v.city.name
 | 
			
		||||
        }, function (args) {
 | 
			
		||||
            return that.cityStats[args.dataIndex].country.name + ": " + that.cityStats[args.dataIndex].province.name + " " + that.cityStats[args.dataIndex].city.name + " " + teaweb.formatNumber(that.cityStats[args.dataIndex].rawCount)
 | 
			
		||||
        }, cityUnit)
 | 
			
		||||
 | 
			
		||||
        window.addEventListener("resize", function () {
 | 
			
		||||
            that.resizeChart("country-chart")
 | 
			
		||||
            that.resizeChart("province-chart")
 | 
			
		||||
            that.resizeChart("city-chart")
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    this.reloadChart = function (chartId, name, stats, xFunc, tooltipFunc, unit) {
 | 
			
		||||
        let chartBox = document.getElementById(chartId)
 | 
			
		||||
        if (chartBox == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let chart = echarts.init(chartBox)
 | 
			
		||||
        let option = {
 | 
			
		||||
            xAxis: {
 | 
			
		||||
                data: stats.map(xFunc)
 | 
			
		||||
            },
 | 
			
		||||
            yAxis: {
 | 
			
		||||
                axisLabel: {
 | 
			
		||||
                    formatter: function (value) {
 | 
			
		||||
                        return value + unit
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            tooltip: {
 | 
			
		||||
                show: true,
 | 
			
		||||
                trigger: "item",
 | 
			
		||||
                formatter: tooltipFunc
 | 
			
		||||
            },
 | 
			
		||||
            grid: {
 | 
			
		||||
                left: 40,
 | 
			
		||||
                top: 10,
 | 
			
		||||
                right: 20,
 | 
			
		||||
                bottom: 20
 | 
			
		||||
            },
 | 
			
		||||
            series: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: name,
 | 
			
		||||
                    type: "bar",
 | 
			
		||||
                    data: stats.map(function (v) {
 | 
			
		||||
                        return v.count;
 | 
			
		||||
                    }),
 | 
			
		||||
                    itemStyle: {
 | 
			
		||||
                        color: "#9DD3E8"
 | 
			
		||||
                    },
 | 
			
		||||
                    barWidth: "20em"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            animation: true
 | 
			
		||||
        }
 | 
			
		||||
        chart.setOption(option)
 | 
			
		||||
        chart.resize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.resizeChart = function (chartId) {
 | 
			
		||||
        let chartBox = document.getElementById(chartId)
 | 
			
		||||
        if (chartBox == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let chart = echarts.init(chartBox)
 | 
			
		||||
        chart.resize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.processMaxUnit = function (stats) {
 | 
			
		||||
        let max = stats.$map(function (k, v) {
 | 
			
		||||
            return v.count
 | 
			
		||||
        }).$max()
 | 
			
		||||
        let divider = 0
 | 
			
		||||
        let unit = ""
 | 
			
		||||
        if (max >= 1000 * 1000 * 1000) {
 | 
			
		||||
            unit = "B"
 | 
			
		||||
            divider = 1000 * 1000 * 1000
 | 
			
		||||
        } else if (max >= 1000 * 1000) {
 | 
			
		||||
            unit = "M"
 | 
			
		||||
            divider = 1000 * 1000
 | 
			
		||||
        } else if (max >= 1000) {
 | 
			
		||||
            unit = "K"
 | 
			
		||||
            divider = 1000
 | 
			
		||||
        }
 | 
			
		||||
        stats.forEach(function (v) {
 | 
			
		||||
            v.rawCount = v.count
 | 
			
		||||
            if (divider > 0) {
 | 
			
		||||
                v.count /= divider
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return unit
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
.chart-box {
 | 
			
		||||
	height: 20em;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								web/views/@default/servers/server/stat/providers.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								web/views/@default/servers/server/stat/providers.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
.chart-box {
 | 
			
		||||
  height: 20em;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=providers.css.map */
 | 
			
		||||
							
								
								
									
										1
									
								
								web/views/@default/servers/server/stat/providers.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/views/@default/servers/server/stat/providers.css.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"sources":["providers.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"providers.css"}
 | 
			
		||||
							
								
								
									
										18
									
								
								web/views/@default/servers/server/stat/providers.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/views/@default/servers/server/stat/providers.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
{$layout}
 | 
			
		||||
 | 
			
		||||
{$var "header"}
 | 
			
		||||
<!-- echart -->
 | 
			
		||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
 | 
			
		||||
{$end}
 | 
			
		||||
 | 
			
		||||
{$template "/left_menu"}
 | 
			
		||||
<div class="right-box">
 | 
			
		||||
    {$ if eq .statIsOn false}
 | 
			
		||||
    <p class="ui message">
 | 
			
		||||
        要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
 | 
			
		||||
    </p>
 | 
			
		||||
    {$else}
 | 
			
		||||
    <h4>运营商排行</h4>
 | 
			
		||||
    <div class="chart-box" id="provider-chart"></div>
 | 
			
		||||
    {$end}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										96
									
								
								web/views/@default/servers/server/stat/providers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								web/views/@default/servers/server/stat/providers.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
    this.$delay(function () {
 | 
			
		||||
        let that = this
 | 
			
		||||
 | 
			
		||||
        let providerUnit = this.processMaxUnit(this.providerStats)
 | 
			
		||||
        this.reloadChart("provider-chart", "运营商", this.providerStats, function (v) {
 | 
			
		||||
            return v.provider.name
 | 
			
		||||
        }, function (args) {
 | 
			
		||||
            return that.providerStats[args.dataIndex].provider.name + ": " + teaweb.formatNumber(that.providerStats[args.dataIndex].rawCount)
 | 
			
		||||
        }, providerUnit)
 | 
			
		||||
        window.addEventListener("resize", function () {
 | 
			
		||||
            that.resizeChart("provider-chart")
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    this.reloadChart = function (chartId, name, stats, xFunc, tooltipFunc, unit) {
 | 
			
		||||
        let chartBox = document.getElementById(chartId)
 | 
			
		||||
        if (chartBox == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let chart = echarts.init(chartBox)
 | 
			
		||||
        let option = {
 | 
			
		||||
            xAxis: {
 | 
			
		||||
                data: stats.map(xFunc)
 | 
			
		||||
            },
 | 
			
		||||
            yAxis: {
 | 
			
		||||
                axisLabel: {
 | 
			
		||||
                    formatter: function (value) {
 | 
			
		||||
                        return value + unit
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            tooltip: {
 | 
			
		||||
                show: true,
 | 
			
		||||
                trigger: "item",
 | 
			
		||||
                formatter: tooltipFunc
 | 
			
		||||
            },
 | 
			
		||||
            grid: {
 | 
			
		||||
                left: 40,
 | 
			
		||||
                top: 10,
 | 
			
		||||
                right: 20,
 | 
			
		||||
                bottom: 20
 | 
			
		||||
            },
 | 
			
		||||
            series: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: name,
 | 
			
		||||
                    type: "bar",
 | 
			
		||||
                    data: stats.map(function (v) {
 | 
			
		||||
                        return v.count;
 | 
			
		||||
                    }),
 | 
			
		||||
                    itemStyle: {
 | 
			
		||||
                        color: "#9DD3E8"
 | 
			
		||||
                    },
 | 
			
		||||
                    barWidth: "20em"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            animation: true
 | 
			
		||||
        }
 | 
			
		||||
        chart.setOption(option)
 | 
			
		||||
        chart.resize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.resizeChart = function (chartId) {
 | 
			
		||||
        let chartBox = document.getElementById(chartId)
 | 
			
		||||
        if (chartBox == null) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        let chart = echarts.init(chartBox)
 | 
			
		||||
        chart.resize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.processMaxUnit = function (stats) {
 | 
			
		||||
        let max = stats.$map(function (k, v) {
 | 
			
		||||
            return v.count
 | 
			
		||||
        }).$max()
 | 
			
		||||
        let divider = 0
 | 
			
		||||
        let unit = ""
 | 
			
		||||
        if (max >= 1000 * 1000 * 1000) {
 | 
			
		||||
            unit = "B"
 | 
			
		||||
            divider = 1000 * 1000 * 1000
 | 
			
		||||
        } else if (max >= 1000 * 1000) {
 | 
			
		||||
            unit = "M"
 | 
			
		||||
            divider = 1000 * 1000
 | 
			
		||||
        } else if (max >= 1000) {
 | 
			
		||||
            unit = "K"
 | 
			
		||||
            divider = 1000
 | 
			
		||||
        }
 | 
			
		||||
        stats.forEach(function (v) {
 | 
			
		||||
            v.rawCount = v.count
 | 
			
		||||
            if (divider > 0) {
 | 
			
		||||
                v.count /= divider
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        return unit
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										3
									
								
								web/views/@default/servers/server/stat/providers.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/views/@default/servers/server/stat/providers.less
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
.chart-box {
 | 
			
		||||
	height: 20em;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user