From ddf549357532af6fbfa01bb9cac050989eee2cd5 Mon Sep 17 00:00:00 2001 From: GoEdgeLab Date: Mon, 25 Jan 2021 16:40:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=9C=8D=E5=8A=A1=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E7=9A=84=E6=95=B0=E6=8D=AE=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + internal/rpc/rpc_client.go | 24 + .../reverseProxy/updateSchedulingPopup.go | 8 +- .../default/servers/server/stat/clients.go | 110 ++++ .../default/servers/server/stat/index.go | 138 ++++- .../default/servers/server/stat/init.go | 2 + .../default/servers/server/stat/providers.go | 86 +++ .../servers/serverutils/server_helper.go | 32 +- .../components/server/http-stat-config-box.js | 2 +- .../components/server/http-websocket-box.js | 2 +- web/public/js/utils.js | 504 +++++++++--------- web/views/@default/dashboard/index.html | 1 - .../@default/servers/server/stat/clients.css | 4 + .../servers/server/stat/clients.css.map | 1 + .../@default/servers/server/stat/clients.html | 21 + .../@default/servers/server/stat/clients.js | 108 ++++ .../@default/servers/server/stat/clients.less | 3 + .../@default/servers/server/stat/index.css | 4 + .../servers/server/stat/index.css.map | 2 +- .../@default/servers/server/stat/index.html | 26 +- .../@default/servers/server/stat/index.js | 113 ++++ .../@default/servers/server/stat/index.less | 3 + .../servers/server/stat/providers.css | 4 + .../servers/server/stat/providers.css.map | 1 + .../servers/server/stat/providers.html | 18 + .../@default/servers/server/stat/providers.js | 96 ++++ .../servers/server/stat/providers.less | 3 + 27 files changed, 1047 insertions(+), 272 deletions(-) create mode 100644 internal/web/actions/default/servers/server/stat/clients.go create mode 100644 internal/web/actions/default/servers/server/stat/providers.go create mode 100644 web/views/@default/servers/server/stat/clients.css create mode 100644 web/views/@default/servers/server/stat/clients.css.map create mode 100644 web/views/@default/servers/server/stat/clients.html create mode 100644 web/views/@default/servers/server/stat/clients.js create mode 100644 web/views/@default/servers/server/stat/clients.less create mode 100644 web/views/@default/servers/server/stat/index.js create mode 100644 web/views/@default/servers/server/stat/providers.css create mode 100644 web/views/@default/servers/server/stat/providers.css.map create mode 100644 web/views/@default/servers/server/stat/providers.html create mode 100644 web/views/@default/servers/server/stat/providers.js create mode 100644 web/views/@default/servers/server/stat/providers.less diff --git a/README.md b/README.md index 5d103c97..a7148e89 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,6 @@ ## 联系我们 有什么问题和建议都可以加入QQ群 `659832182`。 + +## 感谢 +* 感谢[JetBrains公司](https://www.jetbrains.com/)提供免费的IDE开发Licence。 \ No newline at end of file diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go index 756e4262..8a568a5a 100644 --- a/internal/rpc/rpc_client.go +++ b/internal/rpc/rpc_client.go @@ -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()) } diff --git a/internal/web/actions/default/servers/server/settings/reverseProxy/updateSchedulingPopup.go b/internal/web/actions/default/servers/server/settings/reverseProxy/updateSchedulingPopup.go index 9d756239..6b698b24 100644 --- a/internal/web/actions/default/servers/server/settings/reverseProxy/updateSchedulingPopup.go +++ b/internal/web/actions/default/servers/server/settings/reverseProxy/updateSchedulingPopup.go @@ -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) } } diff --git a/internal/web/actions/default/servers/server/stat/clients.go b/internal/web/actions/default/servers/server/stat/clients.go new file mode 100644 index 00000000..1983d9bd --- /dev/null +++ b/internal/web/actions/default/servers/server/stat/clients.go @@ -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() +} diff --git a/internal/web/actions/default/servers/server/stat/index.go b/internal/web/actions/default/servers/server/stat/index.go index dcfe054e..8328404f 100644 --- a/internal/web/actions/default/servers/server/stat/index.go +++ b/internal/web/actions/default/servers/server/stat/index.go @@ -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() } diff --git a/internal/web/actions/default/servers/server/stat/init.go b/internal/web/actions/default/servers/server/stat/init.go index b5e75735..d1f5942c 100644 --- a/internal/web/actions/default/servers/server/stat/init.go +++ b/internal/web/actions/default/servers/server/stat/init.go @@ -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() }) } diff --git a/internal/web/actions/default/servers/server/stat/providers.go b/internal/web/actions/default/servers/server/stat/providers.go new file mode 100644 index 00000000..55a11db6 --- /dev/null +++ b/internal/web/actions/default/servers/server/stat/providers.go @@ -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() +} diff --git a/internal/web/actions/default/servers/serverutils/server_helper.go b/internal/web/actions/default/servers/serverutils/server_helper.go index 7d8aa1d2..4fb81990 100644 --- a/internal/web/actions/default/servers/serverutils/server_helper.go +++ b/internal/web/actions/default/servers/serverutils/server_helper.go @@ -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, diff --git a/web/public/js/components/server/http-stat-config-box.js b/web/public/js/components/server/http-stat-config-box.js index f6115a91..919ece2b 100644 --- a/web/public/js/components/server/http-stat-config-box.js +++ b/web/public/js/components/server/http-stat-config-box.js @@ -5,7 +5,7 @@ Vue.component("http-stat-config-box", { if (stat == null) { stat = { isPrior: false, - isOn: true + isOn: false } } return { diff --git a/web/public/js/components/server/http-websocket-box.js b/web/public/js/components/server/http-websocket-box.js index ef6591a5..6f04cdfa 100644 --- a/web/public/js/components/server/http-websocket-box.js +++ b/web/public/js/components/server/http-websocket-box.js @@ -14,7 +14,7 @@ Vue.component("http-websocket-box", { if (websocketConfig == null) { websocketConfig = { id: 0, - isOn: true, + isOn: false, handshakeTimeout: { count: 30, unit: "second" diff --git a/web/public/js/utils.js b/web/public/js/utils.js index 8a948440..b9497e64 100644 --- a/web/public/js/utils.js +++ b/web/public/js/utils.js @@ -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(?', + 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: '' + html + "", + 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: '', - 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: '' + html + "", - 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() + } }; diff --git a/web/views/@default/dashboard/index.html b/web/views/@default/dashboard/index.html index 976afbea..6dd1205b 100644 --- a/web/views/@default/dashboard/index.html +++ b/web/views/@default/dashboard/index.html @@ -1,6 +1,5 @@ {$layout} - {$var "header"} diff --git a/web/views/@default/servers/server/stat/clients.css b/web/views/@default/servers/server/stat/clients.css new file mode 100644 index 00000000..933bb2da --- /dev/null +++ b/web/views/@default/servers/server/stat/clients.css @@ -0,0 +1,4 @@ +.chart-box { + height: 20em; +} +/*# sourceMappingURL=clients.css.map */ \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/clients.css.map b/web/views/@default/servers/server/stat/clients.css.map new file mode 100644 index 00000000..3b35a86a --- /dev/null +++ b/web/views/@default/servers/server/stat/clients.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["clients.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"clients.css"} \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/clients.html b/web/views/@default/servers/server/stat/clients.html new file mode 100644 index 00000000..79d934e7 --- /dev/null +++ b/web/views/@default/servers/server/stat/clients.html @@ -0,0 +1,21 @@ +{$layout} + +{$var "header"} + + +{$end} + +{$template "/left_menu"} +
+ {$ if eq .statIsOn false} +

