Merge pull request #1316 from influxdata/feature/cheese-curd-templates
Add CRUD interface for templatespull/10616/head
commit
ad53bb020d
|
@ -9,6 +9,7 @@
|
|||
1. [#1292](https://github.com/influxdata/chronograf/pull/1292): Introduce Template Variable Manager
|
||||
1. [#1232](https://github.com/influxdata/chronograf/pull/1232): Fuse the query builder and raw query editor
|
||||
1. [#1286](https://github.com/influxdata/chronograf/pull/1286): Add refreshing JWTs for authentication
|
||||
1. [#1316](https://github.com/influxdata/chronograf/pull/1316): Add templates API scoped within a dashboard
|
||||
|
||||
|
||||
### UI Improvements
|
||||
|
|
|
@ -188,11 +188,41 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
|
|||
Type: c.Type,
|
||||
}
|
||||
}
|
||||
templates := make([]*Template, len(d.Templates))
|
||||
for i, t := range d.Templates {
|
||||
vals := make([]*TemplateValue, len(t.Values))
|
||||
for j, v := range t.Values {
|
||||
vals[j] = &TemplateValue{
|
||||
Selected: v.Selected,
|
||||
Type: v.Type,
|
||||
Value: v.Value,
|
||||
}
|
||||
}
|
||||
|
||||
template := &Template{
|
||||
ID: string(t.ID),
|
||||
TempVar: t.Var,
|
||||
Values: vals,
|
||||
Type: t.Type,
|
||||
Label: t.Label,
|
||||
}
|
||||
if t.Query != nil {
|
||||
template.Query = &TemplateQuery{
|
||||
Command: t.Query.Command,
|
||||
Db: t.Query.DB,
|
||||
Rp: t.Query.RP,
|
||||
Measurement: t.Query.Measurement,
|
||||
TagKey: t.Query.TagKey,
|
||||
FieldKey: t.Query.FieldKey,
|
||||
}
|
||||
}
|
||||
templates[i] = template
|
||||
}
|
||||
return proto.Marshal(&Dashboard{
|
||||
ID: int64(d.ID),
|
||||
Cells: cells,
|
||||
Name: d.Name,
|
||||
ID: int64(d.ID),
|
||||
Cells: cells,
|
||||
Templates: templates,
|
||||
Name: d.Name,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -230,8 +260,44 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
|
|||
Type: c.Type,
|
||||
}
|
||||
}
|
||||
|
||||
templates := make([]chronograf.Template, len(pb.Templates))
|
||||
for i, t := range pb.Templates {
|
||||
vals := make([]chronograf.TemplateValue, len(t.Values))
|
||||
for j, v := range t.Values {
|
||||
vals[j] = chronograf.TemplateValue{
|
||||
Selected: v.Selected,
|
||||
Type: v.Type,
|
||||
Value: v.Value,
|
||||
}
|
||||
}
|
||||
|
||||
template := chronograf.Template{
|
||||
ID: chronograf.TemplateID(t.ID),
|
||||
TemplateVar: chronograf.TemplateVar{
|
||||
Var: t.TempVar,
|
||||
Values: vals,
|
||||
},
|
||||
Type: t.Type,
|
||||
Label: t.Label,
|
||||
}
|
||||
|
||||
if t.Query != nil {
|
||||
template.Query = &chronograf.TemplateQuery{
|
||||
Command: t.Query.Command,
|
||||
DB: t.Query.Db,
|
||||
RP: t.Query.Rp,
|
||||
Measurement: t.Query.Measurement,
|
||||
TagKey: t.Query.TagKey,
|
||||
FieldKey: t.Query.FieldKey,
|
||||
}
|
||||
}
|
||||
templates[i] = template
|
||||
}
|
||||
|
||||
d.ID = chronograf.DashboardID(pb.ID)
|
||||
d.Cells = cells
|
||||
d.Templates = templates
|
||||
d.Name = pb.Name
|
||||
return nil
|
||||
}
|
||||
|
@ -298,7 +364,7 @@ func UnmarshalUser(data []byte, u *chronograf.User) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalUser decodes a user from binary protobuf data.
|
||||
// UnmarshalUserPB decodes a user from binary protobuf data.
|
||||
// We are ignoring the password for now.
|
||||
func UnmarshalUserPB(data []byte, u *User) error {
|
||||
if err := proto.Unmarshal(data, u); err != nil {
|
||||
|
|
|
@ -12,6 +12,9 @@ It has these top-level messages:
|
|||
Source
|
||||
Dashboard
|
||||
DashboardCell
|
||||
Template
|
||||
TemplateValue
|
||||
TemplateQuery
|
||||
Server
|
||||
Layout
|
||||
Cell
|
||||
|
@ -56,9 +59,10 @@ func (*Source) ProtoMessage() {}
|
|||
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
|
||||
|
||||
type Dashboard struct {
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells" json:"cells,omitempty"`
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells" json:"cells,omitempty"`
|
||||
Templates []*Template `protobuf:"bytes,4,rep,name=templates" json:"templates,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Dashboard) Reset() { *m = Dashboard{} }
|
||||
|
@ -73,6 +77,13 @@ func (m *Dashboard) GetCells() []*DashboardCell {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Dashboard) GetTemplates() []*Template {
|
||||
if m != nil {
|
||||
return m.Templates
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DashboardCell struct {
|
||||
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"`
|
||||
|
@ -96,6 +107,59 @@ func (m *DashboardCell) GetQueries() []*Query {
|
|||
return nil
|
||||
}
|
||||
|
||||
type Template struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"`
|
||||
Values []*TemplateValue `protobuf:"bytes,3,rep,name=values" json:"values,omitempty"`
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Label string `protobuf:"bytes,5,opt,name=label,proto3" json:"label,omitempty"`
|
||||
Query *TemplateQuery `protobuf:"bytes,6,opt,name=query" json:"query,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Template) Reset() { *m = Template{} }
|
||||
func (m *Template) String() string { return proto.CompactTextString(m) }
|
||||
func (*Template) ProtoMessage() {}
|
||||
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
|
||||
|
||||
func (m *Template) GetValues() []*TemplateValue {
|
||||
if m != nil {
|
||||
return m.Values
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Template) GetQuery() *TemplateQuery {
|
||||
if m != nil {
|
||||
return m.Query
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TemplateValue struct {
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TemplateValue) Reset() { *m = TemplateValue{} }
|
||||
func (m *TemplateValue) String() string { return proto.CompactTextString(m) }
|
||||
func (*TemplateValue) ProtoMessage() {}
|
||||
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
|
||||
|
||||
type TemplateQuery struct {
|
||||
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"`
|
||||
Rp string `protobuf:"bytes,3,opt,name=rp,proto3" json:"rp,omitempty"`
|
||||
Measurement string `protobuf:"bytes,4,opt,name=measurement,proto3" json:"measurement,omitempty"`
|
||||
TagKey string `protobuf:"bytes,5,opt,name=tag_key,json=tagKey,proto3" json:"tag_key,omitempty"`
|
||||
FieldKey string `protobuf:"bytes,6,opt,name=field_key,json=fieldKey,proto3" json:"field_key,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TemplateQuery) Reset() { *m = TemplateQuery{} }
|
||||
func (m *TemplateQuery) String() string { return proto.CompactTextString(m) }
|
||||
func (*TemplateQuery) ProtoMessage() {}
|
||||
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
|
||||
|
||||
type Server struct {
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
|
@ -108,7 +172,7 @@ type Server struct {
|
|||
func (m *Server) Reset() { *m = Server{} }
|
||||
func (m *Server) String() string { return proto.CompactTextString(m) }
|
||||
func (*Server) ProtoMessage() {}
|
||||
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
|
||||
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
|
||||
|
||||
type Layout struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
|
@ -121,7 +185,7 @@ type Layout struct {
|
|||
func (m *Layout) Reset() { *m = Layout{} }
|
||||
func (m *Layout) String() string { return proto.CompactTextString(m) }
|
||||
func (*Layout) ProtoMessage() {}
|
||||
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
|
||||
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
|
||||
|
||||
func (m *Layout) GetCells() []*Cell {
|
||||
if m != nil {
|
||||
|
@ -146,7 +210,7 @@ type Cell struct {
|
|||
func (m *Cell) Reset() { *m = Cell{} }
|
||||
func (m *Cell) String() string { return proto.CompactTextString(m) }
|
||||
func (*Cell) ProtoMessage() {}
|
||||
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
|
||||
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
|
||||
|
||||
func (m *Cell) GetQueries() []*Query {
|
||||
if m != nil {
|
||||
|
@ -168,7 +232,7 @@ type Query struct {
|
|||
func (m *Query) Reset() { *m = Query{} }
|
||||
func (m *Query) String() string { return proto.CompactTextString(m) }
|
||||
func (*Query) ProtoMessage() {}
|
||||
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
|
||||
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
|
||||
|
||||
func (m *Query) GetRange() *Range {
|
||||
if m != nil {
|
||||
|
@ -185,7 +249,7 @@ type Range struct {
|
|||
func (m *Range) Reset() { *m = Range{} }
|
||||
func (m *Range) String() string { return proto.CompactTextString(m) }
|
||||
func (*Range) ProtoMessage() {}
|
||||
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
|
||||
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
|
||||
|
||||
type AlertRule struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
|
@ -197,7 +261,7 @@ type AlertRule struct {
|
|||
func (m *AlertRule) Reset() { *m = AlertRule{} }
|
||||
func (m *AlertRule) String() string { return proto.CompactTextString(m) }
|
||||
func (*AlertRule) ProtoMessage() {}
|
||||
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
|
||||
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
|
||||
|
||||
type User struct {
|
||||
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
|
@ -207,12 +271,15 @@ type User struct {
|
|||
func (m *User) Reset() { *m = User{} }
|
||||
func (m *User) String() string { return proto.CompactTextString(m) }
|
||||
func (*User) ProtoMessage() {}
|
||||
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
|
||||
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Source)(nil), "internal.Source")
|
||||
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
|
||||
proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell")
|
||||
proto.RegisterType((*Template)(nil), "internal.Template")
|
||||
proto.RegisterType((*TemplateValue)(nil), "internal.TemplateValue")
|
||||
proto.RegisterType((*TemplateQuery)(nil), "internal.TemplateQuery")
|
||||
proto.RegisterType((*Server)(nil), "internal.Server")
|
||||
proto.RegisterType((*Layout)(nil), "internal.Layout")
|
||||
proto.RegisterType((*Cell)(nil), "internal.Cell")
|
||||
|
@ -225,47 +292,58 @@ func init() {
|
|||
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
||||
|
||||
var fileDescriptorInternal = []byte{
|
||||
// 660 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x54, 0xdd, 0x6e, 0xd3, 0x4a,
|
||||
0x10, 0xd6, 0xc6, 0x76, 0x7e, 0xa6, 0x3d, 0x3d, 0x47, 0xab, 0x23, 0x58, 0x71, 0x15, 0x59, 0x20,
|
||||
0x05, 0x24, 0x7a, 0x41, 0x9f, 0xa0, 0xad, 0x25, 0x14, 0x68, 0x4b, 0xd9, 0xb4, 0x70, 0x05, 0xd2,
|
||||
0x36, 0x9d, 0x34, 0x16, 0x8e, 0x6d, 0xd6, 0x36, 0xa9, 0x5f, 0x01, 0xf1, 0x0c, 0x3c, 0x00, 0x97,
|
||||
0xbc, 0x0a, 0x2f, 0x84, 0x66, 0x77, 0xed, 0xb8, 0xa2, 0xa0, 0x5e, 0x71, 0x37, 0xdf, 0xcc, 0x66,
|
||||
0x7e, 0xbe, 0xef, 0x73, 0x60, 0x27, 0x4e, 0x4b, 0xd4, 0xa9, 0x4a, 0x76, 0x73, 0x9d, 0x95, 0x19,
|
||||
0x1f, 0x36, 0x38, 0xfc, 0xdc, 0x83, 0xfe, 0x2c, 0xab, 0xf4, 0x1c, 0xf9, 0x0e, 0xf4, 0xa6, 0x91,
|
||||
0x60, 0x63, 0x36, 0xf1, 0x64, 0x6f, 0x1a, 0x71, 0x0e, 0xfe, 0x89, 0x5a, 0xa1, 0xe8, 0x8d, 0xd9,
|
||||
0x64, 0x24, 0x4d, 0x4c, 0xb9, 0xb3, 0x3a, 0x47, 0xe1, 0xd9, 0x1c, 0xc5, 0xfc, 0x01, 0x0c, 0xcf,
|
||||
0x0b, 0xea, 0xb6, 0x42, 0xe1, 0x9b, 0x7c, 0x8b, 0xa9, 0x76, 0xaa, 0x8a, 0x62, 0x9d, 0xe9, 0x4b,
|
||||
0x11, 0xd8, 0x5a, 0x83, 0xf9, 0x7f, 0xe0, 0x9d, 0xcb, 0x23, 0xd1, 0x37, 0x69, 0x0a, 0xb9, 0x80,
|
||||
0x41, 0x84, 0x0b, 0x55, 0x25, 0xa5, 0x18, 0x8c, 0xd9, 0x64, 0x28, 0x1b, 0x48, 0x7d, 0xce, 0x30,
|
||||
0xc1, 0x2b, 0xad, 0x16, 0x62, 0x68, 0xfb, 0x34, 0x98, 0xef, 0x02, 0x9f, 0xa6, 0x05, 0xce, 0x2b,
|
||||
0x8d, 0xb3, 0x0f, 0x71, 0xfe, 0x06, 0x75, 0xbc, 0xa8, 0xc5, 0xc8, 0x34, 0xb8, 0xa5, 0x42, 0x53,
|
||||
0x8e, 0xb1, 0x54, 0x34, 0x1b, 0x4c, 0xab, 0x06, 0x86, 0xef, 0x61, 0x14, 0xa9, 0x62, 0x79, 0x91,
|
||||
0x29, 0x7d, 0x79, 0x27, 0x3a, 0x9e, 0x42, 0x30, 0xc7, 0x24, 0x29, 0x84, 0x37, 0xf6, 0x26, 0x5b,
|
||||
0xcf, 0xee, 0xef, 0xb6, 0x3c, 0xb7, 0x7d, 0x0e, 0x31, 0x49, 0xa4, 0x7d, 0x15, 0x7e, 0x63, 0xf0,
|
||||
0xcf, 0x8d, 0x02, 0xdf, 0x06, 0x76, 0x6d, 0x66, 0x04, 0x92, 0x5d, 0x13, 0xaa, 0x4d, 0xff, 0x40,
|
||||
0xb2, 0x9a, 0xd0, 0xda, 0x10, 0x1d, 0x48, 0xb6, 0x26, 0xb4, 0x34, 0xf4, 0x06, 0x92, 0x2d, 0xf9,
|
||||
0x63, 0x18, 0x7c, 0xac, 0x50, 0xc7, 0x58, 0x88, 0xc0, 0x8c, 0xfe, 0x77, 0x33, 0xfa, 0x75, 0x85,
|
||||
0xba, 0x96, 0x4d, 0x9d, 0xf6, 0x36, 0xd2, 0x58, 0x9e, 0x4d, 0x4c, 0xb9, 0x92, 0x64, 0x1c, 0xd8,
|
||||
0x1c, 0xc5, 0xee, 0x5e, 0x4b, 0x6e, 0x6f, 0x1a, 0x85, 0x5f, 0x18, 0xf4, 0x67, 0xa8, 0x3f, 0xa1,
|
||||
0xbe, 0x13, 0x15, 0x5d, 0x17, 0x78, 0x7f, 0x70, 0x81, 0x7f, 0xbb, 0x0b, 0x82, 0x8d, 0x0b, 0xfe,
|
||||
0x87, 0x60, 0xa6, 0xe7, 0xd3, 0xc8, 0x6c, 0xec, 0x49, 0x0b, 0xc2, 0xaf, 0x0c, 0xfa, 0x47, 0xaa,
|
||||
0xce, 0xaa, 0xb2, 0xb3, 0x8e, 0xd9, 0x94, 0x8f, 0x61, 0x6b, 0x3f, 0xcf, 0x93, 0x78, 0xae, 0xca,
|
||||
0x38, 0x4b, 0xdd, 0x56, 0xdd, 0x14, 0xbd, 0x38, 0x46, 0x55, 0x54, 0x1a, 0x57, 0x98, 0x96, 0x6e,
|
||||
0xbf, 0x6e, 0x8a, 0x3f, 0x84, 0xe0, 0xd0, 0x28, 0xe9, 0x1b, 0x3a, 0x77, 0x36, 0x74, 0x5a, 0x01,
|
||||
0x4d, 0x91, 0x0e, 0xd9, 0xaf, 0xca, 0x6c, 0x91, 0x64, 0x6b, 0xb3, 0xf1, 0x50, 0xb6, 0x38, 0xfc,
|
||||
0xc1, 0xc0, 0xff, 0x5b, 0x9a, 0x6e, 0x03, 0x8b, 0x9d, 0xa0, 0x2c, 0x6e, 0x15, 0x1e, 0x74, 0x14,
|
||||
0x16, 0x30, 0xa8, 0xb5, 0x4a, 0xaf, 0xb0, 0x10, 0xc3, 0xb1, 0x37, 0xf1, 0x64, 0x03, 0x4d, 0x25,
|
||||
0x51, 0x17, 0x98, 0x14, 0x62, 0x34, 0xf6, 0xc8, 0xfe, 0x0e, 0xb6, 0xae, 0x80, 0x8d, 0x2b, 0xc2,
|
||||
0xef, 0x0c, 0x02, 0x33, 0x9c, 0x7e, 0x77, 0x98, 0xad, 0x56, 0x2a, 0xbd, 0x74, 0xd4, 0x37, 0x90,
|
||||
0xf4, 0x88, 0x0e, 0x1c, 0xed, 0xbd, 0xe8, 0x80, 0xb0, 0x3c, 0x75, 0x24, 0xf7, 0xe4, 0x29, 0xb1,
|
||||
0xf6, 0x5c, 0x67, 0x55, 0x7e, 0x50, 0x5b, 0x7a, 0x47, 0xb2, 0xc5, 0xfc, 0x1e, 0xf4, 0xdf, 0x2e,
|
||||
0x51, 0xbb, 0x9b, 0x47, 0xd2, 0x21, 0x32, 0xc1, 0x11, 0x6d, 0xe5, 0xae, 0xb4, 0x80, 0x3f, 0x82,
|
||||
0x40, 0xd2, 0x15, 0xe6, 0xd4, 0x1b, 0x04, 0x99, 0xb4, 0xb4, 0xd5, 0x70, 0xcf, 0x3d, 0xa3, 0x2e,
|
||||
0xe7, 0x79, 0x8e, 0xda, 0x79, 0xd7, 0x02, 0xd3, 0x3b, 0x5b, 0xa3, 0x36, 0x2b, 0x7b, 0xd2, 0x82,
|
||||
0xf0, 0x1d, 0x8c, 0xf6, 0x13, 0xd4, 0xa5, 0xac, 0x12, 0xfc, 0xc5, 0x62, 0x1c, 0xfc, 0x17, 0xb3,
|
||||
0x57, 0x27, 0x8d, 0xe3, 0x29, 0xde, 0xf8, 0xd4, 0xeb, 0xf8, 0x94, 0x0e, 0x7a, 0xa9, 0x72, 0x35,
|
||||
0x8d, 0x8c, 0xb0, 0x9e, 0x74, 0x28, 0x7c, 0x02, 0x3e, 0x7d, 0x0f, 0x9d, 0xce, 0xfe, 0xef, 0xbe,
|
||||
0xa5, 0x8b, 0xbe, 0xf9, 0x97, 0xde, 0xfb, 0x19, 0x00, 0x00, 0xff, 0xff, 0x93, 0x68, 0x0f, 0xcf,
|
||||
0xb7, 0x05, 0x00, 0x00,
|
||||
// 848 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xe3, 0x44,
|
||||
0x14, 0xd6, 0xc4, 0x76, 0x62, 0x9f, 0xdd, 0x2d, 0x68, 0xb4, 0x62, 0x0d, 0xdc, 0x44, 0x16, 0x48,
|
||||
0x01, 0x89, 0x82, 0xd8, 0x27, 0x68, 0x6b, 0x09, 0x85, 0x76, 0x97, 0x32, 0x69, 0xcb, 0x15, 0x5a,
|
||||
0x4d, 0x92, 0x93, 0xd6, 0xda, 0x49, 0x6c, 0xc6, 0x76, 0xb3, 0x7e, 0x05, 0xc4, 0x05, 0x4f, 0x80,
|
||||
0xc4, 0x2d, 0x97, 0xbc, 0x00, 0x0f, 0xc1, 0x0b, 0xa1, 0x33, 0x33, 0xfe, 0x89, 0xb6, 0xa0, 0xbd,
|
||||
0xe2, 0x6e, 0xbe, 0x73, 0x26, 0xdf, 0x9c, 0x9f, 0xef, 0x73, 0xe0, 0x28, 0xdb, 0x55, 0xa8, 0x77,
|
||||
0x52, 0x1d, 0x17, 0x3a, 0xaf, 0x72, 0x1e, 0xb6, 0x38, 0xf9, 0x79, 0x04, 0xe3, 0x45, 0x5e, 0xeb,
|
||||
0x15, 0xf2, 0x23, 0x18, 0xcd, 0xd3, 0x98, 0x4d, 0xd9, 0xcc, 0x13, 0xa3, 0x79, 0xca, 0x39, 0xf8,
|
||||
0x2f, 0xe5, 0x16, 0xe3, 0xd1, 0x94, 0xcd, 0x22, 0x61, 0xce, 0x14, 0xbb, 0x6a, 0x0a, 0x8c, 0x3d,
|
||||
0x1b, 0xa3, 0x33, 0xff, 0x08, 0xc2, 0xeb, 0x92, 0xd8, 0xb6, 0x18, 0xfb, 0x26, 0xde, 0x61, 0xca,
|
||||
0x5d, 0xca, 0xb2, 0xdc, 0xe7, 0x7a, 0x1d, 0x07, 0x36, 0xd7, 0x62, 0xfe, 0x3e, 0x78, 0xd7, 0xe2,
|
||||
0x22, 0x1e, 0x9b, 0x30, 0x1d, 0x79, 0x0c, 0x93, 0x14, 0x37, 0xb2, 0x56, 0x55, 0x3c, 0x99, 0xb2,
|
||||
0x59, 0x28, 0x5a, 0x48, 0x3c, 0x57, 0xa8, 0xf0, 0x56, 0xcb, 0x4d, 0x1c, 0x5a, 0x9e, 0x16, 0xf3,
|
||||
0x63, 0xe0, 0xf3, 0x5d, 0x89, 0xab, 0x5a, 0xe3, 0xe2, 0x75, 0x56, 0xdc, 0xa0, 0xce, 0x36, 0x4d,
|
||||
0x1c, 0x19, 0x82, 0x07, 0x32, 0xf4, 0xca, 0x0b, 0xac, 0x24, 0xbd, 0x0d, 0x86, 0xaa, 0x85, 0xc9,
|
||||
0xaf, 0x0c, 0xa2, 0x54, 0x96, 0x77, 0xcb, 0x5c, 0xea, 0xf5, 0x3b, 0xcd, 0xe3, 0x0b, 0x08, 0x56,
|
||||
0xa8, 0x54, 0x19, 0x7b, 0x53, 0x6f, 0xf6, 0xe8, 0xeb, 0x67, 0xc7, 0xdd, 0xa0, 0x3b, 0x9e, 0x33,
|
||||
0x54, 0x4a, 0xd8, 0x5b, 0xfc, 0x2b, 0x88, 0x2a, 0xdc, 0x16, 0x4a, 0x56, 0x58, 0xc6, 0xbe, 0xf9,
|
||||
0x09, 0xef, 0x7f, 0x72, 0xe5, 0x52, 0xa2, 0xbf, 0x94, 0xfc, 0xc1, 0xe0, 0xc9, 0x01, 0x15, 0x7f,
|
||||
0x0c, 0xec, 0x8d, 0xa9, 0x2a, 0x10, 0xec, 0x0d, 0xa1, 0xc6, 0x54, 0x14, 0x08, 0xd6, 0x10, 0xda,
|
||||
0x9b, 0xdd, 0x04, 0x82, 0xed, 0x09, 0xdd, 0x99, 0x8d, 0x04, 0x82, 0xdd, 0xf1, 0xcf, 0x60, 0xf2,
|
||||
0x53, 0x8d, 0x3a, 0xc3, 0x32, 0x0e, 0xcc, 0xcb, 0xef, 0xf5, 0x2f, 0x7f, 0x5f, 0xa3, 0x6e, 0x44,
|
||||
0x9b, 0xa7, 0x4e, 0xcd, 0x36, 0xed, 0x6a, 0xcc, 0x99, 0x62, 0x15, 0x6d, 0x7e, 0x62, 0x63, 0x74,
|
||||
0x76, 0x13, 0xb2, 0xfb, 0x18, 0xcd, 0xd3, 0xe4, 0x2f, 0x46, 0x6b, 0xb2, 0xa5, 0x0f, 0xc6, 0x67,
|
||||
0x92, 0xfc, 0x43, 0x08, 0xa9, 0xad, 0x57, 0xf7, 0x52, 0xbb, 0x11, 0x4e, 0x08, 0xdf, 0x48, 0xcd,
|
||||
0xbf, 0x84, 0xf1, 0xbd, 0x54, 0x35, 0x3e, 0x30, 0xc6, 0x96, 0xee, 0x86, 0xf2, 0xc2, 0x5d, 0xeb,
|
||||
0x8a, 0xf1, 0x07, 0xc5, 0x3c, 0x85, 0x40, 0xc9, 0x25, 0x2a, 0xa7, 0x33, 0x0b, 0x68, 0x41, 0xd4,
|
||||
0x55, 0x63, 0x7a, 0x79, 0x90, 0xd9, 0xf6, 0x6e, 0x6f, 0x25, 0xd7, 0xf0, 0xe4, 0xe0, 0xc5, 0xee,
|
||||
0x25, 0x76, 0xf8, 0x92, 0xa9, 0xc3, 0xb5, 0x61, 0x01, 0x49, 0xb4, 0x44, 0x85, 0xab, 0x0a, 0xd7,
|
||||
0x66, 0x05, 0xa1, 0xe8, 0x70, 0xf2, 0x3b, 0xeb, 0x79, 0xcd, 0x7b, 0x24, 0xc2, 0x55, 0xbe, 0xdd,
|
||||
0xca, 0xdd, 0xda, 0x51, 0xb7, 0x90, 0xe6, 0xb6, 0x5e, 0x3a, 0xea, 0xd1, 0x7a, 0x49, 0x58, 0x17,
|
||||
0xce, 0x70, 0x23, 0x5d, 0xf0, 0x29, 0x3c, 0xda, 0xa2, 0x2c, 0x6b, 0x8d, 0x5b, 0xdc, 0x55, 0x6e,
|
||||
0x04, 0xc3, 0x10, 0x7f, 0x06, 0x93, 0x4a, 0xde, 0xbe, 0x7a, 0x8d, 0x8d, 0x9b, 0xc5, 0xb8, 0x92,
|
||||
0xb7, 0xe7, 0xd8, 0xf0, 0x8f, 0x21, 0xda, 0x64, 0xa8, 0xd6, 0x26, 0x65, 0x97, 0x1b, 0x9a, 0xc0,
|
||||
0x39, 0x36, 0xc9, 0x2f, 0x0c, 0xc6, 0x0b, 0xd4, 0xf7, 0xa8, 0xdf, 0x49, 0xf9, 0x43, 0xd7, 0x7b,
|
||||
0xff, 0xe1, 0x7a, 0xff, 0x61, 0xd7, 0x07, 0xbd, 0xeb, 0x9f, 0x42, 0xb0, 0xd0, 0xab, 0x79, 0x6a,
|
||||
0x2a, 0xf2, 0x84, 0x05, 0xc9, 0x6f, 0x0c, 0xc6, 0x17, 0xb2, 0xc9, 0xeb, 0xea, 0x2d, 0x25, 0x4d,
|
||||
0xe1, 0xd1, 0x49, 0x51, 0xa8, 0x6c, 0x25, 0xab, 0x2c, 0xdf, 0xb9, 0xaa, 0x86, 0x21, 0xba, 0xf1,
|
||||
0x62, 0x30, 0x23, 0x5b, 0xdf, 0x30, 0xc4, 0x3f, 0x81, 0xe0, 0xcc, 0x18, 0xd7, 0xba, 0xf0, 0xa8,
|
||||
0xd7, 0x85, 0xf5, 0xab, 0x49, 0x52, 0x23, 0x27, 0x75, 0x95, 0x6f, 0x54, 0xbe, 0x37, 0x15, 0x87,
|
||||
0xa2, 0xc3, 0xc9, 0xdf, 0x0c, 0xfc, 0xff, 0xcb, 0x90, 0x8f, 0x81, 0x65, 0x6e, 0x61, 0x2c, 0xeb,
|
||||
0xec, 0x39, 0x19, 0xd8, 0x33, 0x86, 0x49, 0xa3, 0xe5, 0xee, 0x16, 0xcb, 0x38, 0x9c, 0x7a, 0x33,
|
||||
0x4f, 0xb4, 0xd0, 0x64, 0x8c, 0x17, 0xca, 0x38, 0x9a, 0x7a, 0xa4, 0x34, 0x07, 0x3b, 0x6d, 0x43,
|
||||
0xaf, 0xed, 0xe4, 0x4f, 0x06, 0x41, 0xa7, 0xd0, 0xb3, 0x43, 0x85, 0x9e, 0xf5, 0x0a, 0x4d, 0x4f,
|
||||
0x5b, 0x85, 0xa6, 0xa7, 0x84, 0xc5, 0x65, 0xab, 0x50, 0x71, 0x49, 0x53, 0xfb, 0x46, 0xe7, 0x75,
|
||||
0x71, 0xda, 0xd8, 0xf1, 0x46, 0xa2, 0xc3, 0xfc, 0x03, 0x18, 0xff, 0x70, 0x87, 0xda, 0xf5, 0x1c,
|
||||
0x09, 0x87, 0x48, 0x04, 0x17, 0xc6, 0xbd, 0xb6, 0x4b, 0x0b, 0xf8, 0xa7, 0x10, 0x08, 0xea, 0xc2,
|
||||
0xb4, 0x7a, 0x30, 0x20, 0x13, 0x16, 0x36, 0x9b, 0x3c, 0x77, 0xd7, 0x88, 0xe5, 0xba, 0x28, 0x50,
|
||||
0x3b, 0xed, 0x5a, 0x60, 0xb8, 0xf3, 0x3d, 0xda, 0xcf, 0x8e, 0x27, 0x2c, 0x48, 0x7e, 0x84, 0xe8,
|
||||
0x44, 0xa1, 0xae, 0x44, 0xad, 0xde, 0xfe, 0x58, 0x71, 0xf0, 0xbf, 0x5d, 0x7c, 0xf7, 0xb2, 0x55,
|
||||
0x3c, 0x9d, 0x7b, 0x9d, 0x7a, 0x03, 0x9d, 0x52, 0x43, 0xe7, 0xb2, 0x90, 0xf3, 0xd4, 0x2c, 0xd6,
|
||||
0x13, 0x0e, 0x25, 0x9f, 0x83, 0x4f, 0x7e, 0x18, 0x30, 0xfb, 0xff, 0xe6, 0xa5, 0xe5, 0xd8, 0xfc,
|
||||
0x2b, 0x3f, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff, 0xdd, 0x04, 0x69, 0xb6, 0xa7, 0x07, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ message Dashboard {
|
|||
int64 ID = 1; // ID is the unique ID of the dashboard
|
||||
string Name = 2; // Name is the user-defined name of the dashboard
|
||||
repeated DashboardCell cells = 3; // a representation of all visual data required for rendering the dashboard
|
||||
repeated Template templates = 4; // Templates replace template variables within InfluxQL
|
||||
}
|
||||
|
||||
message DashboardCell {
|
||||
|
@ -31,6 +32,30 @@ message DashboardCell {
|
|||
string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6
|
||||
}
|
||||
|
||||
message Template {
|
||||
string ID = 1; // ID is the unique ID associated with this template
|
||||
string temp_var = 2;
|
||||
repeated TemplateValue values = 3;
|
||||
string type = 4; // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
|
||||
string label = 5; // Label is a user-facing description of the Template
|
||||
TemplateQuery query = 6; // Query is used to generate the choices for a template
|
||||
}
|
||||
|
||||
message TemplateValue {
|
||||
string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
|
||||
string value = 2; // Value is the specific value used to replace a template in an InfluxQL query
|
||||
bool selected = 3; // Selected states that this variable has been picked to use for replacement
|
||||
}
|
||||
|
||||
message TemplateQuery {
|
||||
string command = 1; // Command is the query itself
|
||||
string db = 2; // DB the database for the query (optional)
|
||||
string rp = 3; // RP is a retention policy and optional;
|
||||
string measurement = 4; // Measurement is the optinally selected measurement for the query
|
||||
string tag_key = 5; // TagKey is the optionally selected tag key for the query
|
||||
string field_key = 6; // FieldKey is the optionally selected field key for the query
|
||||
}
|
||||
|
||||
message Server {
|
||||
int64 ID = 1; // ID is the unique ID of the server
|
||||
string Name = 2; // Name is the user-defined name for the server
|
||||
|
|
|
@ -125,14 +125,15 @@ type Range struct {
|
|||
|
||||
// TemplateValue is a value use to replace a template in an InfluxQL query
|
||||
type TemplateValue struct {
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query
|
||||
Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
|
||||
Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement
|
||||
}
|
||||
|
||||
// TemplateVar is a named variable within an InfluxQL query to be replaced with Values
|
||||
type TemplateVar struct {
|
||||
Var string `json:"tempVar"`
|
||||
Values []TemplateValue `json:"values"`
|
||||
Var string `json:"tempVar"` // Var is the string to replace within InfluxQL
|
||||
Values []TemplateValue `json:"values"` // Values are the replacement values within InfluxQL
|
||||
}
|
||||
|
||||
// String converts the template variable into a correct InfluxQL string based
|
||||
|
@ -142,23 +143,35 @@ func (t TemplateVar) String() string {
|
|||
return ""
|
||||
}
|
||||
switch t.Values[0].Type {
|
||||
case "tagKey", "fieldKey":
|
||||
case "tagKey", "fieldKey", "measurement", "database":
|
||||
return `"` + t.Values[0].Value + `"`
|
||||
case "tagValue":
|
||||
return `'` + t.Values[0].Value + `'`
|
||||
case "csv":
|
||||
case "csv", "constant":
|
||||
return t.Values[0].Value
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateID is the unique ID used to identify a template
|
||||
type TemplateID string
|
||||
|
||||
// Template represents a series of choices to replace TemplateVars within InfluxQL
|
||||
type Template struct {
|
||||
TemplateVar
|
||||
ID TemplateID `json:"id"` // ID is the unique ID associated with this template
|
||||
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
|
||||
Label string `json:"label"` // Label is a user-facing description of the Template
|
||||
Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
|
||||
}
|
||||
|
||||
// Query retrieves a Response from a TimeSeries.
|
||||
type Query struct {
|
||||
Command string `json:"query"` // Command is the query itself
|
||||
DB string `json:"db,omitempty"` // DB is optional and if empty will not be used.
|
||||
RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used.
|
||||
TemplateVars []TemplateVar `json:"tempVars"` // TemplateVars are template variables to replace within an InfluxQL query
|
||||
TemplateVars []TemplateVar `json:"tempVars,omitempty"` // TemplateVars are template variables to replace within an InfluxQL query
|
||||
Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes
|
||||
GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags
|
||||
Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data
|
||||
|
@ -174,6 +187,16 @@ type DashboardQuery struct {
|
|||
QueryConfig QueryConfig `json:"queryConfig,omitempty"` // QueryConfig represents the query state that is understood by the data explorer
|
||||
}
|
||||
|
||||
// TemplateQuery is used to retrieve choices for template replacement
|
||||
type TemplateQuery struct {
|
||||
Command string `json:"query"` // Command is the query itself
|
||||
DB string `json:"db,omitempty"` // DB is optional and if empty will not be used.
|
||||
RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used.
|
||||
Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query
|
||||
TagKey string `json:"tagKey"` // TagKey is the optionally selected tag key for the query
|
||||
FieldKey string `json:"fieldKey"` // FieldKey is the optionally selected field key for the query
|
||||
}
|
||||
|
||||
// Response is the result of a query against a TimeSeries
|
||||
type Response interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
|
@ -404,9 +427,10 @@ type DashboardID int
|
|||
|
||||
// Dashboard represents all visual and query data for a dashboard
|
||||
type Dashboard struct {
|
||||
ID DashboardID `json:"id"`
|
||||
Cells []DashboardCell `json:"cells"`
|
||||
Name string `json:"name"`
|
||||
ID DashboardID `json:"id"`
|
||||
Cells []DashboardCell `json:"cells"`
|
||||
Templates []Template `json:"templates"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// DashboardCell holds visual and query information for a cell
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultWidth is used if not specified
|
||||
DefaultWidth = 4
|
||||
// DefaultHeight is used if not specified
|
||||
DefaultHeight = 4
|
||||
)
|
||||
|
||||
type dashboardCellLinks struct {
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
}
|
||||
|
||||
type dashboardCellResponse struct {
|
||||
chronograf.DashboardCell
|
||||
Links dashboardCellLinks `json:"links"`
|
||||
}
|
||||
|
||||
func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardCell) []dashboardCellResponse {
|
||||
base := "/chronograf/v1/dashboards"
|
||||
cells := make([]dashboardCellResponse, len(dcells))
|
||||
for i, cell := range dcells {
|
||||
if len(cell.Queries) == 0 {
|
||||
cell.Queries = make([]chronograf.DashboardQuery, 0)
|
||||
}
|
||||
cells[i] = dashboardCellResponse{
|
||||
DashboardCell: cell,
|
||||
Links: dashboardCellLinks{
|
||||
Self: fmt.Sprintf("%s/%d/cells/%s", base, dID, cell.ID),
|
||||
},
|
||||
}
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
// ValidDashboardCellRequest verifies that the dashboard cells have a query
|
||||
func ValidDashboardCellRequest(c *chronograf.DashboardCell) error {
|
||||
CorrectWidthHeight(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CorrectWidthHeight changes the cell to have at least the
|
||||
// minimum width and height
|
||||
func CorrectWidthHeight(c *chronograf.DashboardCell) {
|
||||
if c.W < 1 {
|
||||
c.W = DefaultWidth
|
||||
}
|
||||
if c.H < 1 {
|
||||
c.H = DefaultHeight
|
||||
}
|
||||
}
|
||||
|
||||
// AddQueryConfig updates a cell by converting InfluxQL into queryconfigs
|
||||
// If influxql cannot be represented by a full query config, then, the
|
||||
// query config's raw text is set to the command.
|
||||
func AddQueryConfig(c *chronograf.DashboardCell) {
|
||||
for i, q := range c.Queries {
|
||||
qc := ToQueryConfig(q.Command)
|
||||
q.QueryConfig = qc
|
||||
c.Queries[i] = q
|
||||
}
|
||||
}
|
||||
|
||||
// DashboardCells returns all cells from a dashboard within the store
|
||||
func (s *Service) DashboardCells(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(e)
|
||||
cells := boards.Cells
|
||||
encodeJSON(w, http.StatusOK, cells, s.Logger)
|
||||
}
|
||||
|
||||
// NewDashboardCell adds a cell to an existing dashboard
|
||||
func (s *Service) NewDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
var cell chronograf.DashboardCell
|
||||
if err := json.NewDecoder(r.Body).Decode(&cell); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidDashboardCellRequest(&cell); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ids := uuid.V4{}
|
||||
cid, err := ids.Generate()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error creating cell ID of dashboard %d: %v", id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
cell.ID = cid
|
||||
|
||||
dash.Cells = append(dash.Cells, cell)
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error adding cell %s to dashboard %d: %v", cid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(dash)
|
||||
for _, cell := range boards.Cells {
|
||||
if cell.ID == cid {
|
||||
encodeJSON(w, http.StatusOK, cell, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DashboardCellID gets a specific cell from an existing dashboard
|
||||
func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(dash)
|
||||
cid := httprouter.GetParamFromContext(ctx, "cid")
|
||||
for _, cell := range boards.Cells {
|
||||
if cell.ID == cid {
|
||||
encodeJSON(w, http.StatusOK, cell, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
notFound(w, id, s.Logger)
|
||||
}
|
||||
|
||||
// RemoveDashboardCell removes a specific cell from an existing dashboard
|
||||
func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
cid := httprouter.GetParamFromContext(ctx, "cid")
|
||||
cellid := -1
|
||||
for i, cell := range dash.Cells {
|
||||
if cell.ID == cid {
|
||||
cellid = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cellid == -1 {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
dash.Cells = append(dash.Cells[:cellid], dash.Cells[cellid+1:]...)
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error removing cell %s from dashboard %d: %v", cid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ReplaceDashboardCell replaces a cell entirely within an existing dashboard
|
||||
func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
cid := httprouter.GetParamFromContext(ctx, "cid")
|
||||
cellid := -1
|
||||
for i, cell := range dash.Cells {
|
||||
if cell.ID == cid {
|
||||
cellid = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cellid == -1 {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var cell chronograf.DashboardCell
|
||||
if err := json.NewDecoder(r.Body).Decode(&cell); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidDashboardCellRequest(&cell); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
cell.ID = cid
|
||||
|
||||
dash.Cells[cellid] = cell
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error updating cell %s in dashboard %d: %v", cid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(dash)
|
||||
for _, cell := range boards.Cells {
|
||||
if cell.ID == cid {
|
||||
encodeJSON(w, http.StatusOK, cell, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,37 +5,21 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultWidth is used if not specified
|
||||
DefaultWidth = 4
|
||||
// DefaultHeight is used if not specified
|
||||
DefaultHeight = 4
|
||||
)
|
||||
|
||||
type dashboardLinks struct {
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
Cells string `json:"cells"` // Cells link to the cells endpoint
|
||||
}
|
||||
|
||||
type dashboardCellLinks struct {
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
}
|
||||
|
||||
type dashboardCellResponse struct {
|
||||
chronograf.DashboardCell
|
||||
Links dashboardCellLinks `json:"links"`
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
Cells string `json:"cells"` // Cells link to the cells endpoint
|
||||
Templates string `json:"templates"` // Templates link to the templates endpoint
|
||||
}
|
||||
|
||||
type dashboardResponse struct {
|
||||
ID chronograf.DashboardID `json:"id"`
|
||||
Cells []dashboardCellResponse `json:"cells"`
|
||||
Name string `json:"name"`
|
||||
Links dashboardLinks `json:"links"`
|
||||
ID chronograf.DashboardID `json:"id"`
|
||||
Cells []dashboardCellResponse `json:"cells"`
|
||||
Templates []templateResponse `json:"templates"`
|
||||
Name string `json:"name"`
|
||||
Links dashboardLinks `json:"links"`
|
||||
}
|
||||
|
||||
type getDashboardsResponse struct {
|
||||
|
@ -46,25 +30,18 @@ func newDashboardResponse(d chronograf.Dashboard) *dashboardResponse {
|
|||
base := "/chronograf/v1/dashboards"
|
||||
DashboardDefaults(&d)
|
||||
AddQueryConfigs(&d)
|
||||
cells := make([]dashboardCellResponse, len(d.Cells))
|
||||
for i, cell := range d.Cells {
|
||||
if len(cell.Queries) == 0 {
|
||||
cell.Queries = make([]chronograf.DashboardQuery, 0)
|
||||
}
|
||||
cells[i] = dashboardCellResponse{
|
||||
DashboardCell: cell,
|
||||
Links: dashboardCellLinks{
|
||||
Self: fmt.Sprintf("%s/%d/cells/%s", base, d.ID, cell.ID),
|
||||
},
|
||||
}
|
||||
}
|
||||
cells := newCellResponses(d.ID, d.Cells)
|
||||
templates := newTemplateResponses(d.ID, d.Templates)
|
||||
|
||||
return &dashboardResponse{
|
||||
ID: d.ID,
|
||||
Name: d.Name,
|
||||
Cells: cells,
|
||||
ID: d.ID,
|
||||
Name: d.Name,
|
||||
Cells: cells,
|
||||
Templates: templates,
|
||||
Links: dashboardLinks{
|
||||
Self: fmt.Sprintf("%s/%d", base, d.ID),
|
||||
Cells: fmt.Sprintf("%s/%d/cells", base, d.ID),
|
||||
Self: fmt.Sprintf("%s/%d", base, d.ID),
|
||||
Cells: fmt.Sprintf("%s/%d/cells", base, d.ID),
|
||||
Templates: fmt.Sprintf("%s/%d/templates", base, d.ID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +62,6 @@ func (s *Service) Dashboards(w http.ResponseWriter, r *http.Request) {
|
|||
for _, dashboard := range dashboards {
|
||||
res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard))
|
||||
}
|
||||
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
||||
|
||||
|
@ -243,19 +219,20 @@ func (s *Service) UpdateDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
// ValidDashboardRequest verifies that the dashboard cells have a query
|
||||
func ValidDashboardRequest(d *chronograf.Dashboard) error {
|
||||
for i, c := range d.Cells {
|
||||
CorrectWidthHeight(&c)
|
||||
if err := ValidDashboardCellRequest(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Cells[i] = c
|
||||
}
|
||||
for _, t := range d.Templates {
|
||||
if err := ValidTemplateRequest(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
DashboardDefaults(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidDashboardCellRequest verifies that the dashboard cells have a query
|
||||
func ValidDashboardCellRequest(c *chronograf.DashboardCell) error {
|
||||
CorrectWidthHeight(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DashboardDefaults updates the dashboard with the default values
|
||||
// if none are specified
|
||||
func DashboardDefaults(d *chronograf.Dashboard) {
|
||||
|
@ -265,17 +242,6 @@ func DashboardDefaults(d *chronograf.Dashboard) {
|
|||
}
|
||||
}
|
||||
|
||||
// CorrectWidthHeight changes the cell to have at least the
|
||||
// minimum width and height
|
||||
func CorrectWidthHeight(c *chronograf.DashboardCell) {
|
||||
if c.W < 1 {
|
||||
c.W = DefaultWidth
|
||||
}
|
||||
if c.H < 1 {
|
||||
c.H = DefaultHeight
|
||||
}
|
||||
}
|
||||
|
||||
// AddQueryConfigs updates all the celsl in the dashboard to have query config
|
||||
// objects corresponding to their influxql queries.
|
||||
func AddQueryConfigs(d *chronograf.Dashboard) {
|
||||
|
@ -284,203 +250,3 @@ func AddQueryConfigs(d *chronograf.Dashboard) {
|
|||
d.Cells[i] = c
|
||||
}
|
||||
}
|
||||
|
||||
// AddQueryConfig updates a cell by converting InfluxQL into queryconfigs
|
||||
// If influxql cannot be represented by a full query config, then, the
|
||||
// query config's raw text is set to the command.
|
||||
func AddQueryConfig(c *chronograf.DashboardCell) {
|
||||
for i, q := range c.Queries {
|
||||
qc := ToQueryConfig(q.Command)
|
||||
q.QueryConfig = qc
|
||||
c.Queries[i] = q
|
||||
}
|
||||
}
|
||||
|
||||
// DashboardCells returns all cells from a dashboard within the store
|
||||
func (s *Service) DashboardCells(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(e)
|
||||
cells := boards.Cells
|
||||
encodeJSON(w, http.StatusOK, cells, s.Logger)
|
||||
}
|
||||
|
||||
// NewDashboardCell adds a cell to an existing dashboard
|
||||
func (s *Service) NewDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
var cell chronograf.DashboardCell
|
||||
if err := json.NewDecoder(r.Body).Decode(&cell); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidDashboardCellRequest(&cell); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ids := uuid.V4{}
|
||||
cid, err := ids.Generate()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error creating cell ID of dashboard %d: %v", id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
cell.ID = cid
|
||||
|
||||
dash.Cells = append(dash.Cells, cell)
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error adding cell %s to dashboard %d: %v", cid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(dash)
|
||||
for _, cell := range boards.Cells {
|
||||
if cell.ID == cid {
|
||||
encodeJSON(w, http.StatusOK, cell, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DashboardCellID adds a cell to an existing dashboard
|
||||
func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(dash)
|
||||
cid := httprouter.GetParamFromContext(ctx, "cid")
|
||||
for _, cell := range boards.Cells {
|
||||
if cell.ID == cid {
|
||||
encodeJSON(w, http.StatusOK, cell, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
notFound(w, id, s.Logger)
|
||||
}
|
||||
|
||||
// RemoveDashboardCell adds a cell to an existing dashboard
|
||||
func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
cid := httprouter.GetParamFromContext(ctx, "cid")
|
||||
cellid := -1
|
||||
for i, cell := range dash.Cells {
|
||||
if cell.ID == cid {
|
||||
cellid = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cellid == -1 {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
dash.Cells = append(dash.Cells[:cellid], dash.Cells[cellid+1:]...)
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error removing cell %s from dashboard %d: %v", cid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ReplaceDashboardCell adds a cell to an existing dashboard
|
||||
func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
cid := httprouter.GetParamFromContext(ctx, "cid")
|
||||
cellid := -1
|
||||
for i, cell := range dash.Cells {
|
||||
if cell.ID == cid {
|
||||
cellid = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cellid == -1 {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var cell chronograf.DashboardCell
|
||||
if err := json.NewDecoder(r.Body).Decode(&cell); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidDashboardCellRequest(&cell); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
cell.ID = cid
|
||||
|
||||
dash.Cells[cellid] = cell
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error updating cell %s in dashboard %d: %v", cid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
boards := newDashboardResponse(dash)
|
||||
for _, cell := range boards.Cells {
|
||||
if cell.ID == cid {
|
||||
encodeJSON(w, http.StatusOK, cell, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -233,6 +235,7 @@ func Test_newDashboardResponse(t *testing.T) {
|
|||
},
|
||||
},
|
||||
want: &dashboardResponse{
|
||||
Templates: []templateResponse{},
|
||||
Cells: []dashboardCellResponse{
|
||||
dashboardCellResponse{
|
||||
Links: dashboardCellLinks{
|
||||
|
@ -289,8 +292,9 @@ func Test_newDashboardResponse(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Links: dashboardLinks{
|
||||
Self: "/chronograf/v1/dashboards/0",
|
||||
Cells: "/chronograf/v1/dashboards/0/cells",
|
||||
Self: "/chronograf/v1/dashboards/0",
|
||||
Cells: "/chronograf/v1/dashboards/0/cells",
|
||||
Templates: "/chronograf/v1/dashboards/0/templates",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -155,6 +155,13 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
router.GET("/chronograf/v1/dashboards/:id/cells/:cid", service.DashboardCellID)
|
||||
router.DELETE("/chronograf/v1/dashboards/:id/cells/:cid", service.RemoveDashboardCell)
|
||||
router.PUT("/chronograf/v1/dashboards/:id/cells/:cid", service.ReplaceDashboardCell)
|
||||
// Dashboard Templates
|
||||
router.GET("/chronograf/v1/dashboards/:id/templates", service.Templates)
|
||||
router.POST("/chronograf/v1/dashboards/:id/templates", service.NewTemplate)
|
||||
|
||||
router.GET("/chronograf/v1/dashboards/:id/templates/:tid", service.TemplateID)
|
||||
router.DELETE("/chronograf/v1/dashboards/:id/templates/:tid", service.RemoveTemplate)
|
||||
router.PUT("/chronograf/v1/dashboards/:id/templates/:tid", service.ReplaceTemplate)
|
||||
|
||||
// Databases
|
||||
router.GET("/chronograf/v1/sources/:id/dbs", service.GetDatabases)
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/uuid"
|
||||
)
|
||||
|
||||
// ValidTemplateRequest checks if the request sent to the server is the correct format.
|
||||
func ValidTemplateRequest(template *chronograf.Template) error {
|
||||
switch template.Type {
|
||||
default:
|
||||
return fmt.Errorf("Unknown template type %s", template.Type)
|
||||
case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases":
|
||||
}
|
||||
|
||||
for _, v := range template.Values {
|
||||
switch v.Type {
|
||||
default:
|
||||
return fmt.Errorf("Unknown template variable type %s", v.Type)
|
||||
case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant":
|
||||
}
|
||||
}
|
||||
|
||||
if template.Type == "query" && template.Query == nil {
|
||||
return fmt.Errorf("No query set for template of type 'query'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type templateLinks struct {
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
}
|
||||
|
||||
type templateResponse struct {
|
||||
chronograf.Template
|
||||
Links templateLinks `json:"links"`
|
||||
}
|
||||
|
||||
func newTemplateResponses(dID chronograf.DashboardID, tmps []chronograf.Template) []templateResponse {
|
||||
res := make([]templateResponse, len(tmps))
|
||||
for i, t := range tmps {
|
||||
res[i] = newTemplateResponse(dID, t)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type templatesResponses struct {
|
||||
Templates []templateResponse `json:"templates"`
|
||||
}
|
||||
|
||||
func newTemplateResponse(dID chronograf.DashboardID, tmp chronograf.Template) templateResponse {
|
||||
base := "/chronograf/v1/dashboards"
|
||||
return templateResponse{
|
||||
Template: tmp,
|
||||
Links: templateLinks{
|
||||
Self: fmt.Sprintf("%s/%d/templates/%s", base, dID, tmp.ID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Templates returns all templates from a dashboard within the store
|
||||
func (s *Service) Templates(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
d, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
res := templatesResponses{
|
||||
Templates: newTemplateResponses(chronograf.DashboardID(id), d.Templates),
|
||||
}
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
||||
|
||||
// NewTemplate adds a template to an existing dashboard
|
||||
func (s *Service) NewTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var template chronograf.Template
|
||||
if err := json.NewDecoder(r.Body).Decode(&template); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidTemplateRequest(&template); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ids := uuid.V4{}
|
||||
tid, err := ids.Generate()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error creating template ID for dashboard %d: %v", id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
template.ID = chronograf.TemplateID(tid)
|
||||
|
||||
dash.Templates = append(dash.Templates, template)
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error adding template %s to dashboard %d: %v", tid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
res := newTemplateResponse(dash.ID, template)
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
||||
|
||||
// TemplateID retrieves a specific template from a dashboard
|
||||
func (s *Service) TemplateID(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
tid := httprouter.GetParamFromContext(ctx, "tid")
|
||||
for _, t := range dash.Templates {
|
||||
if t.ID == chronograf.TemplateID(tid) {
|
||||
res := newTemplateResponse(chronograf.DashboardID(id), t)
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
notFound(w, id, s.Logger)
|
||||
}
|
||||
|
||||
// RemoveTemplate removes a specific template from an existing dashboard
|
||||
func (s *Service) RemoveTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
tid := httprouter.GetParamFromContext(ctx, "tid")
|
||||
pos := -1
|
||||
for i, t := range dash.Templates {
|
||||
if t.ID == chronograf.TemplateID(tid) {
|
||||
pos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if pos == -1 {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
dash.Templates = append(dash.Templates[:pos], dash.Templates[pos+1:]...)
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error removing template %s from dashboard %d: %v", tid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ReplaceTemplate replaces a template entirely within an existing dashboard
|
||||
func (s *Service) ReplaceTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
dash, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
|
||||
if err != nil {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
tid := httprouter.GetParamFromContext(ctx, "tid")
|
||||
pos := -1
|
||||
for i, t := range dash.Templates {
|
||||
if t.ID == chronograf.TemplateID(tid) {
|
||||
pos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if pos == -1 {
|
||||
notFound(w, id, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
var template chronograf.Template
|
||||
if err := json.NewDecoder(r.Body).Decode(&template); err != nil {
|
||||
invalidJSON(w, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ValidTemplateRequest(&template); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
template.ID = chronograf.TemplateID(tid)
|
||||
|
||||
dash.Templates[pos] = template
|
||||
if err := s.DashboardsStore.Update(ctx, dash); err != nil {
|
||||
msg := fmt.Sprintf("Error updating template %s in dashboard %d: %v", tid, id, err)
|
||||
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
res := newTemplateResponse(chronograf.DashboardID(id), template)
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func TestValidTemplateRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
template *chronograf.Template
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Template",
|
||||
template: &chronograf.Template{
|
||||
Type: "fieldKeys",
|
||||
TemplateVar: chronograf.TemplateVar{
|
||||
Values: []chronograf.TemplateValue{
|
||||
{
|
||||
Type: "fieldKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid Template Type",
|
||||
wantErr: true,
|
||||
template: &chronograf.Template{
|
||||
Type: "Unknown Type",
|
||||
TemplateVar: chronograf.TemplateVar{
|
||||
Values: []chronograf.TemplateValue{
|
||||
{
|
||||
Type: "fieldKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid Template Variable Type",
|
||||
wantErr: true,
|
||||
template: &chronograf.Template{
|
||||
Type: "csv",
|
||||
TemplateVar: chronograf.TemplateVar{
|
||||
Values: []chronograf.TemplateValue{
|
||||
{
|
||||
Type: "unknown value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "No query set",
|
||||
wantErr: true,
|
||||
template: &chronograf.Template{
|
||||
Type: "query",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := ValidTemplateRequest(tt.template); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidTemplateRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -120,8 +120,8 @@ class RowWrapper extends Component {
|
|||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
const code = e.target.code.value
|
||||
const label = e.target.label.value
|
||||
// const code = e.target.code.value
|
||||
// const label = e.target.label.value
|
||||
|
||||
// updateTempVarsAsync({code, label})
|
||||
}
|
||||
|
|
|
@ -187,11 +187,11 @@ export default function ui(state = initialState, action) {
|
|||
d =>
|
||||
(d.id === dashboardID
|
||||
? {
|
||||
...d,
|
||||
templates: d.templates.map(
|
||||
t => (t.id === templateID ? {...t, ...updates} : t)
|
||||
),
|
||||
}
|
||||
...d,
|
||||
templates: d.templates.map(
|
||||
t => (t.id === templateID ? {...t, ...updates} : t)
|
||||
),
|
||||
}
|
||||
: d)
|
||||
)
|
||||
return {...state, dashboards}
|
||||
|
|
Loading…
Reference in New Issue