mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-03 04:10:27 +08:00
实现数据看板--用户
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*_plus.go
|
||||
17
internal/web/actions/default/dashboard/boards/dns.go
Normal file
17
internal/web/actions/default/dashboard/boards/dns.go
Normal file
@@ -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()
|
||||
}
|
||||
171
internal/web/actions/default/dashboard/boards/index.go
Normal file
171
internal/web/actions/default/dashboard/boards/index.go
Normal file
@@ -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()
|
||||
}
|
||||
97
internal/web/actions/default/dashboard/boards/user.go
Normal file
97
internal/web/actions/default/dashboard/boards/user.go
Normal file
@@ -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()
|
||||
}
|
||||
17
internal/web/actions/default/dashboard/boards/waf.go
Normal file
17
internal/web/actions/default/dashboard/boards/waf.go
Normal file
@@ -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()
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -172,7 +172,8 @@ window.teaweb = {
|
||||
}
|
||||
return {
|
||||
unit: unit,
|
||||
divider: divider
|
||||
divider: divider,
|
||||
max: max
|
||||
}
|
||||
},
|
||||
popup: function (url, options) {
|
||||
|
||||
6
web/views/@default/dashboard/boards/@menu.html
Normal file
6
web/views/@default/dashboard/boards/@menu.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<first-menu>
|
||||
<menu-item href="/dashboard/boards" code="index">概况</menu-item>
|
||||
<menu-item href="/dashboard/boards/waf" code="waf">WAF</menu-item>
|
||||
<menu-item href="/dashboard/boards/dns" code="dns">DNS</menu-item>
|
||||
<menu-item href="/dashboard/boards/user" code="user">用户</menu-item>
|
||||
</first-menu>
|
||||
33
web/views/@default/dashboard/boards/dns.css
Normal file
33
web/views/@default/dashboard/boards/dns.css
Normal file
@@ -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 */
|
||||
1
web/views/@default/dashboard/boards/dns.css.map
Normal file
1
web/views/@default/dashboard/boards/dns.css.map
Normal file
@@ -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"}
|
||||
29
web/views/@default/dashboard/boards/dns.html
Normal file
29
web/views/@default/dashboard/boards/dns.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<div class="ui four columns grid">
|
||||
<div class="ui column">
|
||||
<h4>域名</h4>
|
||||
<div class="value"><span></span></div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>记录</h4>
|
||||
<div class="value"><span></span></div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>集群</h4>
|
||||
<div class="value"><span></span></div>
|
||||
</div>
|
||||
<div class="ui column no-border">
|
||||
<h4>节点</h4>
|
||||
<div class="value"><span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流量统计 -->
|
||||
|
||||
<!-- 域名排行 -->
|
||||
|
||||
<!-- 记录排行 -->
|
||||
|
||||
<!-- 系统信息 -->
|
||||
46
web/views/@default/dashboard/boards/dns.less
Normal file
46
web/views/@default/dashboard/boards/dns.less
Normal file
@@ -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;
|
||||
}
|
||||
33
web/views/@default/dashboard/boards/index.css
Normal file
33
web/views/@default/dashboard/boards/index.css
Normal file
@@ -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 */
|
||||
1
web/views/@default/dashboard/boards/index.css.map
Normal file
1
web/views/@default/dashboard/boards/index.css.map
Normal file
@@ -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"}
|
||||
58
web/views/@default/dashboard/boards/index.html
Normal file
58
web/views/@default/dashboard/boards/index.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{$layout}
|
||||
{$template "/echarts"}
|
||||
|
||||
<!-- 升级提醒 -->
|
||||
<div class="ui message error" v-if="nodeUpgradeInfo.count > 0"><a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a></div>
|
||||
<div class="ui message error" v-if="monitorNodeUpgradeInfo.count > 0 && teaIsPlus"><a href="/settings/monitorNodes">升级提醒:有 {{monitorNodeUpgradeInfo.count}} 个监控节点需要升级到 v{{monitorNodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a></div>
|
||||
<div class="ui message error" v-if="userNodeUpgradeInfo.count > 0 && teaIsPlus"><a href="/settings/userNodes">升级提醒:有 {{userNodeUpgradeInfo.count}} 个用户节点需要升级到 v{{userNodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a></div>
|
||||
<div class="ui message error" v-if="apiNodeUpgradeInfo.count > 0"><a href="/api">升级提醒:有 {{apiNodeUpgradeInfo.count}} 个API节点需要升级到 v{{apiNodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a></div>
|
||||
<div class="ui message error" v-if="nsNodeUpgradeInfo.count > 0 && teaIsPlus"><a href="/ns/clusters">升级提醒:有 {{nsNodeUpgradeInfo.count}} 个DNS节点需要升级到 v{{nsNodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a></div>
|
||||
<div class="ui message error" v-if="authorityNodeUpgradeInfo.count > 0 && teaIsPlus"><a href="/settings/authority/nodes">升级提醒:有 {{authorityNodeUpgradeInfo.count}} 个企业版认证节点需要升级到 v{{authorityNodeUpgradeInfo.version}} 版本</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a></div>
|
||||
|
||||
{$template "menu"}
|
||||
|
||||
<!-- 统计图表 -->
|
||||
<div class="ui three columns grid">
|
||||
<div class="ui column">
|
||||
<h4>集群<link-icon href="/clusters" v-if="dashboard.canGoNodes"></link-icon></h4>
|
||||
<div class="value"><span>{{dashboard.countNodeClusters}}</span>个</div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>边缘节点<link-icon href="/clusters" v-if="dashboard.canGoNodes"></link-icon></h4>
|
||||
<div class="value"><span>{{dashboard.countNodes}}</span>个</div>
|
||||
</div>
|
||||
|
||||
<div class="ui column no-border">
|
||||
<h4>API节点<link-icon href="/api" v-if="dashboard.canGoSettings"></link-icon></h4>
|
||||
<div class="value"><span>{{dashboard.countAPINodes}}</span>个</div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>用户<link-icon href="/users" v-if="dashboard.canGoUsers"></link-icon></h4>
|
||||
<div class="value"><span>{{dashboard.countUsers}}</span>个</div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>服务<link-icon href="/servers" v-if="dashboard.canGoServers"></link-icon></h4>
|
||||
<div class="value"><span>{{dashboard.countServers}}</span>个</div>
|
||||
</div>
|
||||
|
||||
<div class="ui column no-border">
|
||||
<h4>今日流量</h4>
|
||||
<div class="value"><span>{{todayTraffic}}</span>{{todayTrafficUnit}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="ui menu tabular">
|
||||
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势</a>
|
||||
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势</a>
|
||||
</div>
|
||||
|
||||
<!-- 按小时统计 -->
|
||||
<div class="chart-box" id="hourly-traffic-chart-box" v-show="trafficTab == 'hourly'"></div>
|
||||
|
||||
<!-- 按日统计 -->
|
||||
<div class="chart-box" id="daily-traffic-chart-box" v-show="trafficTab == 'daily'"></div>
|
||||
170
web/views/@default/dashboard/boards/index.js
Normal file
170
web/views/@default/dashboard/boards/index.js
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
46
web/views/@default/dashboard/boards/index.less
Normal file
46
web/views/@default/dashboard/boards/index.less
Normal file
@@ -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;
|
||||
}
|
||||
37
web/views/@default/dashboard/boards/user.css
Normal file
37
web/views/@default/dashboard/boards/user.css
Normal file
@@ -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 */
|
||||
1
web/views/@default/dashboard/boards/user.css.map
Normal file
1
web/views/@default/dashboard/boards/user.css.map
Normal file
@@ -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"}
|
||||
46
web/views/@default/dashboard/boards/user.html
Normal file
46
web/views/@default/dashboard/boards/user.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
{$template "/echarts"}
|
||||
|
||||
<div class="ui four columns grid">
|
||||
<div class="ui column">
|
||||
<h4>用户总数<link-icon href="/users"></link-icon></h4>
|
||||
<div class="value"><span>{{board.totalUsers}}</span></div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>今日新增</h4>
|
||||
<div class="value"><span>{{board.countTodayUsers}}</span></div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>本周新增</h4>
|
||||
<div class="value"><span>{{board.countWeeklyUsers}}</span></div>
|
||||
</div>
|
||||
<div class="ui column no-border">
|
||||
<h4>用户节点<link-icon href="/settings/userNodes"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countUserNodes}}</span>
|
||||
<span v-if="board.countOfflineUserNodes > 0" class="red" style="font-size: 1em">{{board.countOfflineUserNodes}}离线</span>
|
||||
<span v-else style="font-size: 1em">个</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>用户增长趋势</h4>
|
||||
<div class="chart-box" id="daily-stat-chart"></div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<h4>流量排行 <span>(24小时)</span></h4>
|
||||
<div class="chart-box" id="top-traffic-chart"></div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<!-- 系统信息 -->
|
||||
<div class="ui menu tabular">
|
||||
<a href="" class="item" :class="{active: nodeStatusTab == 'cpu'}" @click.prevent="selectNodeStatusTab('cpu')">用户节点CPU</a>
|
||||
<a href="" class="item" :class="{active: nodeStatusTab == 'memory'}" @click.prevent="selectNodeStatusTab('memory')">用户节点内存</a>
|
||||
<a href="" class="item" :class="{active: nodeStatusTab == 'load'}" @click.prevent="selectNodeStatusTab('load')">用户节点负载</a>
|
||||
</div>
|
||||
|
||||
<div class="chart-box" id="cpu-chart" v-show="nodeStatusTab == 'cpu'"></div>
|
||||
<div class="chart-box" id="memory-chart" v-show="nodeStatusTab == 'memory'"></div>
|
||||
<div class="chart-box" id="load-chart" v-show="nodeStatusTab == 'load'"></div>
|
||||
154
web/views/@default/dashboard/boards/user.js
Normal file
154
web/views/@default/dashboard/boards/user.js
Normal file
@@ -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 + "<br/>请求数:" + " " + teaweb.formatNumber(stats[index].countRequests) + "<br/>流量:" + teaweb.formatBytes(stats[index].bytes)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.bytes / axis.divider;
|
||||
},
|
||||
axis: axis
|
||||
})
|
||||
}
|
||||
})
|
||||
53
web/views/@default/dashboard/boards/user.less
Normal file
53
web/views/@default/dashboard/boards/user.less
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
33
web/views/@default/dashboard/boards/waf.css
Normal file
33
web/views/@default/dashboard/boards/waf.css
Normal file
@@ -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 */
|
||||
1
web/views/@default/dashboard/boards/waf.css.map
Normal file
1
web/views/@default/dashboard/boards/waf.css.map
Normal file
@@ -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"}
|
||||
2
web/views/@default/dashboard/boards/waf.html
Normal file
2
web/views/@default/dashboard/boards/waf.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
46
web/views/@default/dashboard/boards/waf.less
Normal file
46
web/views/@default/dashboard/boards/waf.less
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user