+ 要想查看统计数据,需要先开启统计功能,[点击这里]修改配置。 +

+ {$else} +

操作系统排行

+
+ +

浏览器排行

+
+ {$end} +
\ No newline at end of file diff --git a/web/views/@default/servers/server/stat/clients.js b/web/views/@default/servers/server/stat/clients.js new file mode 100644 index 00000000..ba8c1827 --- /dev/null +++ b/web/views/@default/servers/server/stat/clients.js @@ -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 + } +}) diff --git a/web/views/@default/servers/server/stat/clients.less b/web/views/@default/servers/server/stat/clients.less new file mode 100644 index 00000000..bed0b0ae --- /dev/null +++ b/web/views/@default/servers/server/stat/clients.less @@ -0,0 +1,3 @@ +.chart-box { + height: 20em; +} \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/index.css b/web/views/@default/servers/server/stat/index.css index e69de29b..3007cba5 100644 --- a/web/views/@default/servers/server/stat/index.css +++ b/web/views/@default/servers/server/stat/index.css @@ -0,0 +1,4 @@ +.chart-box { + height: 20em; +} +/*# sourceMappingURL=index.css.map */ \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/index.css.map b/web/views/@default/servers/server/stat/index.css.map index 66dc9051..8c66954e 100644 --- a/web/views/@default/servers/server/stat/index.css.map +++ b/web/views/@default/servers/server/stat/index.css.map @@ -1 +1 @@ -undefined \ No newline at end of file +{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"index.css"} \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/index.html b/web/views/@default/servers/server/stat/index.html index cc594e36..4116bc50 100644 --- a/web/views/@default/servers/server/stat/index.html +++ b/web/views/@default/servers/server/stat/index.html @@ -1,6 +1,30 @@ {$layout} +{$var "header"} + + +{$end} + {$template "/left_menu"}
-
此功能暂未开放,敬请期待。
+ {$ if eq .statIsOn false} +

