实现基本的图表管理

This commit is contained in:
GoEdgeLab
2021-07-03 15:44:49 +08:00
parent 48dcbe0cde
commit 068f05535a
42 changed files with 1010 additions and 99 deletions

View File

@@ -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())
}

View File

@@ -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()
}

View 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()
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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()
}

View 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()
}

View 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()
})
}

View File

@@ -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()
}

View File

@@ -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()
})

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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,

View 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>`
})

File diff suppressed because one or more lines are too long

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
{$var "header"}
<!-- echart -->
<script type="text/javascript" src="/js/echarts/echarts.min.js"></script>
{$end}

View File

@@ -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

View File

@@ -744,4 +744,9 @@ td {
.CodeMirror-vscrollbar::-webkit-scrollbar-thumb {
border-radius: 2px;
}
}
// 表格
.scroll-box::-webkit-scrollbar {
width: 4px;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,4 +0,0 @@
{$layout}
{$template "item_menu"}
<warning-message>此功能暂未开通,敬等期待。</warning-message>

View File

@@ -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>

View 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>

View 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]
})
})
})

View 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>

View 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
})
}
})

View 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> &nbsp;
<a href="" @click.prevent="deleteChart(chart.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View 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("保存成功")
})
})
}
})

View 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>

View 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}))
})

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">