mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-27 05:00:25 +08:00
增加服务流量统计
This commit is contained in:
@@ -131,6 +131,10 @@ func (this *RPCClient) ServerHTTPFirewallDailyStatRPC() pb.ServerHTTPFirewallDai
|
||||
return pb.NewServerHTTPFirewallDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
|
||||
return pb.NewServerDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
|
||||
return pb.NewServerGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
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"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -20,140 +20,34 @@ func (this *IndexAction) Init() {
|
||||
|
||||
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)
|
||||
{
|
||||
resp, err := this.RPC().ServerDailyStatRPC().FindServerHourlyStats(this.AdminContext(), &pb.FindServerHourlyStatsRequest{
|
||||
ServerId: params.ServerId,
|
||||
Hours: 24,
|
||||
})
|
||||
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,
|
||||
sort.Slice(resp.Stats, func(i, j int) bool {
|
||||
stat1 := resp.Stats[i]
|
||||
stat2 := resp.Stats[j]
|
||||
return stat1.Hour < stat2.Hour
|
||||
})
|
||||
statMaps := []maps.Map{}
|
||||
for _, stat := range resp.Stats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"day": stat.Hour[:4] + "-" + stat.Hour[4:6] + "-" + stat.Hour[6:8],
|
||||
"hour": stat.Hour[8:],
|
||||
"bytes": stat.Bytes,
|
||||
"cachedBytes": stat.CachedBytes,
|
||||
"countRequests": stat.CountRequests,
|
||||
"countCachedRequests": stat.CountCachedRequests,
|
||||
})
|
||||
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
|
||||
|
||||
// 记录最近使用
|
||||
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
|
||||
ItemType: "server",
|
||||
ItemId: params.ServerId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
this.Data["hourlyStats"] = statMaps
|
||||
}
|
||||
|
||||
this.Show()
|
||||
|
||||
@@ -14,6 +14,7 @@ func init() {
|
||||
Helper(serverutils.NewServerHelper()).
|
||||
Prefix("/servers/server/stat").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/regions", new(RegionsAction)).
|
||||
Get("/providers", new(ProvidersAction)).
|
||||
Get("/clients", new(ClientsAction)).
|
||||
Get("/waf", new(WafAction)).
|
||||
|
||||
160
internal/web/actions/default/servers/server/stat/regions.go
Normal file
160
internal/web/actions/default/servers/server/stat/regions.go
Normal file
@@ -0,0 +1,160 @@
|
||||
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 RegionsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RegionsAction) Init() {
|
||||
this.Nav("", "stat", "")
|
||||
this.SecondMenu("region")
|
||||
}
|
||||
|
||||
func (this *RegionsAction) 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
|
||||
|
||||
// 记录最近使用
|
||||
_, err = this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
|
||||
ItemType: "server",
|
||||
ItemId: params.ServerId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -159,10 +159,15 @@ 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/regions?serverId=" + serverIdString,
|
||||
"isActive": secondMenuItem == "region",
|
||||
})
|
||||
menuItems = append(menuItems, maps.Map{
|
||||
"name": "运营商",
|
||||
"url": "/servers/server/stat/providers?serverId=" + serverIdString,
|
||||
|
||||
@@ -1,302 +1,352 @@
|
||||
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;
|
||||
},
|
||||
|
||||
loadJS: function (file, callback) {
|
||||
let element = document.createElement("script")
|
||||
element.setAttribute("type", "text/javascript")
|
||||
element.setAttribute("src", file)
|
||||
if (typeof callback == "function") {
|
||||
element.addEventListener("load", callback)
|
||||
}
|
||||
document.head.append(element)
|
||||
},
|
||||
loadCSS: function (file, callback) {
|
||||
let element = document.createElement("link")
|
||||
element.setAttribute("rel", "stylesheet")
|
||||
element.setAttribute("type", "text/css")
|
||||
element.setAttribute("href", file)
|
||||
if (typeof callback == "function") {
|
||||
element.addEventListener("load", callback)
|
||||
}
|
||||
document.head.append(element)
|
||||
},
|
||||
datepicker: function (element, callback) {
|
||||
// 加载
|
||||
if (typeof Pikaday == "undefined") {
|
||||
let that = this
|
||||
this.loadJS("/js/moment.min.js")
|
||||
this.loadJS("/js/pikaday.js", function () {
|
||||
that.datepicker(element, callback)
|
||||
})
|
||||
this.loadCSS("/js/pikaday.css")
|
||||
this.loadCSS("/js/pikaday.theme.css")
|
||||
this.loadCSS("/js/pikaday.triangle.css")
|
||||
loadJS: function (file, callback) {
|
||||
let element = document.createElement("script")
|
||||
element.setAttribute("type", "text/javascript")
|
||||
element.setAttribute("src", file)
|
||||
if (typeof callback == "function") {
|
||||
element.addEventListener("load", callback)
|
||||
}
|
||||
document.head.append(element)
|
||||
},
|
||||
loadCSS: function (file, callback) {
|
||||
let element = document.createElement("link")
|
||||
element.setAttribute("rel", "stylesheet")
|
||||
element.setAttribute("type", "text/css")
|
||||
element.setAttribute("href", file)
|
||||
if (typeof callback == "function") {
|
||||
element.addEventListener("load", callback)
|
||||
}
|
||||
document.head.append(element)
|
||||
},
|
||||
datepicker: function (element, callback) {
|
||||
// 加载
|
||||
if (typeof Pikaday == "undefined") {
|
||||
let that = this
|
||||
this.loadJS("/js/moment.min.js")
|
||||
this.loadJS("/js/pikaday.js", function () {
|
||||
that.datepicker(element, callback)
|
||||
})
|
||||
this.loadCSS("/js/pikaday.css")
|
||||
this.loadCSS("/js/pikaday.theme.css")
|
||||
this.loadCSS("/js/pikaday.triangle.css")
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
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";
|
||||
},
|
||||
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();
|
||||
};
|
||||
formatBytes: function (bytes) {
|
||||
bytes = Math.ceil(bytes);
|
||||
if (bytes < 1024) {
|
||||
return bytes + "B";
|
||||
}
|
||||
if (bytes < 1024 * 1024) {
|
||||
return (Math.ceil(bytes * 100 / 1024) / 100) + "K";
|
||||
}
|
||||
if (bytes < 1024 * 1024 * 1024) {
|
||||
return (Math.ceil(bytes * 100 / 1024 / 1024) / 100) + "M";
|
||||
}
|
||||
if (bytes < 1024 * 1024 * 1024 * 1024) {
|
||||
return (Math.ceil(bytes * 100 / 1024 / 1024 / 1024) / 100) + "G";
|
||||
}
|
||||
if (bytes < 1024 * 1024 * 1024 * 1024 * 1024) {
|
||||
return (Math.ceil(bytes * 100 / 1024 / 1024 / 1024 / 1024) / 100) + "T";
|
||||
}
|
||||
return (Math.ceil(bytes * 100 / 1024 / 1024 / 1024 / 1024 / 1024) / 100) + "P";
|
||||
},
|
||||
formatNumber: function (x) {
|
||||
return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ", ");
|
||||
},
|
||||
bytesAxis: function (stats, countFunc) {
|
||||
let max = Math.max.apply(this, stats.map(countFunc))
|
||||
let divider = 1
|
||||
let unit = ""
|
||||
if (max >= 1024 * 1024 * 1024 * 1024) {
|
||||
unit = "T"
|
||||
divider = 1024 * 1024 * 1024 * 1024
|
||||
} else if (max >= 1024 * 1024 * 1024) {
|
||||
unit = "G"
|
||||
divider = 1024 * 1024 * 1024
|
||||
} else if (max >= 1024 * 1024) {
|
||||
unit = "M"
|
||||
divider = 1024 * 1024
|
||||
} else if (max >= 1024) {
|
||||
unit = "K"
|
||||
divider = 1024
|
||||
}
|
||||
return {
|
||||
unit: unit,
|
||||
divider: divider
|
||||
}
|
||||
},
|
||||
countAxis: function (stats, countFunc) {
|
||||
let max = Math.max.apply(this, stats.map(countFunc))
|
||||
let divider = 1
|
||||
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
|
||||
}
|
||||
return {
|
||||
unit: unit,
|
||||
divider: divider
|
||||
}
|
||||
},
|
||||
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");
|
||||
},
|
||||
closePopup: function () {
|
||||
if (this.isPopup()) {
|
||||
window.parent.Swal.close();
|
||||
}
|
||||
},
|
||||
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");
|
||||
},
|
||||
closePopup: function () {
|
||||
if (this.isPopup()) {
|
||||
window.parent.Swal.close();
|
||||
}
|
||||
},
|
||||
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,4 +1,4 @@
|
||||
.chart-box {
|
||||
height: 20em;
|
||||
height: 21em;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
@@ -7,24 +7,11 @@
|
||||
|
||||
{$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="country-chart">
|
||||
<div class="ui column">
|
||||
<h4>请求数统计(小时)</h4>
|
||||
<div class="chart-box" id="hourly-requests-chart"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<h4>省市排行</h4>
|
||||
<div class="chart-box" id="province-chart">
|
||||
|
||||
</div>
|
||||
|
||||
<h4>城市排行</h4>
|
||||
<div class="chart-box" id="city-chart">
|
||||
|
||||
</div>
|
||||
{$end}
|
||||
<h4>流量统计(小时)</h4>
|
||||
<div class="chart-box" id="hourly-traffic-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,116 +1,195 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
let that = this
|
||||
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)
|
||||
this.reloadRequestsChart("hourly-requests-chart", "请求数统计", this.hourlyStats, function (args) {
|
||||
if (args.seriesIndex == 0) {
|
||||
return that.hourlyStats[args.dataIndex].day + " " + that.hourlyStats[args.dataIndex].hour + " 请求数: " + teaweb.formatNumber(that.hourlyStats[args.dataIndex].countRequests)
|
||||
}
|
||||
if (args.seriesIndex == 1) {
|
||||
let ratio = 0
|
||||
if (that.hourlyStats[args.dataIndex].countRequests > 0) {
|
||||
ratio = Math.round(that.hourlyStats[args.dataIndex].countCachedRequests * 10000 / that.hourlyStats[args.dataIndex].countRequests) / 100
|
||||
}
|
||||
return that.hourlyStats[args.dataIndex].day + " " + that.hourlyStats[args.dataIndex].hour + " 缓存请求数: " + teaweb.formatNumber(that.hourlyStats[args.dataIndex].countCachedRequests) + ", 命中率:" + ratio + "%"
|
||||
}
|
||||
return ""
|
||||
})
|
||||
this.reloadTrafficChart("hourly-traffic-chart", "流量统计", this.hourlyStats, function (args) {
|
||||
if (args.seriesIndex == 0) {
|
||||
return that.hourlyStats[args.dataIndex].day + " " + that.hourlyStats[args.dataIndex].hour + " 流量: " + teaweb.formatBytes(that.hourlyStats[args.dataIndex].bytes)
|
||||
}
|
||||
if (args.seriesIndex == 1) {
|
||||
let ratio = 0
|
||||
if (that.hourlyStats[args.dataIndex].bytes > 0) {
|
||||
ratio = Math.round(that.hourlyStats[args.dataIndex].cachedBytes * 10000 / that.hourlyStats[args.dataIndex].bytes) / 100
|
||||
}
|
||||
return that.hourlyStats[args.dataIndex].day + " " + that.hourlyStats[args.dataIndex].hour + " 缓存流量: " + teaweb.formatBytes(that.hourlyStats[args.dataIndex].cachedBytes) + ", 命中率:" + ratio + "%"
|
||||
}
|
||||
return ""
|
||||
})
|
||||
window.addEventListener("resize", function () {
|
||||
that.resizeChart("hourly-requests-chart")
|
||||
that.resizeChart("hourly-traffic-chart")
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
this.reloadRequestsChart = function (chartId, name, stats, tooltipFunc) {
|
||||
let chartBox = document.getElementById(chartId)
|
||||
if (chartBox == null) {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
let axis = teaweb.countAxis(stats, function (v) {
|
||||
return Math.max(v.countRequests, v.countCachedRequests)
|
||||
})
|
||||
|
||||
window.addEventListener("resize", function () {
|
||||
that.resizeChart("country-chart")
|
||||
that.resizeChart("province-chart")
|
||||
that.resizeChart("city-chart")
|
||||
})
|
||||
})
|
||||
let chart = echarts.init(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(function (v) {
|
||||
return v.hour
|
||||
}),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
return value + axis.unit
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: tooltipFunc
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
top: 40,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "请求数",
|
||||
type: "line",
|
||||
data: stats.map(function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
}),
|
||||
itemStyle: {
|
||||
color: "#9DD3E8"
|
||||
},
|
||||
areaStyle: {
|
||||
color: "#9DD3E8"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "缓存请求数",
|
||||
type: "line",
|
||||
data: stats.map(function (v) {
|
||||
return v.countCachedRequests / axis.divider
|
||||
}),
|
||||
itemStyle: {
|
||||
color: "#61A0A8"
|
||||
},
|
||||
areaStyle: {
|
||||
color: "#61A0A8"
|
||||
}
|
||||
}
|
||||
],
|
||||
legend: {
|
||||
data: ['请求数', '缓存请求数']
|
||||
},
|
||||
animation: true
|
||||
}
|
||||
chart.setOption(option)
|
||||
chart.resize()
|
||||
}
|
||||
|
||||
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),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
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.reloadTrafficChart = function (chartId, name, stats, tooltipFunc) {
|
||||
let chartBox = document.getElementById(chartId)
|
||||
if (chartBox == null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.resizeChart = function (chartId) {
|
||||
let chartBox = document.getElementById(chartId)
|
||||
if (chartBox == null) {
|
||||
return
|
||||
}
|
||||
let chart = echarts.init(chartBox)
|
||||
chart.resize()
|
||||
}
|
||||
let axis = teaweb.bytesAxis(stats, function (v) {
|
||||
return Math.max(v.bytes, v.cachedBytes)
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
let chart = echarts.init(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(function (v) {
|
||||
return v.hour
|
||||
}),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
return value + axis.unit
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: tooltipFunc
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
top: 40,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "流量",
|
||||
type: "line",
|
||||
data: stats.map(function (v) {
|
||||
return v.bytes / axis.divider
|
||||
}),
|
||||
itemStyle: {
|
||||
color: "#9DD3E8"
|
||||
},
|
||||
areaStyle: {
|
||||
color: "#9DD3E8"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "缓存流量",
|
||||
type: "line",
|
||||
data: stats.map(function (v) {
|
||||
return v.cachedBytes / axis.divider
|
||||
}),
|
||||
itemStyle: {
|
||||
color: "#61A0A8"
|
||||
},
|
||||
areaStyle: {
|
||||
color: "#61A0A8"
|
||||
}
|
||||
}
|
||||
],
|
||||
legend: {
|
||||
data: ['流量', '缓存流量']
|
||||
},
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.chart-box {
|
||||
height: 20em;
|
||||
height: 21em;
|
||||
}
|
||||
@@ -1,99 +1,79 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
let that = this
|
||||
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")
|
||||
})
|
||||
})
|
||||
let axis = teaweb.countAxis(this.providerStats, function (v) {
|
||||
return v.count
|
||||
})
|
||||
this.providerStats.forEach(function (v) {
|
||||
v.count /= axis.divider
|
||||
})
|
||||
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)
|
||||
}, axis.unit)
|
||||
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),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
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.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),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
this.resizeChart = function (chartId) {
|
||||
let chartBox = document.getElementById(chartId)
|
||||
if (chartBox == null) {
|
||||
return
|
||||
}
|
||||
let chart = echarts.init(chartBox)
|
||||
chart.resize()
|
||||
}
|
||||
})
|
||||
|
||||
4
web/views/@default/servers/server/stat/regions.css
Normal file
4
web/views/@default/servers/server/stat/regions.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.chart-box {
|
||||
height: 20em;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
1
web/views/@default/servers/server/stat/regions.css.map
Normal file
1
web/views/@default/servers/server/stat/regions.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"index.css"}
|
||||
30
web/views/@default/servers/server/stat/regions.html
Normal file
30
web/views/@default/servers/server/stat/regions.html
Normal file
@@ -0,0 +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">
|
||||
{$ 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>
|
||||
116
web/views/@default/servers/server/stat/regions.js
Normal file
116
web/views/@default/servers/server/stat/regions.js
Normal file
@@ -0,0 +1,116 @@
|
||||
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),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
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/regions.less
Normal file
3
web/views/@default/servers/server/stat/regions.less
Normal file
@@ -0,0 +1,3 @@
|
||||
.chart-box {
|
||||
height: 20em;
|
||||
}
|
||||
Reference in New Issue
Block a user