+ 要想查看统计数据,需要先开启统计功能,[点击这里]修改配置。 +

+ {$else} +

地区排行

+
+ +
+ +

省市排行

+
+ +
+ +

城市排行

+
+ +
+ {$end}
\ No newline at end of file diff --git a/web/views/@default/servers/server/stat/index.js b/web/views/@default/servers/server/stat/index.js new file mode 100644 index 00000000..46a05a68 --- /dev/null +++ b/web/views/@default/servers/server/stat/index.js @@ -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 + } +}) diff --git a/web/views/@default/servers/server/stat/index.less b/web/views/@default/servers/server/stat/index.less index e69de29b..bed0b0ae 100644 --- a/web/views/@default/servers/server/stat/index.less +++ b/web/views/@default/servers/server/stat/index.less @@ -0,0 +1,3 @@ +.chart-box { + height: 20em; +} \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/providers.css b/web/views/@default/servers/server/stat/providers.css new file mode 100644 index 00000000..6bd265a1 --- /dev/null +++ b/web/views/@default/servers/server/stat/providers.css @@ -0,0 +1,4 @@ +.chart-box { + height: 20em; +} +/*# sourceMappingURL=providers.css.map */ \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/providers.css.map b/web/views/@default/servers/server/stat/providers.css.map new file mode 100644 index 00000000..8f48bbe0 --- /dev/null +++ b/web/views/@default/servers/server/stat/providers.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["providers.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"providers.css"} \ No newline at end of file diff --git a/web/views/@default/servers/server/stat/providers.html b/web/views/@default/servers/server/stat/providers.html new file mode 100644 index 00000000..fef3db9a --- /dev/null +++ b/web/views/@default/servers/server/stat/providers.html @@ -0,0 +1,18 @@ +{$layout} + +{$var "header"} + + +{$end} + +{$template "/left_menu"} +
+ {$ if eq .statIsOn false} +

+ 要想查看统计数据,需要先开启统计功能,[点击这里]修改配置。 +

+ {$else} +

运营商排行

+
+ {$end} +
\ No newline at end of file diff --git a/web/views/@default/servers/server/stat/providers.js b/web/views/@default/servers/server/stat/providers.js new file mode 100644 index 00000000..e1668060 --- /dev/null +++ b/web/views/@default/servers/server/stat/providers.js @@ -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 + } +}) diff --git a/web/views/@default/servers/server/stat/providers.less b/web/views/@default/servers/server/stat/providers.less new file mode 100644 index 00000000..bed0b0ae --- /dev/null +++ b/web/views/@default/servers/server/stat/providers.less @@ -0,0 +1,3 @@ +.chart-box { + height: 20em; +} \ No newline at end of file