mirror of
https://github.com/cuigh/swirl
synced 2024-12-29 07:12:11 +00:00
Refactor dashboard management
This commit is contained in:
parent
f4546d0888
commit
d5b5ff114a
@ -1348,6 +1348,13 @@ var Swirl;
|
|||||||
this.charts.push(g);
|
this.charts.push(g);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Core.Dispatcher.bind(this.$panel).on("remove-chart", e => {
|
||||||
|
let name = $(e.target).closest("div.column").data("chart-name");
|
||||||
|
Core.Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", dlg => {
|
||||||
|
this.removeGraph(name);
|
||||||
|
dlg.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
$(window).resize(e => {
|
$(window).resize(e => {
|
||||||
$.each(this.charts, (i, g) => {
|
$.each(this.charts, (i, g) => {
|
||||||
g.resize(0, 0);
|
g.resize(0, 0);
|
||||||
@ -1409,7 +1416,7 @@ var Swirl;
|
|||||||
this.loadData();
|
this.loadData();
|
||||||
}
|
}
|
||||||
removeGraph(name) {
|
removeGraph(name) {
|
||||||
let index;
|
let index = -1;
|
||||||
for (let i = 0; i < this.charts.length; i++) {
|
for (let i = 0; i < this.charts.length; i++) {
|
||||||
let c = this.charts[i];
|
let c = this.charts[i];
|
||||||
if (c.getName() === name) {
|
if (c.getName() === name) {
|
||||||
@ -1417,7 +1424,13 @@ var Swirl;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.loadData();
|
if (index >= 0) {
|
||||||
|
console.info(this.charts.length);
|
||||||
|
let $elem = this.charts[index].getElem();
|
||||||
|
this.charts.splice(index, 1);
|
||||||
|
$elem.remove();
|
||||||
|
console.info(this.charts.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
save() {
|
save() {
|
||||||
let charts = this.charts.map(c => {
|
let charts = this.charts.map(c => {
|
||||||
@ -1432,9 +1445,12 @@ var Swirl;
|
|||||||
key: this.opts.key || '',
|
key: this.opts.key || '',
|
||||||
charts: charts,
|
charts: charts,
|
||||||
};
|
};
|
||||||
$ajax.post(`/system/chart/save_panel`, args).json((r) => {
|
$ajax.post(`/system/chart/save_dashboard`, args).json((r) => {
|
||||||
if (!r.success) {
|
if (r.success) {
|
||||||
Core.Modal.alert(r.message);
|
Core.Notification.show("success", "Successfully saved.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Core.Notification.show("danger", r.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2343,6 +2359,9 @@ var Swirl;
|
|||||||
$("#btn-add").click(() => {
|
$("#btn-add").click(() => {
|
||||||
Modal.alert("Coming soon...");
|
Modal.alert("Coming soon...");
|
||||||
});
|
});
|
||||||
|
$("#btn-save").click(() => {
|
||||||
|
Modal.alert("Coming soon...");
|
||||||
|
});
|
||||||
$cb_time.change(e => {
|
$cb_time.change(e => {
|
||||||
this.panel.setTime($(e.target).val());
|
this.panel.setTime($(e.target).val());
|
||||||
});
|
});
|
||||||
|
File diff suppressed because one or more lines are too long
@ -320,6 +320,13 @@ namespace Swirl.Core {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Dispatcher.bind(this.$panel).on("remove-chart", e => {
|
||||||
|
let name = $(e.target).closest("div.column").data("chart-name");
|
||||||
|
Modal.confirm(`Are you sure to delete chart: <strong>${name}</strong>?`, "Delete chart", dlg => {
|
||||||
|
this.removeGraph(name);
|
||||||
|
dlg.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
$(window).resize(e => {
|
$(window).resize(e => {
|
||||||
$.each(this.charts, (i: number, g: Graph) => {
|
$.each(this.charts, (i: number, g: Graph) => {
|
||||||
g.resize(0, 0);
|
g.resize(0, 0);
|
||||||
@ -356,7 +363,7 @@ namespace Swirl.Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addGraph(c: any) {
|
addGraph(c: any) {
|
||||||
for (let i =0; i< this.charts.length; i++) {
|
for (let i = 0; i < this.charts.length; i++) {
|
||||||
let chart = this.charts[i];
|
let chart = this.charts[i];
|
||||||
if (chart.getName() === c.name) {
|
if (chart.getName() === c.name) {
|
||||||
// chart already added.
|
// chart already added.
|
||||||
@ -393,15 +400,22 @@ namespace Swirl.Core {
|
|||||||
|
|
||||||
removeGraph(name: string) {
|
removeGraph(name: string) {
|
||||||
// todo:
|
// todo:
|
||||||
let index:number;
|
let index = -1;
|
||||||
for (let i =0; i< this.charts.length; i++) {
|
for (let i = 0; i < this.charts.length; i++) {
|
||||||
let c = this.charts[i];
|
let c = this.charts[i];
|
||||||
if (c.getName() === name) {
|
if (c.getName() === name) {
|
||||||
index = i;
|
index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.loadData();
|
|
||||||
|
if (index >= 0) {
|
||||||
|
console.info(this.charts.length);
|
||||||
|
let $elem = this.charts[index].getElem();
|
||||||
|
this.charts.splice(index, 1);
|
||||||
|
$elem.remove();
|
||||||
|
console.info(this.charts.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@ -418,9 +432,11 @@ namespace Swirl.Core {
|
|||||||
key: this.opts.key || '',
|
key: this.opts.key || '',
|
||||||
charts: charts,
|
charts: charts,
|
||||||
};
|
};
|
||||||
$ajax.post(`/system/chart/save_panel`, args).json<AjaxResult>((r: AjaxResult) => {
|
$ajax.post(`/system/chart/save_dashboard`, args).json<AjaxResult>((r: AjaxResult) => {
|
||||||
if (!r.success) {
|
if (r.success) {
|
||||||
Modal.alert(r.message);
|
Notification.show("success", "Successfully saved.");
|
||||||
|
} else {
|
||||||
|
Notification.show("danger", r.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,9 @@ namespace Swirl.Service {
|
|||||||
$("#btn-add").click(() => {
|
$("#btn-add").click(() => {
|
||||||
Modal.alert("Coming soon...");
|
Modal.alert("Coming soon...");
|
||||||
});
|
});
|
||||||
|
$("#btn-save").click(() => {
|
||||||
|
Modal.alert("Coming soon...");
|
||||||
|
});
|
||||||
$cb_time.change(e => {
|
$cb_time.change(e => {
|
||||||
this.panel.setTime($(e.target).val());
|
this.panel.setTime($(e.target).val());
|
||||||
});
|
});
|
||||||
|
28
biz/chart.go
28
biz/chart.go
@ -99,20 +99,34 @@ func (b *chartBiz) GetServiceCharts(name string) (charts []*model.Chart, err err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: gocyclo
|
func (b *chartBiz) GetDashboard(name, key string) (dashboard *model.ChartDashboard, err error) {
|
||||||
func (b *chartBiz) Panel(panel model.ChartPanel) (charts []*model.Chart, err error) {
|
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
if len(panel.Charts) == 0 {
|
dashboard, err = d.DashboardGet(name, key)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *chartBiz) UpdateDashboard(dashboard *model.ChartDashboard, user web.User) (err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
err = d.DashboardUpdate(dashboard)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: gocyclo
|
||||||
|
func (b *chartBiz) GetDashboardCharts(dashboard *model.ChartDashboard) (charts []*model.Chart, err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
if len(dashboard.Charts) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
names := make([]string, len(panel.Charts))
|
names := make([]string, len(dashboard.Charts))
|
||||||
for i, c := range panel.Charts {
|
for i, c := range dashboard.Charts {
|
||||||
names[i] = c.Name
|
names[i] = c.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
var cs []*model.Chart
|
var cs []*model.Chart
|
||||||
cs, err = d.ChartBatch(names...)
|
cs, err = b.getCharts(names)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -122,7 +136,7 @@ func (b *chartBiz) Panel(panel model.ChartPanel) (charts []*model.Chart, err err
|
|||||||
for _, c := range cs {
|
for _, c := range cs {
|
||||||
m[c.Name] = c
|
m[c.Name] = c
|
||||||
}
|
}
|
||||||
for _, c := range panel.Charts {
|
for _, c := range dashboard.Charts {
|
||||||
if chart := m[c.Name]; chart != nil {
|
if chart := m[c.Name]; chart != nil {
|
||||||
if c.Width > 0 {
|
if c.Width > 0 {
|
||||||
chart.Width = c.Width
|
chart.Width = c.Width
|
||||||
|
@ -33,13 +33,3 @@ func (b *settingBiz) Update(setting *model.Setting, user web.User) (err error) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *settingBiz) UpdateDashboard(name string, dashboard *model.ChartPanel, user web.User) (err error) {
|
|
||||||
do(func(d dao.Interface) {
|
|
||||||
err = d.UpdateDashboard(name, dashboard)
|
|
||||||
if err == nil {
|
|
||||||
Event.CreateSetting(model.EventActionUpdate, user)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cuigh/auxo/data"
|
||||||
"github.com/cuigh/auxo/errors"
|
"github.com/cuigh/auxo/errors"
|
||||||
"github.com/cuigh/auxo/net/web"
|
"github.com/cuigh/auxo/net/web"
|
||||||
"github.com/cuigh/auxo/util/cast"
|
"github.com/cuigh/auxo/util/cast"
|
||||||
@ -13,29 +14,29 @@ import (
|
|||||||
|
|
||||||
// ChartController is a controller of metric chart.
|
// ChartController is a controller of metric chart.
|
||||||
type ChartController struct {
|
type ChartController struct {
|
||||||
List web.HandlerFunc `path:"/" name:"chart.list" authorize:"!" desc:"chart list page"`
|
List web.HandlerFunc `path:"/" name:"chart.list" authorize:"!" desc:"chart list page"`
|
||||||
Query web.HandlerFunc `path:"/query" name:"chart.query" authorize:"?" desc:"chart query"`
|
Query web.HandlerFunc `path:"/query" name:"chart.query" authorize:"?" desc:"chart query"`
|
||||||
New web.HandlerFunc `path:"/new" name:"chart.new" authorize:"!" desc:"new chart page"`
|
New web.HandlerFunc `path:"/new" name:"chart.new" authorize:"!" desc:"new chart page"`
|
||||||
Create web.HandlerFunc `path:"/new" method:"post" name:"chart.create" authorize:"!" desc:"create chart"`
|
Create web.HandlerFunc `path:"/new" method:"post" name:"chart.create" authorize:"!" desc:"create chart"`
|
||||||
Edit web.HandlerFunc `path:"/:name/edit" name:"chart.edit" authorize:"!" desc:"edit chart page"`
|
Edit web.HandlerFunc `path:"/:name/edit" name:"chart.edit" authorize:"!" desc:"edit chart page"`
|
||||||
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"chart.delete" authorize:"!" desc:"delete chart"`
|
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"chart.delete" authorize:"!" desc:"delete chart"`
|
||||||
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"chart.update" authorize:"!" desc:"update chart"`
|
Update web.HandlerFunc `path:"/:name/edit" method:"post" name:"chart.update" authorize:"!" desc:"update chart"`
|
||||||
Data web.HandlerFunc `path:"/data" name:"chart.data" authorize:"?" desc:"fetch chart datas"`
|
Data web.HandlerFunc `path:"/data" name:"chart.data" authorize:"?" desc:"fetch chart datas"`
|
||||||
SavePanel web.HandlerFunc `path:"/save_panel" method:"post" name:"chart.save_panel" authorize:"!" desc:"save panel"`
|
SaveDashboard web.HandlerFunc `path:"/save_dashboard" method:"post" name:"chart.save_dashboard" authorize:"!" desc:"save dashboard"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chart creates an instance of RoleController
|
// Chart creates an instance of RoleController
|
||||||
func Chart() (c *ChartController) {
|
func Chart() (c *ChartController) {
|
||||||
return &ChartController{
|
return &ChartController{
|
||||||
List: chartList,
|
List: chartList,
|
||||||
Query: chartQuery,
|
Query: chartQuery,
|
||||||
New: chartNew,
|
New: chartNew,
|
||||||
Create: chartCreate,
|
Create: chartCreate,
|
||||||
Edit: chartEdit,
|
Edit: chartEdit,
|
||||||
Update: chartUpdate,
|
Update: chartUpdate,
|
||||||
Delete: chartDelete,
|
Delete: chartDelete,
|
||||||
Data: chartData,
|
Data: chartData,
|
||||||
SavePanel: chartSavePanel,
|
SaveDashboard: chartSaveDashboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,31 +116,30 @@ func chartDelete(ctx web.Context) error {
|
|||||||
|
|
||||||
func chartData(ctx web.Context) error {
|
func chartData(ctx web.Context) error {
|
||||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||||
charts := strings.Split(ctx.Q("charts"), ",")
|
if v := ctx.Q("charts"); v != "" {
|
||||||
key := ctx.Q("key")
|
names := strings.Split(v, ",")
|
||||||
datas, err := biz.Chart.FetchDatas(key, charts, period)
|
key := ctx.Q("key")
|
||||||
if err != nil {
|
datas, err := biz.Chart.FetchDatas(key, names, period)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctx.JSON(datas)
|
||||||
}
|
}
|
||||||
return ctx.JSON(datas)
|
return ctx.JSON(data.Map{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func chartSavePanel(ctx web.Context) error {
|
func chartSaveDashboard(ctx web.Context) error {
|
||||||
data := struct {
|
dashboard := &model.ChartDashboard{}
|
||||||
Name string `json:"name"`
|
err := ctx.Bind(dashboard)
|
||||||
Key string `json:"key"`
|
|
||||||
model.ChartPanel
|
|
||||||
}{}
|
|
||||||
err := ctx.Bind(&data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch data.Name {
|
switch dashboard.Name {
|
||||||
case "home":
|
case "home":
|
||||||
err = biz.Setting.UpdateDashboard(data.Name, &data.ChartPanel, ctx.User())
|
err = biz.Chart.UpdateDashboard(dashboard, ctx.User())
|
||||||
default:
|
default:
|
||||||
err = errors.New("unknown dashboard: " + data.Name)
|
err = errors.New("unknown dashboard: " + dashboard.Name)
|
||||||
}
|
}
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,10 @@ func Home() (c *HomeController) {
|
|||||||
|
|
||||||
func homeIndex(ctx web.Context) (err error) {
|
func homeIndex(ctx web.Context) (err error) {
|
||||||
var (
|
var (
|
||||||
count int
|
count int
|
||||||
setting *model.Setting
|
dashboard *model.ChartDashboard
|
||||||
charts []*model.Chart
|
charts []*model.Chart
|
||||||
m = newModel(ctx)
|
m = newModel(ctx)
|
||||||
)
|
)
|
||||||
|
|
||||||
if count, err = docker.NodeCount(); err != nil {
|
if count, err = docker.NodeCount(); err != nil {
|
||||||
@ -58,10 +58,10 @@ func homeIndex(ctx web.Context) (err error) {
|
|||||||
}
|
}
|
||||||
m.Set("StackCount", count)
|
m.Set("StackCount", count)
|
||||||
|
|
||||||
if setting, err = biz.Setting.Get(); err != nil {
|
if dashboard, err = biz.Chart.GetDashboard("home", ""); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
charts, err = biz.Chart.Panel(setting.Dashboard.Home)
|
charts, err = biz.Chart.GetDashboardCharts(dashboard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,6 @@ type Interface interface {
|
|||||||
|
|
||||||
SettingGet() (setting *model.Setting, err error)
|
SettingGet() (setting *model.Setting, err error)
|
||||||
SettingUpdate(setting *model.Setting) error
|
SettingUpdate(setting *model.Setting) error
|
||||||
UpdateDashboard(name string, dashboard *model.ChartPanel) error
|
|
||||||
|
|
||||||
ChartGet(name string) (*model.Chart, error)
|
ChartGet(name string) (*model.Chart, error)
|
||||||
ChartBatch(names ...string) ([]*model.Chart, error)
|
ChartBatch(names ...string) ([]*model.Chart, error)
|
||||||
@ -70,6 +69,9 @@ type Interface interface {
|
|||||||
ChartCreate(chart *model.Chart) error
|
ChartCreate(chart *model.Chart) error
|
||||||
ChartUpdate(chart *model.Chart) error
|
ChartUpdate(chart *model.Chart) error
|
||||||
ChartDelete(name string) error
|
ChartDelete(name string) error
|
||||||
|
|
||||||
|
DashboardGet(name, key string) (dashboard *model.ChartDashboard, err error)
|
||||||
|
DashboardUpdate(dashboard *model.ChartDashboard) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get return a dao instance according to DB_TYPE.
|
// Get return a dao instance according to DB_TYPE.
|
||||||
|
@ -63,3 +63,29 @@ func (d *Dao) ChartDelete(name string) (err error) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Dao) DashboardGet(name, key string) (dashboard *model.ChartDashboard, err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
dashboard = &model.ChartDashboard{
|
||||||
|
Name: name,
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
err = db.C("dashboard").FindId(dashboard.ID()).One(dashboard)
|
||||||
|
if err == mgo.ErrNotFound {
|
||||||
|
dashboard, err = nil, nil
|
||||||
|
} else if err != nil {
|
||||||
|
dashboard = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) DashboardUpdate(dashboard *model.ChartDashboard) (err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
update := bson.M{
|
||||||
|
"$set": dashboard,
|
||||||
|
}
|
||||||
|
_, err = db.C("dashboard").UpsertId(dashboard.ID(), update)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -28,17 +28,3 @@ func (d *Dao) SettingUpdate(setting *model.Setting) (err error) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dao) UpdateDashboard(name string, dashboard *model.ChartPanel) (err error) {
|
|
||||||
d.do(func(db *database) {
|
|
||||||
update := bson.M{
|
|
||||||
"$set": bson.M{
|
|
||||||
"dashboard": bson.M{
|
|
||||||
name: dashboard,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = db.C("setting").UpdateId(settingID, update)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -43,13 +43,20 @@ type ChartItem struct {
|
|||||||
Colors []string `json:"colors"`
|
Colors []string `json:"colors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChartPanel struct {
|
type ChartDashboard struct {
|
||||||
Refresh bool `json:"refresh"`
|
Name string `json:"name"`
|
||||||
Period int32 `json:"period"` // minutes
|
Key string `json:"key"`
|
||||||
Charts []ChartItem `json:"charts"`
|
Period int32 `json:"period"` // minutes
|
||||||
|
RefreshInterval int32 `json:"refresh_interval"` // seconds, 0 means disabled.
|
||||||
|
Charts []ChartItem `json:"charts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//type ChartPanel []ChartItem
|
func (cd *ChartDashboard) ID() string {
|
||||||
|
if cd.Key == "" {
|
||||||
|
return cd.Name
|
||||||
|
}
|
||||||
|
return cd.Name + ":" + cd.Key
|
||||||
|
}
|
||||||
|
|
||||||
type ChartPoint struct {
|
type ChartPoint struct {
|
||||||
X int64 `json:"x"`
|
X int64 `json:"x"`
|
||||||
|
@ -47,9 +47,6 @@ type Setting struct {
|
|||||||
Metrics struct {
|
Metrics struct {
|
||||||
Prometheus string `bson:"prometheus" json:"prometheus"`
|
Prometheus string `bson:"prometheus" json:"prometheus"`
|
||||||
} `bson:"metrics" json:"metrics"`
|
} `bson:"metrics" json:"metrics"`
|
||||||
Dashboard struct {
|
|
||||||
Home ChartPanel `bson:"home" json:"home"`
|
|
||||||
} `bson:"dashboard" json:"dashboard"`
|
|
||||||
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
||||||
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
|
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,7 @@ var Perms = []PermGroup{
|
|||||||
{Key: "chart.create", Text: "Create"},
|
{Key: "chart.create", Text: "Create"},
|
||||||
{Key: "chart.delete", Text: "Delete"},
|
{Key: "chart.delete", Text: "Delete"},
|
||||||
{Key: "chart.update", Text: "Update"},
|
{Key: "chart.update", Text: "Update"},
|
||||||
|
{Key: "chart.save_dashboard", Text: "Save dashboard"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -87,16 +87,16 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title">{{ .Title }}</p>
|
<p class="card-header-title">{{ .Title }}</p>
|
||||||
<a data-action="remove-chart" class="card-header-icon is-paddingless" aria-label="remove chart">
|
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
|
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a data-action="edit-options" class="card-header-icon" aria-label="edit options">
|
{*<a data-action="edit-options" class="card-header-icon" aria-label="edit options">*}
|
||||||
<span class="icon">
|
{*<span class="icon">*}
|
||||||
<i class="fas fa-ellipsis-h has-text-info" aria-hidden="true"></i>
|
{*<i class="fas fa-ellipsis-h has-text-info" aria-hidden="true"></i>*}
|
||||||
</span>
|
{*</span>*}
|
||||||
</a>
|
{*</a>*}
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div style="height: {{ .Height }}px">
|
<div style="height: {{ .Height }}px">
|
||||||
|
Loading…
Reference in New Issue
Block a user