mirror of
https://github.com/TeaOSLab/EdgeAdmin.git
synced 2025-11-12 19:30:26 +08:00
实现Dashboard
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
type AdminModuleCode = string
|
type AdminModuleCode = string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
AdminModuleCodeDashboard AdminModuleCode = "dashboard" // 看板
|
||||||
AdminModuleCodeServer AdminModuleCode = "server" // 网站
|
AdminModuleCodeServer AdminModuleCode = "server" // 网站
|
||||||
AdminModuleCodeNode AdminModuleCode = "node" // 节点
|
AdminModuleCodeNode AdminModuleCode = "node" // 节点
|
||||||
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
|
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
|
||||||
@@ -109,7 +110,7 @@ func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
|
|||||||
list, ok2 := sharedAdminModuleMapping[adminId]
|
list, ok2 := sharedAdminModuleMapping[adminId]
|
||||||
if ok2 {
|
if ok2 {
|
||||||
if list.IsSuper {
|
if list.IsSuper {
|
||||||
return AdminModuleCodeServer, true
|
return AdminModuleCodeDashboard, true
|
||||||
} else if len(list.Modules) > 0 {
|
} else if len(list.Modules) > 0 {
|
||||||
return list.Modules[0].Code, true
|
return list.Modules[0].Code, true
|
||||||
}
|
}
|
||||||
@@ -132,6 +133,11 @@ func FindAdminFullname(adminId int64) string {
|
|||||||
// 所有权限列表
|
// 所有权限列表
|
||||||
func AllModuleMaps() []maps.Map {
|
func AllModuleMaps() []maps.Map {
|
||||||
return []maps.Map{
|
return []maps.Map{
|
||||||
|
{
|
||||||
|
"name": "看板",
|
||||||
|
"code": AdminModuleCodeDashboard,
|
||||||
|
"url": "/dashboard",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "网站服务",
|
"name": "网站服务",
|
||||||
"code": AdminModuleCodeServer,
|
"code": AdminModuleCodeServer,
|
||||||
|
|||||||
@@ -17,12 +17,26 @@ func FormatBytes(bytes int64) string {
|
|||||||
if bytes < 1024 {
|
if bytes < 1024 {
|
||||||
return FormatInt64(bytes) + "B"
|
return FormatInt64(bytes) + "B"
|
||||||
} else if bytes < 1024*1024 {
|
} else if bytes < 1024*1024 {
|
||||||
return fmt.Sprintf("%.2fK", float64(bytes)/1024)
|
return fmt.Sprintf("%.2fKB", float64(bytes)/1024)
|
||||||
} else if bytes < 1024*1024*1024 {
|
} else if bytes < 1024*1024*1024 {
|
||||||
return fmt.Sprintf("%.2fM", float64(bytes)/1024/1024)
|
return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024)
|
||||||
} else if bytes < 1024*1024*1024*1024 {
|
} else if bytes < 1024*1024*1024*1024 {
|
||||||
return fmt.Sprintf("%.2fG", float64(bytes)/1024/1024/1024)
|
return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("%.2fP", float64(bytes)/1024/1024/1024/1024)
|
return fmt.Sprintf("%.2fPB", float64(bytes)/1024/1024/1024/1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatBits(bits int64) string {
|
||||||
|
if bits < 1000 {
|
||||||
|
return FormatInt64(bits) + "B"
|
||||||
|
} else if bits < 1000*1000 {
|
||||||
|
return fmt.Sprintf("%.2fKB", float64(bits)/1000)
|
||||||
|
} else if bits < 1000*1000*1000 {
|
||||||
|
return fmt.Sprintf("%.2fMB", float64(bits)/1000/1000)
|
||||||
|
} else if bits < 1000*1000*1000*1000 {
|
||||||
|
return fmt.Sprintf("%.2fGB", float64(bits)/1000/10001000)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%.2fPB", float64(bits)/1000/1000/1000/1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ package dashboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -17,6 +22,7 @@ func (this *IndexAction) RunGet(params struct{}) {
|
|||||||
// 取得用户的权限
|
// 取得用户的权限
|
||||||
module, ok := configloaders.FindFirstAdminModule(this.AdminId())
|
module, ok := configloaders.FindFirstAdminModule(this.AdminId())
|
||||||
if ok {
|
if ok {
|
||||||
|
if module != "dashboard" {
|
||||||
for _, m := range configloaders.AllModuleMaps() {
|
for _, m := range configloaders.AllModuleMaps() {
|
||||||
if m.GetString("code") == module {
|
if m.GetString("code") == module {
|
||||||
this.RedirectURL(m.GetString("url"))
|
this.RedirectURL(m.GetString("url"))
|
||||||
@@ -24,6 +30,62 @@ func (this *IndexAction) RunGet(params struct{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取看板数据
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 今日流量
|
||||||
|
todayTrafficBytes := int64(0)
|
||||||
|
if len(resp.DailyTrafficStats) > 0 {
|
||||||
|
todayTrafficBytes = resp.DailyTrafficStats[len(resp.DailyTrafficStats)-1].Bytes
|
||||||
|
}
|
||||||
|
todayTrafficString := numberutils.FormatBits(todayTrafficBytes * 8)
|
||||||
|
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{
|
||||||
|
"count": math.Ceil((float64(stat.Bytes)*8/1000/1000/1000)*1000) / 1000,
|
||||||
|
"hour": stat.Hour[8:],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["hourlyTrafficStats"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15天流量趋势
|
||||||
|
{
|
||||||
|
statMaps := []maps.Map{}
|
||||||
|
for _, stat := range resp.DailyTrafficStats {
|
||||||
|
statMaps = append(statMaps, maps.Map{
|
||||||
|
"count": math.Ceil((float64(stat.Bytes)*8/1000/1000/1000)*1000) / 1000,
|
||||||
|
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["dailyTrafficStats"] = statMaps
|
||||||
|
}
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||||
server.Prefix("/dashboard").
|
server.Prefix("/dashboard").
|
||||||
|
Data("teaMenu", "dashboard").
|
||||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
|
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
|
||||||
GetPost("", new(IndexAction)).
|
GetPost("", new(IndexAction)).
|
||||||
EndAll()
|
EndAll()
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
|
|||||||
// 菜单配置
|
// 菜单配置
|
||||||
func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
||||||
allMaps := []maps.Map{
|
allMaps := []maps.Map{
|
||||||
|
{
|
||||||
|
"code": "dashboard",
|
||||||
|
"module": configloaders.AdminModuleCodeDashboard,
|
||||||
|
"name": "看板",
|
||||||
|
"icon": "dashboard",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"code": "servers",
|
"code": "servers",
|
||||||
"module": configloaders.AdminModuleCodeServer,
|
"module": configloaders.AdminModuleCodeServer,
|
||||||
|
|||||||
16
web/public/js/echarts/echarts.min.js
vendored
Normal file
16
web/public/js/echarts/echarts.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
22
web/views/@default/dashboard/index.css
Normal file
22
web/views/@default/dashboard/index.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
.chart-box {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=index.css.map */
|
||||||
1
web/views/@default/dashboard/index.css.map
Normal file
1
web/views/@default/dashboard/index.css.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;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;;AAKF;EACC,YAAA","file":"index.css"}
|
||||||
@@ -1,2 +1,52 @@
|
|||||||
{$layout}
|
{$layout}
|
||||||
|
|
||||||
|
|
||||||
|
{$var "header"}
|
||||||
|
<!-- echart -->
|
||||||
|
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||||
|
{$end}
|
||||||
|
|
||||||
|
<div class="ui three columns grid">
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>当前集群数</h4>
|
||||||
|
<div class="value"><span>{{dashboard.countNodeClusters}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>当前边缘节点数</h4>
|
||||||
|
<div class="value"><span>{{dashboard.countNodes}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui column no-border">
|
||||||
|
<h4>当前API节点数</h4>
|
||||||
|
<div class="value"><span>{{dashboard.countAPINodes}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>当前用户数</h4>
|
||||||
|
<div class="value"><span>{{dashboard.countUsers}}</span>个</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui column">
|
||||||
|
<h4>当前服务数</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小时流量趋势(GB)</a>
|
||||||
|
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势(GB)</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>
|
||||||
105
web/views/@default/dashboard/index.js
Normal file
105
web/views/@default/dashboard/index.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
Tea.context(function () {
|
||||||
|
this.trafficTab = "hourly"
|
||||||
|
|
||||||
|
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadHourlyTrafficChart()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.selectTrafficTab = function (tab) {
|
||||||
|
this.trafficTab = tab
|
||||||
|
if (tab == "hourly") {
|
||||||
|
|
||||||
|
} else if (tab == "daily") {
|
||||||
|
this.$delay(function () {
|
||||||
|
this.reloadDailyTrafficChart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadHourlyTrafficChart = function () {
|
||||||
|
let chartBox = document.getElementById("hourly-traffic-chart-box")
|
||||||
|
let chart = echarts.init(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: this.hourlyTrafficStats.map(function (v) {
|
||||||
|
return v.hour;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
yAxis: {},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: "{c} GB"
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 40,
|
||||||
|
top: 10,
|
||||||
|
right: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "流量",
|
||||||
|
type: "line",
|
||||||
|
data: this.hourlyTrafficStats.map(function (v) {
|
||||||
|
return v.count;
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animation: false
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reloadDailyTrafficChart = function () {
|
||||||
|
let chartBox = document.getElementById("daily-traffic-chart-box")
|
||||||
|
let chart = echarts.init(chartBox)
|
||||||
|
let option = {
|
||||||
|
xAxis: {
|
||||||
|
data: this.dailyTrafficStats.map(function (v) {
|
||||||
|
return v.day;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
yAxis: {},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: "item",
|
||||||
|
formatter: "{c} GB"
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 40,
|
||||||
|
top: 10,
|
||||||
|
right: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "流量",
|
||||||
|
type: "line",
|
||||||
|
data: this.dailyTrafficStats.map(function (v) {
|
||||||
|
return v.count;
|
||||||
|
}),
|
||||||
|
itemStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: "#9DD3E8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
animation: false
|
||||||
|
}
|
||||||
|
chart.setOption(option)
|
||||||
|
}
|
||||||
|
})
|
||||||
27
web/views/@default/dashboard/index.less
Normal file
27
web/views/@default/dashboard/index.less
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.chart-box {
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user