Merge pull request #1316 from influxdata/feature/cheese-curd-templates

Add CRUD interface for templates
pull/10616/head
Chris Goller 2017-04-20 16:32:54 -05:00 committed by GitHub
commit ad53bb020d
13 changed files with 887 additions and 336 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

261
server/cells.go Normal file
View File

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

View File

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

View File

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

View File

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

248
server/templates.go Normal file
View File

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

71
server/templates_test.go Normal file
View File

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

View File

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

View File

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