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"}
+
+
+
+
+
+
+
+
+
+{$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