实现基本的图表管理
This commit is contained in:
@@ -396,6 +396,10 @@ func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
|
||||
return pb.NewMetricStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
|
||||
return pb.NewMetricChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
|
||||
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
|
||||
type ChartsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ChartsAction) Init() {
|
||||
this.Nav("", "", "chart")
|
||||
}
|
||||
|
||||
func (this *ChartsAction) RunGet(params struct {
|
||||
ItemId int64
|
||||
}) {
|
||||
_, err := InitItem(this.Parent(), params.ItemId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
35
internal/web/actions/default/servers/metrics/charts/chart.go
Normal file
35
internal/web/actions/default/servers/metrics/charts/chart.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/charts/chartutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/metricutils"
|
||||
)
|
||||
|
||||
type ChartAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ChartAction) Init() {
|
||||
this.Nav("", "", "chart,chartIndex")
|
||||
}
|
||||
|
||||
func (this *ChartAction) RunGet(params struct {
|
||||
ChartId int64
|
||||
}) {
|
||||
chart, err := chartutils.InitChart(this.Parent(), params.ChartId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = metricutils.InitItem(this.Parent(), chart.MetricItem.Id)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package chartutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// InitChart 初始化指标图表信息
|
||||
func InitChart(parent *actionutils.ParentAction, chartId int64) (*pb.MetricChart, error) {
|
||||
client, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := client.MetricChartRPC().FindEnabledMetricChart(parent.AdminContext(), &pb.FindEnabledMetricChartRequest{MetricChartId: chartId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var chart = resp.MetricChart
|
||||
if chart == nil {
|
||||
return nil, errors.New("metric chart not found")
|
||||
}
|
||||
parent.Data["chart"] = maps.Map{
|
||||
"id": chart.Id,
|
||||
"name": chart.Name,
|
||||
"isOn": chart.IsOn,
|
||||
"widthDiv": chart.WidthDiv,
|
||||
"maxItems": chart.MaxItems,
|
||||
"type": chart.Type,
|
||||
"typeName": serverconfigs.FindAllMetricChartTypeName(chart.Type),
|
||||
}
|
||||
return chart, nil
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct {
|
||||
ItemId int64
|
||||
}) {
|
||||
this.Data["itemId"] = params.ItemId
|
||||
this.Data["types"] = serverconfigs.FindAllMetricChartTypes()
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
ItemId int64
|
||||
Name string
|
||||
Type string
|
||||
WidthDiv int32
|
||||
MaxItems int32
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
var chartId int64
|
||||
defer func() {
|
||||
this.CreateLogInfo("创建指标图表 %d", chartId)
|
||||
}()
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入图表名称").
|
||||
Field("type", params.Type).
|
||||
Require("请选择图表类型")
|
||||
|
||||
createResp, err := this.RPC().MetricChartRPC().CreateMetricChart(this.AdminContext(), &pb.CreateMetricChartRequest{
|
||||
MetricItemId: params.ItemId,
|
||||
Name: params.Name,
|
||||
Type: params.Type,
|
||||
WidthDiv: params.WidthDiv,
|
||||
MaxItems: params.MaxItems,
|
||||
ParamsJSON: nil,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
chartId = createResp.MetricChartId
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
ChartId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo("删除指标图表 %d", params.ChartId)
|
||||
|
||||
_, err := this.RPC().MetricChartRPC().DeleteMetricChart(this.AdminContext(), &pb.DeleteMetricChartRequest{MetricChartId: params.ChartId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
63
internal/web/actions/default/servers/metrics/charts/index.go
Normal file
63
internal/web/actions/default/servers/metrics/charts/index.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/metricutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "chart")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ItemId int64
|
||||
}) {
|
||||
_, err := metricutils.InitItem(this.Parent(), params.ItemId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
countResp, err := this.RPC().MetricChartRPC().CountEnabledMetricCharts(this.AdminContext(), &pb.CountEnabledMetricChartsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var count = countResp.Count
|
||||
page := this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
chartsResp, err := this.RPC().MetricChartRPC().ListEnabledMetricCharts(this.AdminContext(), &pb.ListEnabledMetricChartsRequest{
|
||||
MetricItemId: params.ItemId,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var charts = chartsResp.MetricCharts
|
||||
var chartMaps = []maps.Map{}
|
||||
for _, chart := range charts {
|
||||
chartMaps = append(chartMaps, maps.Map{
|
||||
"id": chart.Id,
|
||||
"name": chart.Name,
|
||||
"type": chart.Type,
|
||||
"typeName": serverconfigs.FindAllMetricChartTypeName(chart.Type),
|
||||
"isOn": chart.IsOn,
|
||||
"widthDiv": chart.WidthDiv,
|
||||
})
|
||||
}
|
||||
this.Data["charts"] = chartMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
23
internal/web/actions/default/servers/metrics/charts/init.go
Normal file
23
internal/web/actions/default/servers/metrics/charts/init.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
|
||||
Data("teaMenu", "servers").
|
||||
Data("teaSubMenu", "metric").
|
||||
Prefix("/servers/metrics/charts").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Get("/chart", new(ChartAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/charts/chartutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/metricutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type UpdateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateAction) Init() {
|
||||
this.Nav("", "", "chart,chartUpdate")
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunGet(params struct {
|
||||
ChartId int64
|
||||
}) {
|
||||
chart, err := chartutils.InitChart(this.Parent(), params.ChartId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = metricutils.InitItem(this.Parent(), chart.MetricItem.Id)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["types"] = serverconfigs.FindAllMetricChartTypes()
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunPost(params struct {
|
||||
ChartId int64
|
||||
Name string
|
||||
Type string
|
||||
WidthDiv int32
|
||||
MaxItems int32
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo("修改指标图表 %d", params.ChartId)
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入图表名称").
|
||||
Field("type", params.Type).
|
||||
Require("请选择图表类型")
|
||||
|
||||
_, err := this.RPC().MetricChartRPC().UpdateMetricChart(this.AdminContext(), &pb.UpdateMetricChartRequest{
|
||||
MetricChartId: params.ChartId,
|
||||
Name: params.Name,
|
||||
Type: params.Type,
|
||||
WidthDiv: params.WidthDiv,
|
||||
MaxItems: params.MaxItems,
|
||||
ParamsJSON: nil,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -18,7 +18,6 @@ func init() {
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Get("/item", new(ItemAction)).
|
||||
Get("/charts", new(ChartsAction)).
|
||||
Get("/stats", new(StatsAction)).
|
||||
EndAll()
|
||||
})
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
package metrics
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/metricutils"
|
||||
)
|
||||
|
||||
type ItemAction struct {
|
||||
actionutils.ParentAction
|
||||
@@ -15,7 +18,7 @@ func (this *ItemAction) Init() {
|
||||
func (this *ItemAction) RunGet(params struct {
|
||||
ItemId int64
|
||||
}) {
|
||||
_, err := InitItem(this.Parent(), params.ItemId)
|
||||
_, err := metricutils.InitItem(this.Parent(), params.ItemId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
package metricutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -22,7 +22,7 @@ func InitItem(parent *actionutils.ParentAction, itemId int64) (*pb.MetricItem, e
|
||||
}
|
||||
var item = resp.MetricItem
|
||||
if item == nil {
|
||||
return nil, errors.New("not found")
|
||||
return nil, errors.New("metric item not found")
|
||||
}
|
||||
parent.Data["item"] = maps.Map{
|
||||
"id": item.Id,
|
||||
@@ -5,6 +5,7 @@ package metrics
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/metricutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
@@ -21,7 +22,7 @@ func (this *StatsAction) Init() {
|
||||
func (this *StatsAction) RunGet(params struct {
|
||||
ItemId int64
|
||||
}) {
|
||||
item, err := InitItem(this.Parent(), params.ItemId)
|
||||
item, err := metricutils.InitItem(this.Parent(), params.ItemId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ package metrics
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/metricutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
@@ -22,7 +23,7 @@ func (this *UpdateAction) Init() {
|
||||
func (this *UpdateAction) RunGet(params struct {
|
||||
ItemId int64
|
||||
}) {
|
||||
item, err := InitItem(this.Parent(), params.ItemId)
|
||||
item, err := metricutils.InitItem(this.Parent(), params.ItemId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
|
||||
@@ -110,7 +110,9 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/stat"
|
||||
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/iplists"
|
||||
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics/charts"
|
||||
|
||||
// 设置相关
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
|
||||
|
||||
@@ -10,7 +10,13 @@ Vue.component("menu-item", {
|
||||
if (typeof (window.TEA.ACTION.data.firstMenuItem) != "undefined") {
|
||||
itemCode = window.TEA.ACTION.data.firstMenuItem
|
||||
}
|
||||
active = (itemCode == this.code)
|
||||
if (itemCode != null && itemCode.length > 0 && this.code != null && this.code.length > 0) {
|
||||
if (itemCode.indexOf(",") > 0) {
|
||||
active = itemCode.split(",").$contains(this.code)
|
||||
} else {
|
||||
active = (itemCode == this.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
vHref: (this.href == null) ? "" : this.href,
|
||||
|
||||
283
web/public/js/components/server/metric-charts.js
Normal file
283
web/public/js/components/server/metric-charts.js
Normal file
@@ -0,0 +1,283 @@
|
||||
// 指标图表
|
||||
Vue.component("metric-chart", {
|
||||
props: ["v-chart", "v-stats", "v-period-unit"],
|
||||
mounted: function () {
|
||||
this.load()
|
||||
},
|
||||
data: function () {
|
||||
let stats = this.vStats
|
||||
if (stats == null) {
|
||||
stats = []
|
||||
}
|
||||
if (stats.length > 0) {
|
||||
let sum = stats.$sum(function (k, v) {
|
||||
return v.value
|
||||
})
|
||||
if (sum < stats[0].total) {
|
||||
stats.push({
|
||||
keys: ["其他"],
|
||||
value: stats[0].total - sum,
|
||||
total: stats[0].total
|
||||
})
|
||||
}
|
||||
}
|
||||
if (this.vChart.maxItems > 0) {
|
||||
stats = stats.slice(0, this.vChart.maxItems)
|
||||
}
|
||||
|
||||
stats.$rsort(function (v1, v2) {
|
||||
return v1.value - v2.value
|
||||
})
|
||||
|
||||
let widthPercent = 100
|
||||
if (this.vChart.widthDiv > 0) {
|
||||
widthPercent = 100 / this.vChart.widthDiv
|
||||
}
|
||||
|
||||
return {
|
||||
chart: this.vChart,
|
||||
stats: stats,
|
||||
width: widthPercent + "%",
|
||||
chartId: "metric-chart-" + this.vChart.id
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
load: function () {
|
||||
var el = document.getElementById(this.chartId)
|
||||
if (el == null || el.offsetWidth == 0 || el.offsetHeight == 0) {
|
||||
setTimeout(this.load, 100)
|
||||
} else {
|
||||
this.render(el)
|
||||
}
|
||||
},
|
||||
render: function (el) {
|
||||
let chart = echarts.init(el)
|
||||
switch (this.chart.type) {
|
||||
case "pie":
|
||||
this.renderPie(chart)
|
||||
break
|
||||
case "bar":
|
||||
this.renderBar(chart)
|
||||
break
|
||||
case "timeBar":
|
||||
this.renderTimeBar(chart)
|
||||
break
|
||||
case "timeLine":
|
||||
this.renderTimeLine(chart)
|
||||
break
|
||||
case "table":
|
||||
this.renderTable(chart)
|
||||
break
|
||||
}
|
||||
},
|
||||
renderPie: function (chart) {
|
||||
let values = this.stats.map(function (v) {
|
||||
return {
|
||||
name: v.keys[0],
|
||||
value: v.value
|
||||
}
|
||||
})
|
||||
let that = this
|
||||
chart.setOption({
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: function (data) {
|
||||
let stat = that.stats[data.dataIndex]
|
||||
let percent = 0
|
||||
if (stat.total > 0) {
|
||||
percent = Math.round((stat.value * 100 / stat.total) * 100) / 100
|
||||
}
|
||||
return stat.keys[0] + ": " + stat.value + ",占比:" + percent + "%"
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: name,
|
||||
type: "pie",
|
||||
data: values,
|
||||
areaStyle: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
renderTimeBar: function (chart) {
|
||||
this.stats.$sort(function (v1, v2) {
|
||||
return (v1.time < v2.time) ? -1 : 1
|
||||
})
|
||||
let values = this.stats.map(function (v) {
|
||||
return v.value
|
||||
})
|
||||
let that = this
|
||||
chart.setOption({
|
||||
xAxis: {
|
||||
data: this.stats.map(function (v) {
|
||||
return that.formatTime(v.time)
|
||||
})
|
||||
},
|
||||
yAxis: {},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: function (data) {
|
||||
let stat = that.stats[data.dataIndex]
|
||||
return that.formatTime(stat.time) + ": " + stat.value
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
top: 10,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: name,
|
||||
type: "bar",
|
||||
data: values,
|
||||
itemStyle: {
|
||||
color: "#9DD3E8"
|
||||
},
|
||||
areaStyle: {},
|
||||
barWidth: "20em"
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
renderTimeLine: function (chart) {
|
||||
this.stats.$sort(function (v1, v2) {
|
||||
return (v1.time < v2.time) ? -1 : 1
|
||||
})
|
||||
let values = this.stats.map(function (v) {
|
||||
return v.value
|
||||
})
|
||||
let that = this
|
||||
chart.setOption({
|
||||
xAxis: {
|
||||
data: this.stats.map(function (v) {
|
||||
return that.formatTime(v.time)
|
||||
})
|
||||
},
|
||||
yAxis: {},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: function (data) {
|
||||
let stat = that.stats[data.dataIndex]
|
||||
return that.formatTime(stat.time) + ": " + stat.value
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
top: 10,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: name,
|
||||
type: "line",
|
||||
data: values,
|
||||
itemStyle: {
|
||||
color: "#9DD3E8"
|
||||
},
|
||||
areaStyle: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
renderBar: function (chart) {
|
||||
let values = this.stats.map(function (v) {
|
||||
return v.value
|
||||
})
|
||||
let that = this
|
||||
chart.setOption({
|
||||
xAxis: {
|
||||
data: this.stats.map(function (v) {
|
||||
return v.keys[0]
|
||||
}),
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: function (data) {
|
||||
let stat = that.stats[data.dataIndex]
|
||||
let percent = 0
|
||||
if (stat.total > 0) {
|
||||
percent = Math.round((stat.value * 100 / stat.total) * 100) / 100
|
||||
}
|
||||
return stat.keys[0] + ": " + stat.value + ",占比:" + percent + "%"
|
||||
}
|
||||
},
|
||||
yAxis: {},
|
||||
grid: {
|
||||
left: 40,
|
||||
top: 10,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: name,
|
||||
type: "bar",
|
||||
data: values,
|
||||
itemStyle: {
|
||||
color: "#9DD3E8"
|
||||
},
|
||||
areaStyle: {},
|
||||
barWidth: "20em"
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
renderTable: function (chart) {
|
||||
let table = `<table class="ui table celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>对象</th>
|
||||
<th>数值</th>
|
||||
<th>占比</th>
|
||||
</tr>
|
||||
</thead>`
|
||||
this.stats.forEach(function (v) {
|
||||
table += "<tr><td>" + v.keys[0] + "</td><td>" + v.value + "</td>"
|
||||
let percent = 0
|
||||
if (v.total > 0) {
|
||||
percent = Math.round((v.value * 100 / v.total) * 100) / 100
|
||||
}
|
||||
table += "<td><div class=\"ui progress blue\"><div class=\"bar\" style=\"min-width: 0; height: 4px; width: " + percent + "%\"></div></div>" + percent + "%</td>"
|
||||
table += "</tr>"
|
||||
})
|
||||
|
||||
table += `</table>`
|
||||
document.getElementById(this.chartId).innerHTML = table
|
||||
},
|
||||
formatTime: function (time) {
|
||||
switch (this.vPeriodUnit) {
|
||||
case "month":
|
||||
return time.substring(0, 4) + "-" + time.substring(4, 6)
|
||||
case "week":
|
||||
return time.substring(0, 4) + "-" + time.substring(4, 6)
|
||||
case "day":
|
||||
return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8)
|
||||
case "hour":
|
||||
return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + " " + time.substring(8, 10)
|
||||
case "minute":
|
||||
return time.substring(0, 4) + "-" + time.substring(4, 6) + "-" + time.substring(6, 8) + " " + time.substring(8, 10) + ":" + time.substring(10, 12)
|
||||
}
|
||||
return time
|
||||
}
|
||||
},
|
||||
template: `<div style="float: left" :style="{'width': width}">
|
||||
<div>{{chart.name}}</div>
|
||||
<div class="ui divider"></div>
|
||||
<div style="height: 20em; overflow-y: auto" :id="chartId" class="scroll-box"></div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("metric-board", {
|
||||
template: `<div><slot></slot></div>`
|
||||
})
|
||||
61
web/public/js/echarts/echarts.min.js
vendored
61
web/public/js/echarts/echarts.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -290,6 +290,11 @@ window.teaweb = {
|
||||
showConfirmButton: false
|
||||
});
|
||||
},
|
||||
successRefresh: function (message) {
|
||||
teaweb.success(message, function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
},
|
||||
warn: function (message, callback) {
|
||||
var width = "20em";
|
||||
if (message.length > 30) {
|
||||
|
||||
4
web/views/@default/@echarts.html
Normal file
4
web/views/@default/@echarts.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
@@ -669,4 +669,7 @@ td {
|
||||
.source-code-box .CodeMirror-vscrollbar::-webkit-scrollbar-thumb {
|
||||
border-radius: 2px;
|
||||
}
|
||||
.scroll-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
/*# sourceMappingURL=@layout.css.map */
|
||||
File diff suppressed because one or more lines are too long
@@ -744,4 +744,9 @@ td {
|
||||
.CodeMirror-vscrollbar::-webkit-scrollbar-thumb {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格
|
||||
.scroll-box::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
{$layout}
|
||||
{$template "../node_menu"}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
<h4>上行流量(字节)</h4>
|
||||
<div class="chart-box" id="traffic-in-chart"></div>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$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>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{$layout}
|
||||
{$template "item_menu"}
|
||||
|
||||
<warning-message>此功能暂未开通,敬等期待。</warning-message>
|
||||
@@ -0,0 +1,6 @@
|
||||
<second-menu>
|
||||
<menu-item :href="Tea.url('.', { itemId: item.id })">图表列表</menu-item>
|
||||
<span class="item disabled">|</span>
|
||||
<menu-item :href="Tea.url('.chart', {chartId: chart.id})" code="chartIndex">详情"{{chart.name}}"</menu-item>
|
||||
<menu-item :href="Tea.url('.update', {chartId: chart.id})" code="chartUpdate">修改</menu-item>
|
||||
</second-menu>
|
||||
45
web/views/@default/servers/metrics/charts/chart.html
Normal file
45
web/views/@default/servers/metrics/charts/chart.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{$layout}
|
||||
{$template "../item_menu"}
|
||||
{$template "chart_menu"}
|
||||
{$template "/echarts"}
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">图表名称</td>
|
||||
<td>
|
||||
{{chart.name}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>状态</td>
|
||||
<td>
|
||||
<label-on :v-is-on="chart.isOn"></label-on>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>图表类型</td>
|
||||
<td>
|
||||
{{chart.typeName}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>宽度</td>
|
||||
<td>
|
||||
<span v-if="chart.widthDiv == 0">100%</span>
|
||||
<span v-else>1/{{chart.widthDiv}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>对象数限制</td>
|
||||
<td>
|
||||
<span v-if="chart.maxItems <= 0" class="disabled">没有限制</span>
|
||||
<span v-else>{{chart.maxItems}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>图表示例</h4>
|
||||
<p class="comment"><span class="red">图中数据均为测试数据。</span></p>
|
||||
<metric-board>
|
||||
<metric-chart :v-chart="chart" :v-stats="testingStats" :v-period-unit="item.periodUnit"></metric-chart>
|
||||
</metric-board>
|
||||
63
web/views/@default/servers/metrics/charts/chart.js
Normal file
63
web/views/@default/servers/metrics/charts/chart.js
Normal file
@@ -0,0 +1,63 @@
|
||||
Tea.context(function () {
|
||||
this.format = function (v) {
|
||||
if (v == 0) {
|
||||
return "00"
|
||||
}
|
||||
if (v < 10) {
|
||||
return "0" + v
|
||||
}
|
||||
return v.toString()
|
||||
}
|
||||
|
||||
let randValues = []
|
||||
let times = []
|
||||
let count = 6
|
||||
for (let i = 0; i < count; i++) {
|
||||
randValues.push(Math.ceil(Math.random() * 100))
|
||||
switch (this.item.periodUnit) {
|
||||
case "month": {
|
||||
let date = new Date()
|
||||
date.setMonth(date.getMonth() - (count - 1 - i))
|
||||
let month = date.getMonth() + 1
|
||||
times.push(date.getFullYear() + this.format(month))
|
||||
}
|
||||
break
|
||||
case "week": {
|
||||
let date = new Date()
|
||||
times.push(date.getFullYear() + this.format(50 + i - count))
|
||||
}
|
||||
break
|
||||
case "day": {
|
||||
let date = new Date()
|
||||
date.setDate(date.getDate() - (count - i - 1))
|
||||
let day = date.getDate()
|
||||
times.push(date.getFullYear() + this.format(date.getMonth() + 1) + this.format(day))
|
||||
}
|
||||
break
|
||||
case "hour": {
|
||||
let date = new Date()
|
||||
date.setHours(date.getHours() - (count - i - 1))
|
||||
times.push(date.getFullYear() + this.format(date.getMonth() + 1) + this.format(date.getDate()) + this.format(date.getHours()))
|
||||
}
|
||||
break
|
||||
case "minute": {
|
||||
let date = new Date()
|
||||
date.setMinutes(date.getMinutes() - (count - i - 1))
|
||||
times.push(date.getFullYear() + this.format(date.getMonth() + 1) + this.format(date.getDate()) + this.format(date.getHours()) + this.format(date.getMinutes()))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
let total = randValues.$sum()
|
||||
|
||||
this.testingStats = []
|
||||
let that = this
|
||||
randValues.forEach(function (v, index) {
|
||||
that.testingStats.push({
|
||||
keys: ["对象" + (index + 1)],
|
||||
value: v,
|
||||
total: total,
|
||||
time: times[index]
|
||||
})
|
||||
})
|
||||
})
|
||||
49
web/views/@default/servers/metrics/charts/createPopup.html
Normal file
49
web/views/@default/servers/metrics/charts/createPopup.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>创建图表</h3>
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="itemId" :value="itemId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">图表名称 *</td>
|
||||
<td>
|
||||
<input type="text" maxlength="100" name="name" ref="focus"/>
|
||||
<p class="comment">也会作为有些图表的标题。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>图表类型 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="type" v-model="type" @change="changeType">
|
||||
<option v-for="type in types" :value="type.code">{{type.name}}</option>
|
||||
</select>
|
||||
<p class="comment" v-if="typeDefinition != null"><i class="icon" :class="typeDefinition.icon"></i> {{typeDefinition.description}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>宽度</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="widthDiv">
|
||||
<option value="0">100%</option>
|
||||
<option value="2">1/2</option>
|
||||
<option value="3">1/3</option>
|
||||
<option value="4">1/4</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>对象数限制</td>
|
||||
<td>
|
||||
<input type="text" name="maxItems" maxlength="2" style="width: 4em"/>
|
||||
<p class="comment">在图表中能显示的最多对象数,0表示不限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
15
web/views/@default/servers/metrics/charts/createPopup.js
Normal file
15
web/views/@default/servers/metrics/charts/createPopup.js
Normal file
@@ -0,0 +1,15 @@
|
||||
Tea.context(function () {
|
||||
this.type = this.types[0].code
|
||||
this.typeDefinition = null
|
||||
|
||||
this.$delay(function () {
|
||||
this.changeType()
|
||||
})
|
||||
|
||||
this.changeType = function () {
|
||||
let that = this
|
||||
this.typeDefinition = this.types.$find(function (k, v) {
|
||||
return v.code == that.type
|
||||
})
|
||||
}
|
||||
})
|
||||
35
web/views/@default/servers/metrics/charts/index.html
Normal file
35
web/views/@default/servers/metrics/charts/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{$layout}
|
||||
{$template "../item_menu"}
|
||||
|
||||
<second-menu>
|
||||
<menu-item @click.prevent="createChart">[创建图表]</menu-item>
|
||||
</second-menu>
|
||||
|
||||
|
||||
<p class="comment" v-if="charts.length == 0">暂时还没有图表。</p>
|
||||
<table class="ui table celled selectable" v-if="charts.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>图表名称</th>
|
||||
<th>类型</th>
|
||||
<th>宽度</th>
|
||||
<th class="two wide">状态</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="chart in charts">
|
||||
<td>{{chart.name}}</td>
|
||||
<td>{{chart.typeName}}</td>
|
||||
<td>
|
||||
<span v-if="chart.widthDiv > 0">1/{{chart.widthDiv}}</span>
|
||||
<span v-else>1</span>
|
||||
</td>
|
||||
<td><label-on :v-is-on="chart.isOn"></label-on></td>
|
||||
<td>
|
||||
<a :href="Tea.url('.chart', {chartId: chart.id})">详情</a>
|
||||
<a href="" @click.prevent="deleteChart(chart.id)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
21
web/views/@default/servers/metrics/charts/index.js
Normal file
21
web/views/@default/servers/metrics/charts/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
Tea.context(function () {
|
||||
this.createChart = function () {
|
||||
teaweb.popup(Tea.url(".createPopup?itemId=" + this.item.id), {
|
||||
callback: function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
},
|
||||
height: "27em"
|
||||
})
|
||||
}
|
||||
|
||||
this.deleteChart = function (chartId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除这个图表吗?", function () {
|
||||
that.$post(".delete")
|
||||
.params({ chartId: chartId })
|
||||
.success(function () {
|
||||
teaweb.successRefresh("保存成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
54
web/views/@default/servers/metrics/charts/update.html
Normal file
54
web/views/@default/servers/metrics/charts/update.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{$layout}
|
||||
{$template "../item_menu"}
|
||||
{$template "chart_menu"}
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="chartId" :value="chart.id"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">图表名称 *</td>
|
||||
<td>
|
||||
<input type="text" maxlength="100" name="name" ref="focus" v-model="chart.name"/>
|
||||
<p class="comment">也会作为有些图表的标题。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>图表类型 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="type" v-model="type" @change="changeType">
|
||||
<option v-for="type in types" :value="type.code">{{type.name}}</option>
|
||||
</select>
|
||||
<p class="comment" v-if="typeDefinition != null"><i class="icon" :class="typeDefinition.icon"></i> {{typeDefinition.description}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>宽度</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="widthDiv" v-model="chart.widthDiv">
|
||||
<option value="0">100%</option>
|
||||
<option value="2">1/2</option>
|
||||
<option value="3">1/3</option>
|
||||
<option value="4">1/4</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>是否启用</td>
|
||||
<td><checkbox name="isOn" value="1" v-model="chart.isOn"></checkbox></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><more-options-indicator></more-options-indicator></td>
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td>对象数限制</td>
|
||||
<td>
|
||||
<input type="text" name="maxItems" maxlength="2" style="width: 4em" v-model="chart.maxItems"/>
|
||||
<p class="comment">在图表中能显示的最多对象数,0表示不限制。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
17
web/views/@default/servers/metrics/charts/update.js
Normal file
17
web/views/@default/servers/metrics/charts/update.js
Normal file
@@ -0,0 +1,17 @@
|
||||
Tea.context(function () {
|
||||
this.type = this.chart.type
|
||||
this.typeDefinition = null
|
||||
|
||||
this.$delay(function () {
|
||||
this.changeType()
|
||||
})
|
||||
|
||||
this.changeType = function () {
|
||||
let that = this
|
||||
this.typeDefinition = this.types.$find(function (k, v) {
|
||||
return v.code == that.type
|
||||
})
|
||||
}
|
||||
|
||||
this.success = NotifySuccess("保存成功", Tea.url(".chart", {chartId: this.chart.id}))
|
||||
})
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- echart -->
|
||||
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
|
||||
{$end}
|
||||
{$template "/echarts"}
|
||||
|
||||
{$template "/left_menu"}
|
||||
<div class="right-box">
|
||||
|
||||
Reference in New Issue
Block a user