diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..21efdc0b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*_plus.go \ No newline at end of file diff --git a/internal/web/actions/default/dashboard/boards/dns.go b/internal/web/actions/default/dashboard/boards/dns.go new file mode 100644 index 00000000..e1b48a29 --- /dev/null +++ b/internal/web/actions/default/dashboard/boards/dns.go @@ -0,0 +1,17 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package boards + +import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + +type DnsAction struct { + actionutils.ParentAction +} + +func (this *DnsAction) Init() { + this.Nav("", "", "dns") +} + +func (this *DnsAction) RunGet(params struct{}) { + this.Show() +} diff --git a/internal/web/actions/default/dashboard/boards/index.go b/internal/web/actions/default/dashboard/boards/index.go new file mode 100644 index 00000000..94c36d6a --- /dev/null +++ b/internal/web/actions/default/dashboard/boards/index.go @@ -0,0 +1,171 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package boards + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" + "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + "regexp" +) + +type IndexAction struct { + actionutils.ParentAction +} + +func (this *IndexAction) Init() { + this.Nav("", "", "index") +} + +func (this *IndexAction) RunGet(params struct{}) { + if !teaconst.IsPlus { + this.RedirectURL("/dashboard") + return + } + + // 取得用户的权限 + module, ok := configloaders.FindFirstAdminModule(this.AdminId()) + if ok { + if module != "dashboard" { + for _, m := range configloaders.AllModuleMaps() { + if m.GetString("code") == module { + this.RedirectURL(m.GetString("url")) + return + } + } + } + } + + // 读取看板数据 + resp, err := this.RPC().AdminRPC().ComposeAdminDashboard(this.AdminContext(), &pb.ComposeAdminDashboardRequest{}) + if err != nil { + this.ErrorPage(err) + return + } + this.Data["dashboard"] = maps.Map{ + "countServers": resp.CountServers, + "countNodeClusters": resp.CountNodeClusters, + "countNodes": resp.CountNodes, + "countUsers": resp.CountUsers, + "countAPINodes": resp.CountAPINodes, + "countDBNodes": resp.CountDBNodes, + "countUserNodes": resp.CountUserNodes, + + "canGoServers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeServer), + "canGoNodes": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeNode), + "canGoSettings": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeSetting), + "canGoUsers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeUser), + } + + // 今日流量 + todayTrafficBytes := int64(0) + if len(resp.DailyTrafficStats) > 0 { + todayTrafficBytes = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-1].Bytes + } + todayTrafficString := numberutils.FormatBytes(todayTrafficBytes) + result := regexp.MustCompile(`^(?U)(.+)([a-zA-Z]+)$`).FindStringSubmatch(todayTrafficString) + if len(result) > 2 { + this.Data["todayTraffic"] = result[1] + this.Data["todayTrafficUnit"] = result[2] + } else { + this.Data["todayTraffic"] = todayTrafficString + this.Data["todayTrafficUnit"] = "" + } + + // 24小时流量趋势 + { + statMaps := []maps.Map{} + for _, stat := range resp.HourlyTrafficStats { + statMaps = append(statMaps, maps.Map{ + "bytes": stat.Bytes, + "hour": stat.Hour[8:], + }) + } + this.Data["hourlyTrafficStats"] = statMaps + } + + // 15天流量趋势 + { + statMaps := []maps.Map{} + for _, stat := range resp.DailyTrafficStats { + statMaps = append(statMaps, maps.Map{ + "bytes": stat.Bytes, + "day": stat.Day[4:6] + "月" + stat.Day[6:] + "日", + }) + } + this.Data["dailyTrafficStats"] = statMaps + } + + // 版本升级 + if resp.NodeUpgradeInfo != nil { + this.Data["nodeUpgradeInfo"] = maps.Map{ + "count": resp.NodeUpgradeInfo.CountNodes, + "version": resp.NodeUpgradeInfo.NewVersion, + } + } else { + this.Data["nodeUpgradeInfo"] = maps.Map{ + "count": 0, + "version": "", + } + } + if resp.MonitorNodeUpgradeInfo != nil { + this.Data["monitorNodeUpgradeInfo"] = maps.Map{ + "count": resp.MonitorNodeUpgradeInfo.CountNodes, + "version": resp.MonitorNodeUpgradeInfo.NewVersion, + } + } else { + this.Data["monitorNodeUpgradeInfo"] = maps.Map{ + "count": 0, + "version": "", + } + } + if resp.ApiNodeUpgradeInfo != nil { + this.Data["apiNodeUpgradeInfo"] = maps.Map{ + "count": resp.ApiNodeUpgradeInfo.CountNodes, + "version": resp.ApiNodeUpgradeInfo.NewVersion, + } + } else { + this.Data["apiNodeUpgradeInfo"] = maps.Map{ + "count": 0, + "version": "", + } + } + if resp.UserNodeUpgradeInfo != nil { + this.Data["userNodeUpgradeInfo"] = maps.Map{ + "count": resp.UserNodeUpgradeInfo.CountNodes, + "version": resp.UserNodeUpgradeInfo.NewVersion, + } + } else { + this.Data["userNodeUpgradeInfo"] = maps.Map{ + "count": 0, + "version": 0, + } + } + if resp.AuthorityNodeUpgradeInfo != nil { + this.Data["authorityNodeUpgradeInfo"] = maps.Map{ + "count": resp.AuthorityNodeUpgradeInfo.CountNodes, + "version": resp.AuthorityNodeUpgradeInfo.NewVersion, + } + } else { + this.Data["authorityNodeUpgradeInfo"] = maps.Map{ + "count": 0, + "version": "", + } + } + if resp.NsNodeUpgradeInfo != nil { + this.Data["nsNodeUpgradeInfo"] = maps.Map{ + "count": resp.NsNodeUpgradeInfo.CountNodes, + "version": resp.NsNodeUpgradeInfo.NewVersion, + } + } else { + this.Data["nsNodeUpgradeInfo"] = maps.Map{ + "count": 0, + "version": "", + } + } + + this.Show() +} diff --git a/internal/web/actions/default/dashboard/boards/user.go b/internal/web/actions/default/dashboard/boards/user.go new file mode 100644 index 00000000..d990126f --- /dev/null +++ b/internal/web/actions/default/dashboard/boards/user.go @@ -0,0 +1,97 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package boards + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" + "github.com/iwind/TeaGo/types" + timeutil "github.com/iwind/TeaGo/utils/time" +) + +type UserAction struct { + actionutils.ParentAction +} + +func (this *UserAction) Init() { + this.Nav("", "", "user") +} + +func (this *UserAction) RunGet(params struct{}) { + resp, err := this.RPC().UserRPC().ComposeUserGlobalBoard(this.AdminContext(), &pb.ComposeUserGlobalBoardRequest{}) + if err != nil { + this.ErrorPage(err) + return + } + this.Data["board"] = maps.Map{ + "totalUsers": resp.TotalUsers, + "countTodayUsers": resp.CountTodayUsers, + "countWeeklyUsers": resp.CountWeeklyUsers, + "countUserNodes": resp.CountUserNodes, + "countOfflineUserNodes": resp.CountOfflineUserNodes, + } + + { + statMaps := []maps.Map{} + for _, stat := range resp.DailyStats { + statMaps = append(statMaps, maps.Map{ + "day": stat.Day, + "count": stat.Count, + }) + } + this.Data["dailyStats"] = statMaps + } + + // CPU + { + var statMaps = []maps.Map{} + for _, stat := range resp.CpuNodeValues { + statMaps = append(statMaps, maps.Map{ + "time": timeutil.FormatTime("H:i", stat.CreatedAt), + "value": types.Float32(string(stat.ValueJSON)), + }) + } + this.Data["cpuValues"] = statMaps + } + + // Memory + { + var statMaps = []maps.Map{} + for _, stat := range resp.MemoryNodeValues { + statMaps = append(statMaps, maps.Map{ + "time": timeutil.FormatTime("H:i", stat.CreatedAt), + "value": types.Float32(string(stat.ValueJSON)), + }) + } + this.Data["memoryValues"] = statMaps + } + + // Load + { + var statMaps = []maps.Map{} + for _, stat := range resp.LoadNodeValues { + statMaps = append(statMaps, maps.Map{ + "time": timeutil.FormatTime("H:i", stat.CreatedAt), + "value": types.Float32(string(stat.ValueJSON)), + }) + } + this.Data["loadValues"] = statMaps + } + + // 流量排行 + { + var statMaps = []maps.Map{} + for _, stat := range resp.TopTrafficStats { + statMaps = append(statMaps, maps.Map{ + "userId": stat.UserId, + "userName": stat.UserName, + "countRequests": stat.CountRequests, + "bytes": stat.Bytes, + }) + } + this.Data["topTrafficStats"] = statMaps + } + + this.Show() +} diff --git a/internal/web/actions/default/dashboard/boards/waf.go b/internal/web/actions/default/dashboard/boards/waf.go new file mode 100644 index 00000000..c9911241 --- /dev/null +++ b/internal/web/actions/default/dashboard/boards/waf.go @@ -0,0 +1,17 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package boards + +import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + +type WafAction struct { + actionutils.ParentAction +} + +func (this *WafAction) Init() { + this.Nav("", "", "waf") +} + +func (this *WafAction) RunGet(params struct{}) { + this.Show() +} diff --git a/internal/web/actions/default/dashboard/index.go b/internal/web/actions/default/dashboard/index.go index 345082ed..c3c59ac4 100644 --- a/internal/web/actions/default/dashboard/index.go +++ b/internal/web/actions/default/dashboard/index.go @@ -1,7 +1,10 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + package dashboard import ( "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" @@ -18,6 +21,11 @@ func (this *IndexAction) Init() { } func (this *IndexAction) RunGet(params struct{}) { + if teaconst.IsPlus { + this.RedirectURL("/dashboard/boards") + return + } + // 取得用户的权限 module, ok := configloaders.FindFirstAdminModule(this.AdminId()) if ok { diff --git a/internal/web/actions/default/dashboard/init.go b/internal/web/actions/default/dashboard/init.go index 23b5de30..07ef2959 100644 --- a/internal/web/actions/default/dashboard/init.go +++ b/internal/web/actions/default/dashboard/init.go @@ -2,6 +2,7 @@ package dashboard import ( "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards" "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" "github.com/iwind/TeaGo" ) @@ -12,6 +13,14 @@ func init() { Data("teaMenu", "dashboard"). Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)). GetPost("", new(IndexAction)). + + // 看板 + Prefix("/dashboard/boards"). + Get("", new(boards.IndexAction)). + Get("/waf", new(boards.WafAction)). + Get("/dns", new(boards.DnsAction)). + Get("/user", new(boards.UserAction)). + EndAll() }) } diff --git a/web/public/js/utils.js b/web/public/js/utils.js index 30990069..51fb2393 100644 --- a/web/public/js/utils.js +++ b/web/public/js/utils.js @@ -172,7 +172,8 @@ window.teaweb = { } return { unit: unit, - divider: divider + divider: divider, + max: max } }, popup: function (url, options) { diff --git a/web/views/@default/dashboard/boards/@menu.html b/web/views/@default/dashboard/boards/@menu.html new file mode 100644 index 00000000..92a6861f --- /dev/null +++ b/web/views/@default/dashboard/boards/@menu.html @@ -0,0 +1,6 @@ + + 概况 + WAF + DNS + 用户 + \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/dns.css b/web/views/@default/dashboard/boards/dns.css new file mode 100644 index 00000000..27ad787d --- /dev/null +++ b/web/views/@default/dashboard/boards/dns.css @@ -0,0 +1,33 @@ +.ui.message .icon { + position: absolute; + right: 1em; + top: 1.8em; +} +.grid { + margin-top: 2em !important; + margin-left: 2em !important; +} +.grid .column { + margin-bottom: 2em; + border-right: 1px #eee solid; +} +.grid .column div.value { + margin-top: 1.5em; +} +.grid .column div.value span { + font-size: 2em; + margin-right: 0.2em; +} +.grid .column.no-border { + border-right: 0; +} +.grid h4 a { + display: none; +} +.grid .column:hover a { + display: inline; +} +.chart-box { + height: 20em; +} +/*# sourceMappingURL=dns.css.map */ \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/dns.css.map b/web/views/@default/dashboard/boards/dns.css.map new file mode 100644 index 00000000..a67e0fd5 --- /dev/null +++ b/web/views/@default/dashboard/boards/dns.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["dns.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA","file":"dns.css"} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/dns.html b/web/views/@default/dashboard/boards/dns.html new file mode 100644 index 00000000..c8483a70 --- /dev/null +++ b/web/views/@default/dashboard/boards/dns.html @@ -0,0 +1,29 @@ +{$layout} +{$template "menu"} + +
+
+

域名

+
+
+
+

记录

+
+
+
+

集群

+
+
+
+

节点

+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/dns.less b/web/views/@default/dashboard/boards/dns.less new file mode 100644 index 00000000..fcb67336 --- /dev/null +++ b/web/views/@default/dashboard/boards/dns.less @@ -0,0 +1,46 @@ +.ui.message { + .icon { + position: absolute; + right: 1em; + top: 1.8em; + } +} + +.grid { + margin-top: 2em !important; + margin-left: 2em !important; + + .column { + margin-bottom: 2em; + border-right: 1px #eee solid; + + div.value { + margin-top: 1.5em; + + span { + font-size: 2em; + margin-right: 0.2em; + } + } + } + + .column.no-border { + border-right: 0; + } + + h4 { + a { + display: none; + } + } + + .column:hover { + a { + display: inline; + } + } +} + +.chart-box { + height: 20em; +} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/index.css b/web/views/@default/dashboard/boards/index.css new file mode 100644 index 00000000..6a894a18 --- /dev/null +++ b/web/views/@default/dashboard/boards/index.css @@ -0,0 +1,33 @@ +.ui.message .icon { + position: absolute; + right: 1em; + top: 1.8em; +} +.grid { + margin-top: 2em !important; + margin-left: 2em !important; +} +.grid .column { + margin-bottom: 2em; + border-right: 1px #eee solid; +} +.grid .column div.value { + margin-top: 1.5em; +} +.grid .column div.value span { + font-size: 2em; + margin-right: 0.2em; +} +.grid .column.no-border { + border-right: 0; +} +.grid h4 a { + display: none; +} +.grid .column:hover a { + display: inline; +} +.chart-box { + height: 20em; +} +/*# sourceMappingURL=index.css.map */ \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/index.css.map b/web/views/@default/dashboard/boards/index.css.map new file mode 100644 index 00000000..53288e00 --- /dev/null +++ b/web/views/@default/dashboard/boards/index.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA","file":"index.css"} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/index.html b/web/views/@default/dashboard/boards/index.html new file mode 100644 index 00000000..1df7eb65 --- /dev/null +++ b/web/views/@default/dashboard/boards/index.html @@ -0,0 +1,58 @@ +{$layout} +{$template "/echarts"} + + +
升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本
+
升级提醒:有 {{monitorNodeUpgradeInfo.count}} 个监控节点需要升级到 v{{monitorNodeUpgradeInfo.version}} 版本
+
升级提醒:有 {{userNodeUpgradeInfo.count}} 个用户节点需要升级到 v{{userNodeUpgradeInfo.version}} 版本
+
升级提醒:有 {{apiNodeUpgradeInfo.count}} 个API节点需要升级到 v{{apiNodeUpgradeInfo.version}} 版本
+
升级提醒:有 {{nsNodeUpgradeInfo.count}} 个DNS节点需要升级到 v{{nsNodeUpgradeInfo.version}} 版本
+
升级提醒:有 {{authorityNodeUpgradeInfo.count}} 个企业版认证节点需要升级到 v{{authorityNodeUpgradeInfo.version}} 版本
+ +{$template "menu"} + + +
+
+

集群

+
{{dashboard.countNodeClusters}}
+
+ +
+

边缘节点

+
{{dashboard.countNodes}}
+
+ +
+

API节点

+
{{dashboard.countAPINodes}}
+
+ +
+

用户

+
{{dashboard.countUsers}}
+
+ +
+

服务

+
{{dashboard.countServers}}
+
+ +
+

今日流量

+
{{todayTraffic}}{{todayTrafficUnit}}
+
+
+ +
+ + + + +
+ + +
\ No newline at end of file diff --git a/web/views/@default/dashboard/boards/index.js b/web/views/@default/dashboard/boards/index.js new file mode 100644 index 00000000..55458208 --- /dev/null +++ b/web/views/@default/dashboard/boards/index.js @@ -0,0 +1,170 @@ +Tea.context(function () { + this.trafficTab = "hourly" + + this.$delay(function () { + this.reloadHourlyTrafficChart() + + let that = this + window.addEventListener("resize", function () { + if (that.trafficTab == "hourly") { + that.resizeHourlyTrafficChart() + } + if (that.trafficTab == "daily") { + that.resizeDailyTrafficChart() + } + }) + }) + + this.selectTrafficTab = function (tab) { + this.trafficTab = tab + if (tab == "hourly") { + this.$delay(function () { + this.reloadHourlyTrafficChart() + }) + } else if (tab == "daily") { + this.$delay(function () { + this.reloadDailyTrafficChart() + }) + } + } + + this.resizeHourlyTrafficChart = function () { + let chartBox = document.getElementById("hourly-traffic-chart-box") + let chart = echarts.init(chartBox) + chart.resize() + } + + this.reloadHourlyTrafficChart = function () { + let axis = teaweb.bytesAxis(this.hourlyTrafficStats, function (v) { + return v.bytes + }) + let chartBox = document.getElementById("hourly-traffic-chart-box") + let chart = echarts.init(chartBox) + let that = this + let option = { + xAxis: { + data: this.hourlyTrafficStats.map(function (v) { + return v.hour; + }) + }, + yAxis: { + axisLabel: { + formatter: function (v) { + return v + axis.unit + } + } + }, + tooltip: { + show: true, + trigger: "item", + formatter: function (args) { + let index = args.dataIndex + return that.hourlyTrafficStats[index].hour + "时:" + teaweb.formatBytes(that.hourlyTrafficStats[index].bytes) + } + }, + grid: { + left: 40, + top: 10, + right: 20 + }, + series: [ + { + name: "流量", + type: "line", + data: this.hourlyTrafficStats.map(function (v) { + return v.bytes / axis.divider; + }), + itemStyle: { + color: "#9DD3E8" + }, + lineStyle: { + color: "#9DD3E8" + }, + areaStyle: { + color: "#9DD3E8" + } + } + ], + animation: false + } + chart.setOption(option) + chart.resize() + } + + this.resizeDailyTrafficChart = function () { + let chartBox = document.getElementById("daily-traffic-chart-box") + let chart = echarts.init(chartBox) + chart.resize() + } + + this.reloadDailyTrafficChart = function () { + let axis = teaweb.bytesAxis(this.dailyTrafficStats, function (v) { + return v.bytes + }) + let chartBox = document.getElementById("daily-traffic-chart-box") + let chart = echarts.init(chartBox) + let that = this + let option = { + xAxis: { + data: this.dailyTrafficStats.map(function (v) { + return v.day; + }) + }, + yAxis: { + axisLabel: { + formatter: function (v) { + return v + axis.unit + } + } + }, + tooltip: { + show: true, + trigger: "item", + formatter: function (args) { + let index = args.dataIndex + return that.dailyTrafficStats[index].day + ":" + teaweb.formatBytes(that.dailyTrafficStats[index].bytes) + } + }, + grid: { + left: 40, + top: 10, + right: 20 + }, + series: [ + { + name: "流量", + type: "line", + data: this.dailyTrafficStats.map(function (v) { + return v.bytes / axis.divider; + }), + itemStyle: { + color: "#9DD3E8" + }, + lineStyle: { + color: "#9DD3E8" + }, + areaStyle: { + color: "#9DD3E8" + } + } + ], + animation: false + } + chart.setOption(option) + chart.resize() + } + + /** + * 升级提醒 + */ + this.closeMessage = function (e) { + let target = e.target + while (true) { + target = target.parentNode + if (target.tagName.toUpperCase() == "DIV") { + target.style.cssText = "display: none" + break + } + } + } +}) diff --git a/web/views/@default/dashboard/boards/index.less b/web/views/@default/dashboard/boards/index.less new file mode 100644 index 00000000..fcb67336 --- /dev/null +++ b/web/views/@default/dashboard/boards/index.less @@ -0,0 +1,46 @@ +.ui.message { + .icon { + position: absolute; + right: 1em; + top: 1.8em; + } +} + +.grid { + margin-top: 2em !important; + margin-left: 2em !important; + + .column { + margin-bottom: 2em; + border-right: 1px #eee solid; + + div.value { + margin-top: 1.5em; + + span { + font-size: 2em; + margin-right: 0.2em; + } + } + } + + .column.no-border { + border-right: 0; + } + + h4 { + a { + display: none; + } + } + + .column:hover { + a { + display: inline; + } + } +} + +.chart-box { + height: 20em; +} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/user.css b/web/views/@default/dashboard/boards/user.css new file mode 100644 index 00000000..8b48ce59 --- /dev/null +++ b/web/views/@default/dashboard/boards/user.css @@ -0,0 +1,37 @@ +.ui.message .icon { + position: absolute; + right: 1em; + top: 1.8em; +} +.grid { + margin-top: 2em !important; + margin-left: 2em !important; +} +.grid .column { + margin-bottom: 2em; + border-right: 1px #eee solid; +} +.grid .column div.value { + margin-top: 1.5em; +} +.grid .column div.value span { + font-size: 2em; + margin-right: 0.2em; +} +.grid .column.no-border { + border-right: 0; +} +.grid h4 a { + display: none; +} +.grid .column:hover a { + display: inline; +} +.chart-box { + height: 20em; +} +h4 span { + font-size: 0.8em; + color: grey; +} +/*# sourceMappingURL=user.css.map */ \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/user.css.map b/web/views/@default/dashboard/boards/user.css.map new file mode 100644 index 00000000..26f62d1a --- /dev/null +++ b/web/views/@default/dashboard/boards/user.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["user.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA;;AAGD,EACC;EACC,gBAAA;EACA,WAAA","file":"user.css"} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/user.html b/web/views/@default/dashboard/boards/user.html new file mode 100644 index 00000000..99faf597 --- /dev/null +++ b/web/views/@default/dashboard/boards/user.html @@ -0,0 +1,46 @@ +{$layout} +{$template "menu"} +{$template "/echarts"} + +
+
+

用户总数

+
{{board.totalUsers}}
+
+
+

今日新增

+
{{board.countTodayUsers}}
+
+
+

本周新增

+
{{board.countWeeklyUsers}}
+
+
+

用户节点

+
{{board.countUserNodes}} + {{board.countOfflineUserNodes}}离线 + +
+
+
+ +

用户增长趋势

+
+ +
+ +

流量排行 (24小时)

+
+ +
+ + + + +
+
+
diff --git a/web/views/@default/dashboard/boards/user.js b/web/views/@default/dashboard/boards/user.js new file mode 100644 index 00000000..abc9f0fd --- /dev/null +++ b/web/views/@default/dashboard/boards/user.js @@ -0,0 +1,154 @@ +Tea.context(function () { + this.$delay(function () { + this.reloadDailyStats() + this.reloadCPUChart() + this.reloadTopTrafficChart() + }) + + this.reloadDailyStats = function () { + let axis = teaweb.countAxis(this.dailyStats, function (v) { + return v.count + }) + let max = axis.max + if (max < 10) { + max = 10 + } else if (max < 100) { + max = 100 + } + teaweb.renderLineChart({ + id: "daily-stat-chart", + name: "用户", + values: this.dailyStats, + x: function (v) { + return v.day.substring(4, 6) + "-" + v.day.substring(6) + }, + tooltip: function (args, stats) { + let index = args.dataIndex + return stats[index].day.substring(4, 6) + "-" + stats[index].day.substring(6) + ":" + stats[index].count + }, + value: function (v) { + return v.count; + }, + axis: axis, + max: max + }) + } + + /** + * 系统信息 + */ + this.nodeStatusTab = "cpu" + + this.selectNodeStatusTab = function (tab) { + this.nodeStatusTab = tab + this.$delay(function () { + switch (tab) { + case "cpu": + this.reloadCPUChart() + break + case "memory": + this.reloadMemoryChart() + break + case "load": + this.reloadLoadChart() + break + } + }) + } + + this.reloadCPUChart = function () { + let axis = {unit: "%", divider: 1} + teaweb.renderLineChart({ + id: "cpu-chart", + name: "CPU", + values: this.cpuValues, + x: function (v) { + return v.time + }, + tooltip: function (args, stats) { + return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100 * 100) / 100) + "%" + }, + value: function (v) { + return v.value * 100; + }, + axis: axis, + max: 100 + }) + } + + this.reloadMemoryChart = function () { + let axis = {unit: "%", divider: 1} + teaweb.renderLineChart({ + id: "memory-chart", + name: "内存", + values: this.memoryValues, + x: function (v) { + return v.time + }, + tooltip: function (args, stats) { + return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100 * 100) / 100) + "%" + }, + value: function (v) { + return v.value * 100; + }, + axis: axis, + max: 100 + }) + } + + this.reloadLoadChart = function () { + let axis = {unit: "", divider: 1} + let max = this.loadValues.$map(function (k, v) { + return v.value + }).$max() + if (max < 10) { + max = 10 + } else if (max < 20) { + max = 20 + } else if (max < 100) { + max = 100 + } else { + max = null + } + teaweb.renderLineChart({ + id: "load-chart", + name: "负载", + values: this.loadValues, + x: function (v) { + return v.time + }, + tooltip: function (args, stats) { + return stats[args.dataIndex].time + ":" + (Math.ceil(stats[args.dataIndex].value * 100) / 100) + }, + value: function (v) { + return v.value; + }, + axis: axis, + max: max + }) + } + + // 流量排行 + this.reloadTopTrafficChart = function () { + let that = this + let axis = teaweb.bytesAxis(this.topTrafficStats, function (v) { + return v.bytes + }) + teaweb.renderBarChart({ + id: "top-traffic-chart", + name: "流量", + values: this.topTrafficStats, + x: function (v) { + return v.userName + }, + tooltip: function (args, stats) { + let index = args.dataIndex + return stats[index].userName + "
请求数:" + " " + teaweb.formatNumber(stats[index].countRequests) + "
流量:" + teaweb.formatBytes(stats[index].bytes) + }, + value: function (v) { + return v.bytes / axis.divider; + }, + axis: axis + }) + } +}) \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/user.less b/web/views/@default/dashboard/boards/user.less new file mode 100644 index 00000000..f3b91629 --- /dev/null +++ b/web/views/@default/dashboard/boards/user.less @@ -0,0 +1,53 @@ +.ui.message { + .icon { + position: absolute; + right: 1em; + top: 1.8em; + } +} + +.grid { + margin-top: 2em !important; + margin-left: 2em !important; + + .column { + margin-bottom: 2em; + border-right: 1px #eee solid; + + div.value { + margin-top: 1.5em; + + span { + font-size: 2em; + margin-right: 0.2em; + } + } + } + + .column.no-border { + border-right: 0; + } + + h4 { + a { + display: none; + } + } + + .column:hover { + a { + display: inline; + } + } +} + +.chart-box { + height: 20em; +} + +h4 { + span { + font-size: 0.8em; + color: grey; + } +} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/waf.css b/web/views/@default/dashboard/boards/waf.css new file mode 100644 index 00000000..7a6beaea --- /dev/null +++ b/web/views/@default/dashboard/boards/waf.css @@ -0,0 +1,33 @@ +.ui.message .icon { + position: absolute; + right: 1em; + top: 1.8em; +} +.grid { + margin-top: 2em !important; + margin-left: 2em !important; +} +.grid .column { + margin-bottom: 2em; + border-right: 1px #eee solid; +} +.grid .column div.value { + margin-top: 1.5em; +} +.grid .column div.value span { + font-size: 2em; + margin-right: 0.2em; +} +.grid .column.no-border { + border-right: 0; +} +.grid h4 a { + display: none; +} +.grid .column:hover a { + display: inline; +} +.chart-box { + height: 20em; +} +/*# sourceMappingURL=waf.css.map */ \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/waf.css.map b/web/views/@default/dashboard/boards/waf.css.map new file mode 100644 index 00000000..e252fba4 --- /dev/null +++ b/web/views/@default/dashboard/boards/waf.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["waf.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA;;AAIF;EACC,0BAAA;EACA,2BAAA;;AAFD,KAIC;EACC,kBAAA;EACA,4BAAA;;AANF,KAIC,QAIC,IAAG;EACF,iBAAA;;AATH,KAIC,QAIC,IAAG,MAGF;EACC,cAAA;EACA,mBAAA;;AAbJ,KAkBC,QAAO;EACN,eAAA;;AAnBF,KAsBC,GACC;EACC,aAAA;;AAxBH,KA4BC,QAAO,MACN;EACC,eAAA;;AAKH;EACC,YAAA","file":"waf.css"} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/waf.html b/web/views/@default/dashboard/boards/waf.html new file mode 100644 index 00000000..e896c845 --- /dev/null +++ b/web/views/@default/dashboard/boards/waf.html @@ -0,0 +1,2 @@ +{$layout} +{$template "menu"} \ No newline at end of file diff --git a/web/views/@default/dashboard/boards/waf.less b/web/views/@default/dashboard/boards/waf.less new file mode 100644 index 00000000..fcb67336 --- /dev/null +++ b/web/views/@default/dashboard/boards/waf.less @@ -0,0 +1,46 @@ +.ui.message { + .icon { + position: absolute; + right: 1em; + top: 1.8em; + } +} + +.grid { + margin-top: 2em !important; + margin-left: 2em !important; + + .column { + margin-bottom: 2em; + border-right: 1px #eee solid; + + div.value { + margin-top: 1.5em; + + span { + font-size: 2em; + margin-right: 0.2em; + } + } + } + + .column.no-border { + border-right: 0; + } + + h4 { + a { + display: none; + } + } + + .column:hover { + a { + display: inline; + } + } +} + +.chart-box { + height: 20em; +} \ No newline at end of file