Merge branch 'master' into file-drag-polish
commit
052e041a66
|
@ -4,6 +4,7 @@
|
|||
1. [#1715](https://github.com/influxdata/chronograf/pull/1715): Chronograf now renders on IE11.
|
||||
1. [#1870](https://github.com/influxdata/chronograf/pull/1870): Fix console error for placing prop on div
|
||||
1. [#1864](https://github.com/influxdata/chronograf/pull/1864): Fix Write Data form upload button and add `onDragExit` handler
|
||||
1. [#1891](https://github.com/influxdata/chronograf/pull/1891): Fix Kapacitor config for PagerDuty via the UI
|
||||
|
||||
### Features
|
||||
1. [#1863](https://github.com/influxdata/chronograf/pull/1863): Improve 'new-sources' server flag example by adding 'type' key
|
||||
|
@ -19,6 +20,7 @@
|
|||
1. [#1866](https://github.com/influxdata/chronograf/pull/1866): Fix non-persistence of dashboard graph types
|
||||
|
||||
### Features
|
||||
1. [#1859](https://github.com/influxdata/chronograf/pull/1859): Add y-axis controls to the API for layouts
|
||||
|
||||
### UI Improvements
|
||||
1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay
|
||||
|
@ -30,6 +32,7 @@
|
|||
1. [#1813](https://github.com/influxdata/chronograf/pull/1813): Guarantee UUID for each Alert Table key to prevent dropping items when keys overlap
|
||||
|
||||
### Features
|
||||
1. [#1744](https://github.com/influxdata/chronograf/pull/1744): Add a few time range shortcuts to the custom time range menu
|
||||
1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis bounds
|
||||
1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis label
|
||||
|
||||
|
|
|
@ -98,6 +98,14 @@ func MarshalLayout(l chronograf.Layout) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
axes := make(map[string]*Axis, len(c.Axes))
|
||||
for a, r := range c.Axes {
|
||||
axes[a] = &Axis{
|
||||
Bounds: r.Bounds,
|
||||
Label: r.Label,
|
||||
}
|
||||
}
|
||||
|
||||
cells[i] = &Cell{
|
||||
X: c.X,
|
||||
Y: c.Y,
|
||||
|
@ -107,6 +115,7 @@ func MarshalLayout(l chronograf.Layout) ([]byte, error) {
|
|||
Name: c.Name,
|
||||
Queries: queries,
|
||||
Type: c.Type,
|
||||
Axes: axes,
|
||||
}
|
||||
}
|
||||
return proto.Marshal(&Layout{
|
||||
|
@ -148,6 +157,13 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
axes := make(map[string]chronograf.Axis, len(c.Axes))
|
||||
for a, r := range c.Axes {
|
||||
axes[a] = chronograf.Axis{
|
||||
Bounds: r.Bounds,
|
||||
Label: r.Label,
|
||||
}
|
||||
}
|
||||
|
||||
cells[i] = chronograf.Cell{
|
||||
X: c.X,
|
||||
|
@ -158,6 +174,7 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error {
|
|||
Name: c.Name,
|
||||
Queries: queries,
|
||||
Type: c.Type,
|
||||
Axes: axes,
|
||||
}
|
||||
}
|
||||
l.Cells = cells
|
||||
|
|
|
@ -217,16 +217,17 @@ func (m *Layout) GetCells() []*Cell {
|
|||
}
|
||||
|
||||
type Cell 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"`
|
||||
W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"`
|
||||
H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"`
|
||||
Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"`
|
||||
I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"`
|
||||
Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Yranges []int64 `protobuf:"varint,8,rep,name=yranges" json:"yranges,omitempty"`
|
||||
Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"`
|
||||
Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,omitempty"`
|
||||
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"`
|
||||
W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"`
|
||||
H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"`
|
||||
Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"`
|
||||
I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"`
|
||||
Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Yranges []int64 `protobuf:"varint,8,rep,name=yranges" json:"yranges,omitempty"`
|
||||
Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"`
|
||||
Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Axes map[string]*Axis `protobuf:"bytes,11,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"`
|
||||
}
|
||||
|
||||
func (m *Cell) Reset() { *m = Cell{} }
|
||||
|
@ -241,6 +242,13 @@ func (m *Cell) GetQueries() []*Query {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Cell) GetAxes() map[string]*Axis {
|
||||
if m != nil {
|
||||
return m.Axes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Query 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"`
|
||||
|
@ -315,66 +323,67 @@ func init() {
|
|||
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
||||
|
||||
var fileDescriptorInternal = []byte{
|
||||
// 974 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xdf, 0x6e, 0xe3, 0xc4,
|
||||
0x17, 0xd6, 0xc4, 0x76, 0x12, 0x9f, 0x76, 0xfb, 0xfb, 0x69, 0xb4, 0x62, 0xcd, 0x72, 0x13, 0x2c,
|
||||
0x90, 0x02, 0x12, 0x05, 0xed, 0x0a, 0x09, 0x71, 0x97, 0x36, 0x68, 0x55, 0xda, 0x5d, 0xca, 0xa4,
|
||||
0x2d, 0xdc, 0xa0, 0xd5, 0xc4, 0x39, 0x4d, 0xac, 0x75, 0x62, 0x33, 0xb6, 0x9b, 0xf8, 0x2d, 0x78,
|
||||
0x02, 0x24, 0x24, 0xae, 0xb8, 0xe0, 0x82, 0x17, 0xe0, 0x21, 0x78, 0x21, 0x74, 0x66, 0xc6, 0x7f,
|
||||
0xc2, 0x76, 0xd1, 0x5e, 0x71, 0x37, 0xdf, 0x39, 0xe3, 0x6f, 0x66, 0xbe, 0xf3, 0x9d, 0x23, 0xc3,
|
||||
0x51, 0xbc, 0x29, 0x50, 0x6d, 0x64, 0x72, 0x9c, 0xa9, 0xb4, 0x48, 0xf9, 0xb0, 0xc6, 0xe1, 0xef,
|
||||
0x3d, 0xe8, 0xcf, 0xd2, 0x52, 0x45, 0xc8, 0x8f, 0xa0, 0x77, 0x36, 0x0d, 0xd8, 0x88, 0x8d, 0x1d,
|
||||
0xd1, 0x3b, 0x9b, 0x72, 0x0e, 0xee, 0x0b, 0xb9, 0xc6, 0xa0, 0x37, 0x62, 0x63, 0x5f, 0xe8, 0x35,
|
||||
0xc5, 0xae, 0xaa, 0x0c, 0x03, 0xc7, 0xc4, 0x68, 0xcd, 0x1f, 0xc3, 0xf0, 0x3a, 0x27, 0xb6, 0x35,
|
||||
0x06, 0xae, 0x8e, 0x37, 0x98, 0x72, 0x97, 0x32, 0xcf, 0xb7, 0xa9, 0x5a, 0x04, 0x9e, 0xc9, 0xd5,
|
||||
0x98, 0xff, 0x1f, 0x9c, 0x6b, 0x71, 0x11, 0xf4, 0x75, 0x98, 0x96, 0x3c, 0x80, 0xc1, 0x14, 0x6f,
|
||||
0x65, 0x99, 0x14, 0xc1, 0x60, 0xc4, 0xc6, 0x43, 0x51, 0x43, 0xe2, 0xb9, 0xc2, 0x04, 0x97, 0x4a,
|
||||
0xde, 0x06, 0x43, 0xc3, 0x53, 0x63, 0x7e, 0x0c, 0xfc, 0x6c, 0x93, 0x63, 0x54, 0x2a, 0x9c, 0xbd,
|
||||
0x8a, 0xb3, 0x1b, 0x54, 0xf1, 0x6d, 0x15, 0xf8, 0x9a, 0xe0, 0x9e, 0x0c, 0x9d, 0xf2, 0x1c, 0x0b,
|
||||
0x49, 0x67, 0x83, 0xa6, 0xaa, 0x21, 0x0f, 0xe1, 0x70, 0xb6, 0x92, 0x0a, 0x17, 0x33, 0x8c, 0x14,
|
||||
0x16, 0xc1, 0x81, 0x4e, 0xef, 0xc5, 0xc2, 0x9f, 0x18, 0xf8, 0x53, 0x99, 0xaf, 0xe6, 0xa9, 0x54,
|
||||
0x8b, 0xb7, 0xd2, 0xec, 0x13, 0xf0, 0x22, 0x4c, 0x92, 0x3c, 0x70, 0x46, 0xce, 0xf8, 0xe0, 0xc9,
|
||||
0xa3, 0xe3, 0xa6, 0x18, 0x0d, 0xcf, 0x29, 0x26, 0x89, 0x30, 0xbb, 0xf8, 0x67, 0xe0, 0x17, 0xb8,
|
||||
0xce, 0x12, 0x59, 0x60, 0x1e, 0xb8, 0xfa, 0x13, 0xde, 0x7e, 0x72, 0x65, 0x53, 0xa2, 0xdd, 0x14,
|
||||
0xfe, 0xd6, 0x83, 0x07, 0x7b, 0x54, 0xfc, 0x10, 0xd8, 0x4e, 0xdf, 0xca, 0x13, 0x6c, 0x47, 0xa8,
|
||||
0xd2, 0x37, 0xf2, 0x04, 0xab, 0x08, 0x6d, 0x75, 0xfd, 0x3c, 0xc1, 0xb6, 0x84, 0x56, 0xba, 0x6a,
|
||||
0x9e, 0x60, 0x2b, 0xfe, 0x11, 0x0c, 0x7e, 0x2c, 0x51, 0xc5, 0x98, 0x07, 0x9e, 0x3e, 0xf9, 0x7f,
|
||||
0xed, 0xc9, 0xdf, 0x96, 0xa8, 0x2a, 0x51, 0xe7, 0xe9, 0xa5, 0xba, 0xe2, 0xa6, 0x7c, 0x7a, 0x4d,
|
||||
0xb1, 0x82, 0xdc, 0x31, 0x30, 0x31, 0x5a, 0x5b, 0x85, 0x4c, 0xcd, 0x48, 0xa1, 0xcf, 0xc1, 0x95,
|
||||
0x3b, 0xcc, 0x03, 0x5f, 0xf3, 0xbf, 0xff, 0x06, 0x31, 0x8e, 0x27, 0x3b, 0xcc, 0xbf, 0xda, 0x14,
|
||||
0xaa, 0x12, 0x7a, 0xfb, 0xe3, 0x67, 0xe0, 0x37, 0x21, 0x72, 0xce, 0x2b, 0xac, 0xf4, 0x03, 0x7d,
|
||||
0x41, 0x4b, 0xfe, 0x01, 0x78, 0x77, 0x32, 0x29, 0x8d, 0xf0, 0x07, 0x4f, 0x8e, 0x5a, 0xda, 0xc9,
|
||||
0x2e, 0xce, 0x85, 0x49, 0x7e, 0xd9, 0xfb, 0x82, 0x85, 0xdf, 0x83, 0x4b, 0x21, 0xaa, 0x75, 0x82,
|
||||
0x4b, 0x19, 0x55, 0x27, 0x69, 0xb9, 0x59, 0xe4, 0x01, 0x1b, 0x39, 0x63, 0x47, 0xec, 0xc5, 0xf8,
|
||||
0x3b, 0xd0, 0x9f, 0x9b, 0x6c, 0x6f, 0xe4, 0x8c, 0x7d, 0x61, 0x11, 0x7f, 0x08, 0x5e, 0x22, 0xe7,
|
||||
0x98, 0xd8, 0x36, 0x30, 0x20, 0xfc, 0x93, 0x91, 0x49, 0x4d, 0x51, 0x3a, 0xc6, 0x30, 0xcf, 0x7e,
|
||||
0x17, 0x86, 0x54, 0xb0, 0x97, 0x77, 0x52, 0x59, 0x73, 0x0c, 0x08, 0xdf, 0x48, 0xc5, 0x3f, 0x85,
|
||||
0xbe, 0xbe, 0xde, 0x3d, 0x06, 0xa9, 0xe9, 0x6e, 0x28, 0x2f, 0xec, 0xb6, 0x46, 0x66, 0xb7, 0x23,
|
||||
0x73, 0x73, 0x25, 0xaf, 0x73, 0x25, 0xb2, 0x1e, 0xd5, 0xab, 0xd2, 0x55, 0xba, 0x97, 0xd9, 0x54,
|
||||
0xd5, 0xec, 0x0a, 0xaf, 0xe1, 0xc1, 0xde, 0x89, 0xcd, 0x49, 0x6c, 0xff, 0xa4, 0x56, 0x6a, 0xdf,
|
||||
0x4a, 0x4b, 0x0d, 0x9a, 0x63, 0x82, 0x51, 0x81, 0x0b, 0xad, 0xca, 0x50, 0x34, 0x38, 0xfc, 0x85,
|
||||
0xb5, 0xbc, 0xfa, 0x3c, 0x6a, 0xc1, 0x28, 0x5d, 0xaf, 0xe5, 0x66, 0x61, 0xa9, 0x6b, 0x48, 0xba,
|
||||
0x2d, 0xe6, 0x96, 0xba, 0xb7, 0x98, 0x13, 0x56, 0x99, 0xd5, 0xb9, 0xa7, 0x32, 0x3e, 0x82, 0x83,
|
||||
0x35, 0xca, 0xbc, 0x54, 0xb8, 0xc6, 0x4d, 0x61, 0x25, 0xe8, 0x86, 0xf8, 0x23, 0x18, 0x14, 0x72,
|
||||
0xf9, 0x92, 0x0c, 0x62, 0xb4, 0xe8, 0x17, 0x72, 0x79, 0x8e, 0x15, 0x7f, 0x0f, 0xfc, 0xdb, 0x18,
|
||||
0x93, 0x85, 0x4e, 0x19, 0xdb, 0x0e, 0x75, 0xe0, 0x1c, 0xab, 0xf0, 0x57, 0x06, 0xfd, 0x19, 0xaa,
|
||||
0x3b, 0x54, 0x6f, 0xd5, 0xd3, 0xdd, 0x99, 0xe7, 0xfc, 0xcb, 0xcc, 0x73, 0xef, 0x9f, 0x79, 0x5e,
|
||||
0x3b, 0xf3, 0x1e, 0x82, 0x37, 0x53, 0xd1, 0xd9, 0x54, 0xdf, 0xc8, 0x11, 0x06, 0x90, 0xf3, 0x26,
|
||||
0x51, 0x11, 0xdf, 0xa1, 0x1d, 0x84, 0x16, 0x85, 0x3f, 0x33, 0xe8, 0x5f, 0xc8, 0x2a, 0x2d, 0x8b,
|
||||
0xd7, 0x1c, 0x36, 0x82, 0x83, 0x49, 0x96, 0x25, 0x71, 0x24, 0x8b, 0x38, 0xdd, 0xd8, 0xdb, 0x76,
|
||||
0x43, 0xb4, 0xe3, 0x79, 0x47, 0x3b, 0x73, 0xef, 0x6e, 0x88, 0xda, 0xe8, 0x54, 0x8f, 0x2a, 0x33,
|
||||
0x77, 0x3a, 0x6d, 0x64, 0x26, 0x94, 0x4e, 0xd2, 0x03, 0x27, 0x65, 0x91, 0xde, 0x26, 0xe9, 0x56,
|
||||
0xbf, 0x64, 0x28, 0x1a, 0x1c, 0xfe, 0xc5, 0xc0, 0xfd, 0xaf, 0x46, 0xd0, 0x21, 0xb0, 0xd8, 0x16,
|
||||
0x92, 0xc5, 0xcd, 0x40, 0x1a, 0x74, 0x06, 0x52, 0x00, 0x83, 0x4a, 0xc9, 0xcd, 0x12, 0xf3, 0x60,
|
||||
0xa8, 0xfb, 0xbb, 0x86, 0x3a, 0xa3, 0x7b, 0xc4, 0x4c, 0x22, 0x5f, 0xd4, 0xb0, 0xf1, 0x3c, 0xb4,
|
||||
0x9e, 0x0f, 0xff, 0x60, 0xe0, 0x35, 0xce, 0x3d, 0xdd, 0x77, 0xee, 0x69, 0xeb, 0xdc, 0xe9, 0x49,
|
||||
0xed, 0xdc, 0xe9, 0x09, 0x61, 0x71, 0x59, 0x3b, 0x57, 0x5c, 0x92, 0x6a, 0xcf, 0x54, 0x5a, 0x66,
|
||||
0x27, 0x95, 0x91, 0xd7, 0x17, 0x0d, 0xa6, 0x72, 0x7f, 0xb7, 0x42, 0x65, 0xdf, 0xec, 0x0b, 0x8b,
|
||||
0xc8, 0x1c, 0x17, 0xba, 0xab, 0xcd, 0x2b, 0x0d, 0xe0, 0x1f, 0x82, 0x27, 0xe8, 0x15, 0xfa, 0xa9,
|
||||
0x7b, 0x02, 0xe9, 0xb0, 0x30, 0xd9, 0xf0, 0xa9, 0xdd, 0x46, 0x2c, 0xd7, 0x59, 0x86, 0xca, 0x7a,
|
||||
0xda, 0x00, 0xcd, 0x9d, 0x6e, 0xd1, 0x8c, 0x23, 0x47, 0x18, 0x10, 0xfe, 0x00, 0xfe, 0x24, 0x41,
|
||||
0x55, 0x88, 0x32, 0x79, 0x7d, 0x88, 0x71, 0x70, 0xbf, 0x9e, 0x7d, 0xf3, 0xa2, 0xee, 0x04, 0x5a,
|
||||
0xb7, 0xfe, 0x75, 0xfe, 0xe1, 0xdf, 0x73, 0x99, 0xc9, 0xb3, 0xa9, 0x2e, 0xac, 0x23, 0x2c, 0x0a,
|
||||
0x3f, 0x06, 0x97, 0xfa, 0xa4, 0xc3, 0xec, 0xbe, 0xa9, 0xc7, 0xe6, 0x7d, 0xfd, 0xaf, 0xf2, 0xf4,
|
||||
0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x79, 0x79, 0x87, 0x37, 0xbd, 0x08, 0x00, 0x00,
|
||||
// 985 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xcd, 0x6e, 0x23, 0x45,
|
||||
0x10, 0x56, 0xcf, 0x8f, 0xed, 0x29, 0x67, 0x03, 0x6a, 0xad, 0xd8, 0x61, 0xb9, 0x98, 0x11, 0x48,
|
||||
0x06, 0x41, 0x40, 0xbb, 0x42, 0x42, 0xdc, 0x9c, 0x18, 0xad, 0x42, 0xb2, 0x4b, 0x68, 0x27, 0x81,
|
||||
0x0b, 0x5a, 0xb5, 0xc7, 0x95, 0x64, 0xb4, 0x63, 0xcf, 0xd0, 0x33, 0x13, 0x7b, 0xde, 0x82, 0x27,
|
||||
0x40, 0x42, 0xe2, 0xc4, 0x81, 0x03, 0x2f, 0xc0, 0x9d, 0xa7, 0x42, 0xd5, 0xdd, 0x33, 0x1e, 0xb3,
|
||||
0x59, 0xb4, 0x17, 0xf6, 0xd6, 0x5f, 0x55, 0xbb, 0xba, 0xe6, 0xab, 0xfa, 0x3e, 0x19, 0xf6, 0x93,
|
||||
0x55, 0x89, 0x6a, 0x25, 0xd3, 0x83, 0x5c, 0x65, 0x65, 0xc6, 0x07, 0x0d, 0x8e, 0xfe, 0x70, 0xa0,
|
||||
0x37, 0xcb, 0x2a, 0x15, 0x23, 0xdf, 0x07, 0xe7, 0x78, 0x1a, 0xb2, 0x11, 0x1b, 0xbb, 0xc2, 0x39,
|
||||
0x9e, 0x72, 0x0e, 0xde, 0x33, 0xb9, 0xc4, 0xd0, 0x19, 0xb1, 0x71, 0x20, 0xf4, 0x99, 0x62, 0xe7,
|
||||
0x75, 0x8e, 0xa1, 0x6b, 0x62, 0x74, 0xe6, 0x0f, 0x61, 0x70, 0x51, 0x50, 0xb5, 0x25, 0x86, 0x9e,
|
||||
0x8e, 0xb7, 0x98, 0x72, 0x67, 0xb2, 0x28, 0xd6, 0x99, 0x5a, 0x84, 0xbe, 0xc9, 0x35, 0x98, 0xbf,
|
||||
0x0d, 0xee, 0x85, 0x38, 0x0d, 0x7b, 0x3a, 0x4c, 0x47, 0x1e, 0x42, 0x7f, 0x8a, 0x57, 0xb2, 0x4a,
|
||||
0xcb, 0xb0, 0x3f, 0x62, 0xe3, 0x81, 0x68, 0x20, 0xd5, 0x39, 0xc7, 0x14, 0xaf, 0x95, 0xbc, 0x0a,
|
||||
0x07, 0xa6, 0x4e, 0x83, 0xf9, 0x01, 0xf0, 0xe3, 0x55, 0x81, 0x71, 0xa5, 0x70, 0xf6, 0x22, 0xc9,
|
||||
0x2f, 0x51, 0x25, 0x57, 0x75, 0x18, 0xe8, 0x02, 0x77, 0x64, 0xe8, 0x95, 0xa7, 0x58, 0x4a, 0x7a,
|
||||
0x1b, 0x74, 0xa9, 0x06, 0xf2, 0x08, 0xf6, 0x66, 0x37, 0x52, 0xe1, 0x62, 0x86, 0xb1, 0xc2, 0x32,
|
||||
0x1c, 0xea, 0xf4, 0x4e, 0x2c, 0xfa, 0x99, 0x41, 0x30, 0x95, 0xc5, 0xcd, 0x3c, 0x93, 0x6a, 0xf1,
|
||||
0x5a, 0x9c, 0x7d, 0x0a, 0x7e, 0x8c, 0x69, 0x5a, 0x84, 0xee, 0xc8, 0x1d, 0x0f, 0x1f, 0x3d, 0x38,
|
||||
0x68, 0x87, 0xd1, 0xd6, 0x39, 0xc2, 0x34, 0x15, 0xe6, 0x16, 0xff, 0x1c, 0x82, 0x12, 0x97, 0x79,
|
||||
0x2a, 0x4b, 0x2c, 0x42, 0x4f, 0xff, 0x84, 0x6f, 0x7f, 0x72, 0x6e, 0x53, 0x62, 0x7b, 0x29, 0xfa,
|
||||
0xdd, 0x81, 0x7b, 0x3b, 0xa5, 0xf8, 0x1e, 0xb0, 0x8d, 0xee, 0xca, 0x17, 0x6c, 0x43, 0xa8, 0xd6,
|
||||
0x1d, 0xf9, 0x82, 0xd5, 0x84, 0xd6, 0x7a, 0x7e, 0xbe, 0x60, 0x6b, 0x42, 0x37, 0x7a, 0x6a, 0xbe,
|
||||
0x60, 0x37, 0xfc, 0x23, 0xe8, 0xff, 0x54, 0xa1, 0x4a, 0xb0, 0x08, 0x7d, 0xfd, 0xf2, 0x5b, 0xdb,
|
||||
0x97, 0xbf, 0xab, 0x50, 0xd5, 0xa2, 0xc9, 0xd3, 0x97, 0xea, 0x89, 0x9b, 0xf1, 0xe9, 0x33, 0xc5,
|
||||
0x4a, 0xda, 0x8e, 0xbe, 0x89, 0xd1, 0xd9, 0x32, 0x64, 0x66, 0x46, 0x0c, 0x7d, 0x01, 0x9e, 0xdc,
|
||||
0x60, 0x11, 0x06, 0xba, 0xfe, 0xfb, 0xaf, 0x20, 0xe3, 0x60, 0xb2, 0xc1, 0xe2, 0xeb, 0x55, 0xa9,
|
||||
0x6a, 0xa1, 0xaf, 0x3f, 0x7c, 0x02, 0x41, 0x1b, 0xa2, 0xcd, 0x79, 0x81, 0xb5, 0xfe, 0xc0, 0x40,
|
||||
0xd0, 0x91, 0x7f, 0x00, 0xfe, 0xad, 0x4c, 0x2b, 0x43, 0xfc, 0xf0, 0xd1, 0xfe, 0xb6, 0xec, 0x64,
|
||||
0x93, 0x14, 0xc2, 0x24, 0xbf, 0x72, 0xbe, 0x64, 0xd1, 0x0f, 0xe0, 0x51, 0x88, 0x66, 0x9d, 0xe2,
|
||||
0xb5, 0x8c, 0xeb, 0xc3, 0xac, 0x5a, 0x2d, 0x8a, 0x90, 0x8d, 0xdc, 0xb1, 0x2b, 0x76, 0x62, 0xfc,
|
||||
0x1d, 0xe8, 0xcd, 0x4d, 0xd6, 0x19, 0xb9, 0xe3, 0x40, 0x58, 0xc4, 0xef, 0x83, 0x9f, 0xca, 0x39,
|
||||
0xa6, 0x56, 0x06, 0x06, 0x44, 0x7f, 0x31, 0x5a, 0x52, 0x33, 0x94, 0xce, 0x62, 0x98, 0xcf, 0x7e,
|
||||
0x17, 0x06, 0x34, 0xb0, 0xe7, 0xb7, 0x52, 0xd9, 0xe5, 0xe8, 0x13, 0xbe, 0x94, 0x8a, 0x7f, 0x06,
|
||||
0x3d, 0xdd, 0xde, 0x1d, 0x0b, 0xd2, 0x94, 0xbb, 0xa4, 0xbc, 0xb0, 0xd7, 0x5a, 0x9a, 0xbd, 0x0e,
|
||||
0xcd, 0x6d, 0x4b, 0x7e, 0xa7, 0x25, 0x5a, 0x3d, 0x9a, 0x57, 0xad, 0xa7, 0x74, 0x67, 0x65, 0x33,
|
||||
0x55, 0x73, 0x2b, 0xba, 0x80, 0x7b, 0x3b, 0x2f, 0xb6, 0x2f, 0xb1, 0xdd, 0x97, 0xb6, 0x54, 0x07,
|
||||
0x96, 0x5a, 0x12, 0x68, 0x81, 0x29, 0xc6, 0x25, 0x2e, 0x34, 0x2b, 0x03, 0xd1, 0xe2, 0xe8, 0x57,
|
||||
0xb6, 0xad, 0xab, 0xdf, 0x23, 0x09, 0xc6, 0xd9, 0x72, 0x29, 0x57, 0x0b, 0x5b, 0xba, 0x81, 0xc4,
|
||||
0xdb, 0x62, 0x6e, 0x4b, 0x3b, 0x8b, 0x39, 0x61, 0x95, 0x5b, 0x9e, 0x1d, 0x95, 0xf3, 0x11, 0x0c,
|
||||
0x97, 0x28, 0x8b, 0x4a, 0xe1, 0x12, 0x57, 0xa5, 0xa5, 0xa0, 0x1b, 0xe2, 0x0f, 0xa0, 0x5f, 0xca,
|
||||
0xeb, 0xe7, 0xb4, 0x20, 0x86, 0x8b, 0x5e, 0x29, 0xaf, 0x4f, 0xb0, 0xe6, 0xef, 0x41, 0x70, 0x95,
|
||||
0x60, 0xba, 0xd0, 0x29, 0xb3, 0xb6, 0x03, 0x1d, 0x38, 0xc1, 0x3a, 0xfa, 0x8d, 0x41, 0x6f, 0x86,
|
||||
0xea, 0x16, 0xd5, 0x6b, 0x69, 0xba, 0xeb, 0x79, 0xee, 0x7f, 0x78, 0x9e, 0x77, 0xb7, 0xe7, 0xf9,
|
||||
0x5b, 0xcf, 0xbb, 0x0f, 0xfe, 0x4c, 0xc5, 0xc7, 0x53, 0xdd, 0x91, 0x2b, 0x0c, 0xa0, 0xcd, 0x9b,
|
||||
0xc4, 0x65, 0x72, 0x8b, 0xd6, 0x08, 0x2d, 0x8a, 0x7e, 0x61, 0xd0, 0x3b, 0x95, 0x75, 0x56, 0x95,
|
||||
0x2f, 0x6d, 0xd8, 0x08, 0x86, 0x93, 0x3c, 0x4f, 0x93, 0x58, 0x96, 0x49, 0xb6, 0xb2, 0xdd, 0x76,
|
||||
0x43, 0x74, 0xe3, 0x69, 0x87, 0x3b, 0xd3, 0x77, 0x37, 0x44, 0x32, 0x3a, 0xd2, 0x56, 0x65, 0x7c,
|
||||
0xa7, 0x23, 0x23, 0xe3, 0x50, 0x3a, 0x49, 0x1f, 0x38, 0xa9, 0xca, 0xec, 0x2a, 0xcd, 0xd6, 0xfa,
|
||||
0x4b, 0x06, 0xa2, 0xc5, 0xd1, 0xdf, 0x0e, 0x78, 0x6f, 0xca, 0x82, 0xf6, 0x80, 0x25, 0x76, 0x90,
|
||||
0x2c, 0x69, 0x0d, 0xa9, 0xdf, 0x31, 0xa4, 0x10, 0xfa, 0xb5, 0x92, 0xab, 0x6b, 0x2c, 0xc2, 0x81,
|
||||
0xd6, 0x77, 0x03, 0x75, 0x46, 0x6b, 0xc4, 0x38, 0x51, 0x20, 0x1a, 0xd8, 0xee, 0x3c, 0x74, 0x76,
|
||||
0xfe, 0x13, 0x6b, 0x5a, 0x43, 0xdd, 0x51, 0xb8, 0x4b, 0xcb, 0xff, 0xe7, 0x55, 0x7f, 0x32, 0xf0,
|
||||
0x5b, 0xc1, 0x1c, 0xed, 0x0a, 0xe6, 0x68, 0x2b, 0x98, 0xe9, 0x61, 0x23, 0x98, 0xe9, 0x21, 0x61,
|
||||
0x71, 0xd6, 0x08, 0x46, 0x9c, 0xd1, 0xb0, 0x9e, 0xa8, 0xac, 0xca, 0x0f, 0x6b, 0x33, 0xd5, 0x40,
|
||||
0xb4, 0x98, 0xb6, 0xec, 0xfb, 0x1b, 0x54, 0x96, 0xea, 0x40, 0x58, 0x44, 0x3b, 0x79, 0xaa, 0xcd,
|
||||
0xc4, 0x90, 0x6b, 0x00, 0xff, 0x10, 0x7c, 0x41, 0xe4, 0x69, 0x86, 0x77, 0xe6, 0xa2, 0xc3, 0xc2,
|
||||
0x64, 0xa3, 0xc7, 0xf6, 0x1a, 0x55, 0xb9, 0xc8, 0x73, 0x54, 0x56, 0x4a, 0x06, 0xe8, 0xda, 0xd9,
|
||||
0x1a, 0x8d, 0x0b, 0xba, 0xc2, 0x80, 0xe8, 0x47, 0x08, 0x26, 0x29, 0xaa, 0x52, 0x54, 0xe9, 0xcb,
|
||||
0xde, 0xc9, 0xc1, 0xfb, 0x66, 0xf6, 0xed, 0xb3, 0x46, 0x80, 0x74, 0xde, 0xca, 0xc6, 0xfd, 0x97,
|
||||
0x6c, 0x4e, 0x64, 0x2e, 0x8f, 0xa7, 0x7a, 0x9f, 0x5c, 0x61, 0x51, 0xf4, 0x31, 0x78, 0x24, 0xcf,
|
||||
0x4e, 0x65, 0xef, 0x55, 0xd2, 0x9e, 0xf7, 0xf4, 0x5f, 0xa4, 0xc7, 0xff, 0x04, 0x00, 0x00, 0xff,
|
||||
0xff, 0x5a, 0xf0, 0x92, 0xd4, 0x34, 0x09, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ message Cell {
|
|||
repeated int64 yranges = 8; // Limits of the y-axes
|
||||
repeated string ylabels = 9; // Labels of the y-axes
|
||||
string type = 10; // Cell visualization type
|
||||
map<string, Axis> axes = 11; // Axes represent the graphical viewport for a cell's visualizations
|
||||
}
|
||||
|
||||
message Query {
|
||||
|
|
|
@ -108,6 +108,12 @@ func TestMarshalLayout(t *testing.T) {
|
|||
I: "anotherid",
|
||||
Type: "line",
|
||||
Name: "cell1",
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"y": chronograf.Axis{
|
||||
Bounds: []string{"0", "100"},
|
||||
Label: "foo",
|
||||
},
|
||||
},
|
||||
Queries: []chronograf.Query{
|
||||
{
|
||||
Range: &chronograf.Range{
|
||||
|
@ -133,8 +139,8 @@ func TestMarshalLayout(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
} else if err := internal.UnmarshalLayout(buf, &vv); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !reflect.DeepEqual(layout, vv) {
|
||||
t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, layout)
|
||||
} else if !cmp.Equal(layout, vv) {
|
||||
t.Fatal("source protobuf copy error: diff:\n", cmp.Diff(layout, vv))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -675,14 +675,15 @@ type DashboardsStore interface {
|
|||
|
||||
// Cell is a rectangle and multiple time series queries to visualize.
|
||||
type Cell struct {
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
W int32 `json:"w"`
|
||||
H int32 `json:"h"`
|
||||
I string `json:"i"`
|
||||
Name string `json:"name"`
|
||||
Queries []Query `json:"queries"`
|
||||
Type string `json:"type"`
|
||||
X int32 `json:"x"`
|
||||
Y int32 `json:"y"`
|
||||
W int32 `json:"w"`
|
||||
H int32 `json:"h"`
|
||||
I string `json:"i"`
|
||||
Name string `json:"name"`
|
||||
Queries []Query `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Layout is a collection of Cells for visualization
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
var _ chronograf.LayoutStore = &LayoutStore{}
|
||||
|
||||
type LayoutStore struct {
|
||||
AddF func(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error)
|
||||
AllF func(ctx context.Context) ([]chronograf.Layout, error)
|
||||
DeleteF func(ctx context.Context, layout chronograf.Layout) error
|
||||
GetF func(ctx context.Context, id string) (chronograf.Layout, error)
|
||||
UpdateF func(ctx context.Context, layout chronograf.Layout) error
|
||||
}
|
||||
|
||||
func (s *LayoutStore) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) {
|
||||
return s.AddF(ctx, layout)
|
||||
}
|
||||
|
||||
func (s *LayoutStore) All(ctx context.Context) ([]chronograf.Layout, error) {
|
||||
return s.AllF(ctx)
|
||||
}
|
||||
|
||||
func (s *LayoutStore) Delete(ctx context.Context, layout chronograf.Layout) error {
|
||||
return s.DeleteF(ctx, layout)
|
||||
}
|
||||
|
||||
func (s *LayoutStore) Get(ctx context.Context, id string) (chronograf.Layout, error) {
|
||||
return s.GetF(ctx, id)
|
||||
}
|
||||
|
||||
func (s *LayoutStore) Update(ctx context.Context, layout chronograf.Layout) error {
|
||||
return s.UpdateF(ctx, layout)
|
||||
}
|
|
@ -23,6 +23,23 @@ func newLayoutResponse(layout chronograf.Layout) layoutResponse {
|
|||
httpAPILayouts := "/chronograf/v1/layouts"
|
||||
href := fmt.Sprintf("%s/%s", httpAPILayouts, layout.ID)
|
||||
rel := "self"
|
||||
|
||||
for idx, cell := range layout.Cells {
|
||||
axes := []string{"x", "y", "y2"}
|
||||
|
||||
if cell.Axes == nil {
|
||||
layout.Cells[idx].Axes = make(map[string]chronograf.Axis, len(axes))
|
||||
}
|
||||
|
||||
for _, axis := range axes {
|
||||
if _, found := cell.Axes[axis]; !found {
|
||||
layout.Cells[idx].Axes[axis] = chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return layoutResponse{
|
||||
Layout: layout,
|
||||
Link: link{
|
||||
|
|
|
@ -1,79 +1,183 @@
|
|||
package server_test
|
||||
|
||||
/*
|
||||
func TestNewLayout(t *testing.T) {
|
||||
t.Parallel()
|
||||
var tests = []struct {
|
||||
Desc string
|
||||
AddError error
|
||||
ExistingLayouts map[string]chronograf.Layout
|
||||
NewLayout *models.Layout
|
||||
ExpectedID int
|
||||
ExpectedHref string
|
||||
ExpectedStatus int
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/mocks"
|
||||
"github.com/influxdata/chronograf/server"
|
||||
)
|
||||
|
||||
func Test_Layouts(t *testing.T) {
|
||||
layoutTests := []struct {
|
||||
name string
|
||||
expected chronograf.Layout
|
||||
allLayouts []chronograf.Layout
|
||||
focusedApp string // should filter all layouts to this app only
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
Desc: "Test that an error in datastore returns 500 status",
|
||||
AddError: errors.New("error"),
|
||||
NewLayout: &models.Layout{
|
||||
Measurement: new(string),
|
||||
App: new(string),
|
||||
Cells: []*models.Cell{
|
||||
&models.Cell{
|
||||
X: new(int32),
|
||||
Y: new(int32),
|
||||
W: new(int32),
|
||||
H: new(int32),
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedStatus: http.StatusInternalServerError,
|
||||
"empty layout",
|
||||
chronograf.Layout{},
|
||||
[]chronograf.Layout{},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
Desc: "Test that creating a layout returns 201 status",
|
||||
ExistingLayouts: map[string]chronograf.Layout{},
|
||||
NewLayout: &models.Layout{
|
||||
Measurement: new(string),
|
||||
App: new(string),
|
||||
Cells: []*models.Cell{
|
||||
&models.Cell{
|
||||
X: new(int32),
|
||||
Y: new(int32),
|
||||
W: new(int32),
|
||||
H: new(int32),
|
||||
"several layouts",
|
||||
chronograf.Layout{
|
||||
ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c",
|
||||
Application: "influxdb",
|
||||
Measurement: "influxdb",
|
||||
},
|
||||
[]chronograf.Layout{
|
||||
chronograf.Layout{
|
||||
ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c",
|
||||
Application: "influxdb",
|
||||
Measurement: "influxdb",
|
||||
},
|
||||
},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"filtered app",
|
||||
chronograf.Layout{
|
||||
ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c",
|
||||
Application: "influxdb",
|
||||
Measurement: "influxdb",
|
||||
},
|
||||
[]chronograf.Layout{
|
||||
chronograf.Layout{
|
||||
ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c",
|
||||
Application: "influxdb",
|
||||
Measurement: "influxdb",
|
||||
},
|
||||
chronograf.Layout{
|
||||
ID: "b020101b-ea6b-4c8c-9f0e-db0ba501f4ef",
|
||||
Application: "chronograf",
|
||||
Measurement: "chronograf",
|
||||
},
|
||||
},
|
||||
"influxdb",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"axis zero values",
|
||||
chronograf.Layout{
|
||||
ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c",
|
||||
Application: "influxdb",
|
||||
Measurement: "influxdb",
|
||||
Cells: []chronograf.Cell{
|
||||
{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: 4,
|
||||
H: 4,
|
||||
I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd",
|
||||
Name: "A Graph",
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
},
|
||||
"y": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
},
|
||||
"y2": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExpectedID: 0,
|
||||
ExpectedHref: "/chronograf/v1/layouts/0",
|
||||
ExpectedStatus: http.StatusCreated,
|
||||
[]chronograf.Layout{
|
||||
chronograf.Layout{
|
||||
ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c",
|
||||
Application: "influxdb",
|
||||
Measurement: "influxdb",
|
||||
Cells: []chronograf.Cell{
|
||||
{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
W: 4,
|
||||
H: 4,
|
||||
I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd",
|
||||
Name: "A Graph",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// The mocked backing store will be used to
|
||||
// check stored values.
|
||||
store := server.Store{
|
||||
LayoutStore: &mock.LayoutStore{
|
||||
AddError: test.AddError,
|
||||
Layouts: test.ExistingLayouts,
|
||||
},
|
||||
}
|
||||
for _, test := range layoutTests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Send the test layout to the mocked store.
|
||||
params := op.PostLayoutsParams{
|
||||
Layout: test.NewLayout,
|
||||
}
|
||||
resp := store.NewLayout(context.Background(), params)
|
||||
w := httptest.NewRecorder()
|
||||
resp.WriteResponse(w, runtime.JSONProducer())
|
||||
if w.Code != test.ExpectedStatus {
|
||||
t.Fatalf("Expected status %d; actual %d", test.ExpectedStatus, w.Code)
|
||||
}
|
||||
loc := w.Header().Get("Location")
|
||||
if loc != test.ExpectedHref {
|
||||
t.Fatalf("Expected status %s; actual %s", test.ExpectedHref, loc)
|
||||
}
|
||||
// setup mock chronograf.Service and mock logger
|
||||
lg := &mocks.TestLogger{}
|
||||
svc := server.Service{
|
||||
LayoutStore: &mocks.LayoutStore{
|
||||
AllF: func(ctx context.Context) ([]chronograf.Layout, error) {
|
||||
if len(test.allLayouts) == 0 {
|
||||
return []chronograf.Layout{
|
||||
test.expected,
|
||||
}, nil
|
||||
} else {
|
||||
return test.allLayouts, nil
|
||||
}
|
||||
},
|
||||
},
|
||||
Logger: lg,
|
||||
}
|
||||
|
||||
// setup mock request and response
|
||||
rr := httptest.NewRecorder()
|
||||
reqURL := url.URL{
|
||||
Path: "/chronograf/v1/layouts",
|
||||
}
|
||||
params := reqURL.Query()
|
||||
|
||||
// add query params required by test
|
||||
if test.focusedApp != "" {
|
||||
params.Add("app", test.focusedApp)
|
||||
}
|
||||
|
||||
// re-inject query params
|
||||
reqURL.RawQuery = params.Encode()
|
||||
|
||||
req := httptest.NewRequest("GET", reqURL.RequestURI(), strings.NewReader(""))
|
||||
|
||||
// invoke handler for layouts endpoint
|
||||
svc.Layouts(rr, req)
|
||||
|
||||
// create a throwaway frame to unwrap Layouts
|
||||
respFrame := struct {
|
||||
Layouts []struct {
|
||||
chronograf.Layout
|
||||
Link interface{} `json:"-"`
|
||||
} `json:"layouts"`
|
||||
}{}
|
||||
|
||||
// decode resp into respFrame
|
||||
resp := rr.Result()
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil {
|
||||
t.Fatalf("%q - Error unmarshaling JSON: err: %s", test.name, err.Error())
|
||||
}
|
||||
|
||||
// compare actual and expected
|
||||
if !cmp.Equal(test.expected, respFrame.Layouts[0].Layout) {
|
||||
t.Fatalf("%q - Expected layouts to be equal: diff:\n\t%s", test.name, cmp.Diff(test.expected, respFrame.Layouts[0].Layout))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
plugins: [
|
||||
'react',
|
||||
'prettier',
|
||||
'babel',
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
|
@ -98,7 +99,6 @@
|
|||
'no-floating-decimal': 2,
|
||||
'no-implicit-coercion': 0,
|
||||
'no-implied-eval': 2,
|
||||
'no-invalid-this': 2,
|
||||
'no-iterator': 2,
|
||||
'no-lone-blocks': 2,
|
||||
'no-loop-func': 2,
|
||||
|
@ -209,7 +209,7 @@
|
|||
// React
|
||||
'jsx-quotes': [1, "prefer-double"],
|
||||
'react/display-name': 0,
|
||||
'react/jsx-no-bind': 0,
|
||||
'react/jsx-no-bind': [2, {ignoreRefs: true}],
|
||||
'react/jsx-boolean-value': [2, 'always'],
|
||||
'react/jsx-curly-spacing': [2, 'never'],
|
||||
'react/jsx-equals-spacing': [2, 'never'],
|
||||
|
@ -235,11 +235,16 @@
|
|||
'react/require-extension': 0,
|
||||
'react/self-closing-comp': 0, // TODO: we can re-enable this if some brave soul wants to update the code (mostly spans acting as icons)
|
||||
'react/sort-comp': 0, // TODO: 2
|
||||
|
||||
// Prettier
|
||||
'prettier/prettier': ['error', {
|
||||
'singleQuote': true,
|
||||
'trailingComma': 'es5',
|
||||
'bracketSpacing': false,
|
||||
'semi': false,
|
||||
}],
|
||||
|
||||
// Babel
|
||||
'babel/no-invalid-this': 1
|
||||
},
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
"calculate-size": "^1.1.1",
|
||||
"classnames": "^2.2.3",
|
||||
"dygraphs": "^2.0.0",
|
||||
"eslint-plugin-babel": "^4.1.2",
|
||||
"fast.js": "^0.1.1",
|
||||
"fixed-data-table": "^0.6.1",
|
||||
"he": "^1.1.1",
|
||||
|
|
|
@ -12,15 +12,6 @@ class DatabaseRow extends Component {
|
|||
isEditing: false,
|
||||
isDeleting: false,
|
||||
}
|
||||
this.handleKeyDown = ::this.handleKeyDown
|
||||
this.handleClickOutside = ::this.handleClickOutside
|
||||
this.handleStartEdit = ::this.handleStartEdit
|
||||
this.handleEndEdit = ::this.handleEndEdit
|
||||
this.handleCreate = ::this.handleCreate
|
||||
this.handleUpdate = ::this.handleUpdate
|
||||
this.getInputValues = ::this.getInputValues
|
||||
this.handleStartDelete = ::this.handleStartDelete
|
||||
this.handleEndDelete = ::this.handleEndDelete
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -29,9 +20,112 @@ class DatabaseRow extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleRemove = () => {
|
||||
const {database, retentionPolicy, onRemove} = this.props
|
||||
onRemove(database, retentionPolicy)
|
||||
}
|
||||
|
||||
handleClickOutside = () => {
|
||||
const {database, retentionPolicy, onRemove} = this.props
|
||||
if (retentionPolicy.isNew) {
|
||||
onRemove(database, retentionPolicy)
|
||||
}
|
||||
|
||||
this.handleEndEdit()
|
||||
this.handleEndDelete()
|
||||
}
|
||||
|
||||
handleStartEdit = () => {
|
||||
this.setState({isEditing: true})
|
||||
}
|
||||
|
||||
handleEndEdit = () => {
|
||||
this.setState({isEditing: false})
|
||||
}
|
||||
|
||||
handleStartDelete = () => {
|
||||
this.setState({isDeleting: true})
|
||||
}
|
||||
|
||||
handleEndDelete = () => {
|
||||
this.setState({isDeleting: false})
|
||||
}
|
||||
|
||||
handleCreate = () => {
|
||||
const {database, retentionPolicy, onCreate} = this.props
|
||||
const validInputs = this.getInputValues()
|
||||
if (!validInputs) {
|
||||
return
|
||||
}
|
||||
|
||||
onCreate(database, {...retentionPolicy, ...validInputs})
|
||||
this.handleEndEdit()
|
||||
}
|
||||
|
||||
handleUpdate = () => {
|
||||
const {database, retentionPolicy, onUpdate} = this.props
|
||||
const validInputs = this.getInputValues()
|
||||
if (!validInputs) {
|
||||
return
|
||||
}
|
||||
|
||||
onUpdate(database, retentionPolicy, validInputs)
|
||||
this.handleEndEdit()
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
const {key} = e
|
||||
const {retentionPolicy, database, onRemove} = this.props
|
||||
|
||||
if (key === 'Escape') {
|
||||
if (retentionPolicy.isNew) {
|
||||
onRemove(database, retentionPolicy)
|
||||
return
|
||||
}
|
||||
|
||||
this.handleEndEdit()
|
||||
}
|
||||
|
||||
if (key === 'Enter') {
|
||||
if (retentionPolicy.isNew) {
|
||||
this.handleCreate()
|
||||
return
|
||||
}
|
||||
|
||||
this.handleUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
getInputValues = () => {
|
||||
const {
|
||||
notify,
|
||||
retentionPolicy: {name: currentName},
|
||||
isRFDisplayed,
|
||||
} = this.props
|
||||
|
||||
const name = (this.name && this.name.value.trim()) || currentName
|
||||
let duration = this.duration.value.trim()
|
||||
// Replication > 1 is only valid for Influx Enterprise
|
||||
const replication = isRFDisplayed ? +this.replication.value.trim() : 1
|
||||
|
||||
if (!duration || (isRFDisplayed && !replication)) {
|
||||
notify('error', 'Fields cannot be empty')
|
||||
return
|
||||
}
|
||||
|
||||
if (duration === '∞') {
|
||||
duration = 'INF'
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
duration,
|
||||
replication,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
onRemove,
|
||||
retentionPolicy: {name, duration, replication, isDefault, isNew},
|
||||
retentionPolicy,
|
||||
database,
|
||||
|
@ -53,7 +147,7 @@ class DatabaseRow extends Component {
|
|||
type="text"
|
||||
defaultValue={name}
|
||||
placeholder="Name this RP"
|
||||
onKeyDown={e => this.handleKeyDown(e, database)}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
ref={r => (this.name = r)}
|
||||
autoFocus={true}
|
||||
spellCheck={false}
|
||||
|
@ -68,7 +162,7 @@ class DatabaseRow extends Component {
|
|||
type="text"
|
||||
defaultValue={formattedDuration}
|
||||
placeholder="How long should Data last"
|
||||
onKeyDown={e => this.handleKeyDown(e, database)}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
ref={r => (this.duration = r)}
|
||||
autoFocus={!isNew}
|
||||
spellCheck={false}
|
||||
|
@ -84,7 +178,7 @@ class DatabaseRow extends Component {
|
|||
min="1"
|
||||
defaultValue={replication || 1}
|
||||
placeholder="# of Nodes"
|
||||
onKeyDown={e => this.handleKeyDown(e, database)}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
ref={r => (this.replication = r)}
|
||||
spellCheck={false}
|
||||
autoComplete={false}
|
||||
|
@ -98,11 +192,7 @@ class DatabaseRow extends Component {
|
|||
<YesNoButtons
|
||||
buttonSize="btn-xs"
|
||||
onConfirm={isNew ? this.handleCreate : this.handleUpdate}
|
||||
onCancel={
|
||||
isNew
|
||||
? () => onRemove(database, retentionPolicy)
|
||||
: this.handleEndEdit
|
||||
}
|
||||
onCancel={isNew ? this.handleRemove : this.handleEndEdit}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -137,7 +227,7 @@ class DatabaseRow extends Component {
|
|||
>
|
||||
{isDeleting
|
||||
? <YesNoButtons
|
||||
onConfirm={() => onDelete(database, retentionPolicy)}
|
||||
onConfirm={onDelete(database, retentionPolicy)}
|
||||
onCancel={this.handleEndDelete}
|
||||
buttonSize="btn-xs"
|
||||
/>
|
||||
|
@ -152,105 +242,6 @@ class DatabaseRow extends Component {
|
|||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
const {database, retentionPolicy, onRemove} = this.props
|
||||
if (retentionPolicy.isNew) {
|
||||
onRemove(database, retentionPolicy)
|
||||
}
|
||||
|
||||
this.handleEndEdit()
|
||||
this.handleEndDelete()
|
||||
}
|
||||
|
||||
handleStartEdit() {
|
||||
this.setState({isEditing: true})
|
||||
}
|
||||
|
||||
handleEndEdit() {
|
||||
this.setState({isEditing: false})
|
||||
}
|
||||
|
||||
handleStartDelete() {
|
||||
this.setState({isDeleting: true})
|
||||
}
|
||||
|
||||
handleEndDelete() {
|
||||
this.setState({isDeleting: false})
|
||||
}
|
||||
|
||||
handleCreate() {
|
||||
const {database, retentionPolicy, onCreate} = this.props
|
||||
const validInputs = this.getInputValues()
|
||||
if (!validInputs) {
|
||||
return
|
||||
}
|
||||
|
||||
onCreate(database, {...retentionPolicy, ...validInputs})
|
||||
this.handleEndEdit()
|
||||
}
|
||||
|
||||
handleUpdate() {
|
||||
const {database, retentionPolicy, onUpdate} = this.props
|
||||
const validInputs = this.getInputValues()
|
||||
if (!validInputs) {
|
||||
return
|
||||
}
|
||||
|
||||
onUpdate(database, retentionPolicy, validInputs)
|
||||
this.handleEndEdit()
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
const {key} = e
|
||||
const {retentionPolicy, database, onRemove} = this.props
|
||||
|
||||
if (key === 'Escape') {
|
||||
if (retentionPolicy.isNew) {
|
||||
onRemove(database, retentionPolicy)
|
||||
return
|
||||
}
|
||||
|
||||
this.handleEndEdit()
|
||||
}
|
||||
|
||||
if (key === 'Enter') {
|
||||
if (retentionPolicy.isNew) {
|
||||
this.handleCreate()
|
||||
return
|
||||
}
|
||||
|
||||
this.handleUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
getInputValues() {
|
||||
const {
|
||||
notify,
|
||||
retentionPolicy: {name: currentName},
|
||||
isRFDisplayed,
|
||||
} = this.props
|
||||
|
||||
const name = (this.name && this.name.value.trim()) || currentName
|
||||
let duration = this.duration.value.trim()
|
||||
// Replication > 1 is only valid for Influx Enterprise
|
||||
const replication = isRFDisplayed ? +this.replication.value.trim() : 1
|
||||
|
||||
if (!duration || (isRFDisplayed && !replication)) {
|
||||
notify('error', 'Fields cannot be empty')
|
||||
return
|
||||
}
|
||||
|
||||
if (duration === '∞') {
|
||||
duration = 'INF'
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
duration,
|
||||
replication,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, number, shape, string} = PropTypes
|
||||
|
|
|
@ -57,7 +57,7 @@ const Header = ({
|
|||
<button
|
||||
className="btn btn-xs btn-primary"
|
||||
disabled={isAddRPDisabled}
|
||||
onClick={() => onAddRetentionPolicy(database)}
|
||||
onClick={onAddRetentionPolicy(database)}
|
||||
>
|
||||
Add Retention Policy
|
||||
</button>
|
||||
|
@ -65,7 +65,7 @@ const Header = ({
|
|||
? null
|
||||
: <button
|
||||
className="btn btn-xs btn-danger"
|
||||
onClick={() => onStartDelete(database)}
|
||||
onClick={onStartDelete(database)}
|
||||
>
|
||||
Delete
|
||||
</button>}
|
||||
|
@ -88,8 +88,8 @@ const Header = ({
|
|||
type="text"
|
||||
value={database.deleteCode || ''}
|
||||
placeholder={`DELETE ${database.name}`}
|
||||
onChange={e => onDatabaseDeleteConfirm(database, e)}
|
||||
onKeyDown={e => onDatabaseDeleteConfirm(database, e)}
|
||||
onChange={onDatabaseDeleteConfirm(database)}
|
||||
onKeyDown={onDatabaseDeleteConfirm(database)}
|
||||
autoFocus={true}
|
||||
autoComplete={false}
|
||||
spellCheck={false}
|
||||
|
@ -121,8 +121,8 @@ const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) =>
|
|||
type="text"
|
||||
value={database.name}
|
||||
placeholder="Name this Database"
|
||||
onChange={e => onEdit(database, {name: e.target.value})}
|
||||
onKeyDown={e => onKeyDown(e, database)}
|
||||
onChange={onEdit(database)}
|
||||
onKeyDown={onKeyDown(database)}
|
||||
autoFocus={true}
|
||||
spellCheck={false}
|
||||
autoComplete={false}
|
||||
|
|
|
@ -3,14 +3,13 @@ import React, {Component, PropTypes} from 'react'
|
|||
class FilterBar extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
filterText: '',
|
||||
}
|
||||
|
||||
this.handleText = ::this.handleText
|
||||
}
|
||||
|
||||
handleText(e) {
|
||||
handleText = e => {
|
||||
this.setState(
|
||||
{filterText: e.target.value},
|
||||
this.props.onFilter(e.target.value)
|
||||
|
@ -43,7 +42,7 @@ class FilterBar extends Component {
|
|||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isEditing}
|
||||
onClick={() => onClickCreate(type)}
|
||||
onClick={onClickCreate(type)}
|
||||
>
|
||||
Create {placeholderText.substring(0, placeholderText.length - 1)}
|
||||
</button>
|
||||
|
|
|
@ -43,21 +43,6 @@ const isValidRole = role => {
|
|||
class AdminPage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickCreate = ::this.handleClickCreate
|
||||
this.handleEditUser = ::this.handleEditUser
|
||||
this.handleEditRole = ::this.handleEditRole
|
||||
this.handleSaveUser = ::this.handleSaveUser
|
||||
this.handleSaveRole = ::this.handleSaveRole
|
||||
this.handleCancelEditUser = ::this.handleCancelEditUser
|
||||
this.handleCancelEditRole = ::this.handleCancelEditRole
|
||||
this.handleDeleteRole = ::this.handleDeleteRole
|
||||
this.handleDeleteUser = ::this.handleDeleteUser
|
||||
this.handleUpdateRoleUsers = ::this.handleUpdateRoleUsers
|
||||
this.handleUpdateRolePermissions = ::this.handleUpdateRolePermissions
|
||||
this.handleUpdateUserPermissions = ::this.handleUpdateUserPermissions
|
||||
this.handleUpdateUserRoles = ::this.handleUpdateUserRoles
|
||||
this.handleUpdateUserPassword = ::this.handleUpdateUserPassword
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -70,7 +55,7 @@ class AdminPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleClickCreate(type) {
|
||||
handleClickCreate = type => () => {
|
||||
if (type === 'users') {
|
||||
this.props.addUser()
|
||||
} else if (type === 'roles') {
|
||||
|
@ -78,15 +63,15 @@ class AdminPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleEditUser(user, updates) {
|
||||
handleEditUser = (user, updates) => {
|
||||
this.props.editUser(user, updates)
|
||||
}
|
||||
|
||||
handleEditRole(role, updates) {
|
||||
handleEditRole = (role, updates) => {
|
||||
this.props.editRole(role, updates)
|
||||
}
|
||||
|
||||
async handleSaveUser(user) {
|
||||
handleSaveUser = async user => {
|
||||
const {notify} = this.props
|
||||
if (!isValidUser(user)) {
|
||||
notify('error', 'Username and/or password too short')
|
||||
|
@ -99,7 +84,7 @@ class AdminPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async handleSaveRole(role) {
|
||||
handleSaveRole = async role => {
|
||||
const {notify} = this.props
|
||||
if (!isValidRole(role)) {
|
||||
notify('error', 'Role name too short')
|
||||
|
@ -112,39 +97,39 @@ class AdminPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleCancelEditUser(user) {
|
||||
handleCancelEditUser = user => {
|
||||
this.props.removeUser(user)
|
||||
}
|
||||
|
||||
handleCancelEditRole(role) {
|
||||
handleCancelEditRole = role => {
|
||||
this.props.removeRole(role)
|
||||
}
|
||||
|
||||
handleDeleteRole(role) {
|
||||
handleDeleteRole = role => {
|
||||
this.props.deleteRole(role)
|
||||
}
|
||||
|
||||
handleDeleteUser(user) {
|
||||
handleDeleteUser = user => {
|
||||
this.props.deleteUser(user)
|
||||
}
|
||||
|
||||
handleUpdateRoleUsers(role, users) {
|
||||
handleUpdateRoleUsers = (role, users) => {
|
||||
this.props.updateRoleUsers(role, users)
|
||||
}
|
||||
|
||||
handleUpdateRolePermissions(role, permissions) {
|
||||
handleUpdateRolePermissions = (role, permissions) => {
|
||||
this.props.updateRolePermissions(role, permissions)
|
||||
}
|
||||
|
||||
handleUpdateUserPermissions(user, permissions) {
|
||||
handleUpdateUserPermissions = (user, permissions) => {
|
||||
this.props.updateUserPermissions(user, permissions)
|
||||
}
|
||||
|
||||
handleUpdateUserRoles(user, roles) {
|
||||
handleUpdateUserRoles = (user, roles) => {
|
||||
this.props.updateUserRoles(user, roles)
|
||||
}
|
||||
|
||||
handleUpdateUserPassword(user, password) {
|
||||
handleUpdateUserPassword = (user, password) => {
|
||||
this.props.updateUserPassword(user, password)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,6 @@ import {publishAutoDismissingNotification} from 'shared/dispatchers'
|
|||
class DatabaseManagerPage extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.handleKeyDownDatabase = ::this.handleKeyDownDatabase
|
||||
this.handleDatabaseDeleteConfirm = ::this.handleDatabaseDeleteConfirm
|
||||
this.handleCreateDatabase = ::this.handleCreateDatabase
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -32,22 +29,34 @@ class DatabaseManagerPage extends Component {
|
|||
onKeyDownDatabase={this.handleKeyDownDatabase}
|
||||
onDatabaseDeleteConfirm={this.handleDatabaseDeleteConfirm}
|
||||
addDatabase={actions.addDatabase}
|
||||
onEditDatabase={actions.editDatabase}
|
||||
onEditDatabase={this.handleEditDatabase}
|
||||
onCancelDatabase={actions.removeDatabase}
|
||||
onConfirmDatabase={this.handleCreateDatabase}
|
||||
onDeleteDatabase={actions.deleteDatabaseAsync}
|
||||
onStartDeleteDatabase={actions.addDatabaseDeleteCode}
|
||||
onStartDeleteDatabase={this.handleStartDeleteDatabase}
|
||||
onRemoveDeleteCode={actions.removeDatabaseDeleteCode}
|
||||
onAddRetentionPolicy={actions.addRetentionPolicy}
|
||||
onAddRetentionPolicy={this.handleAddRetentionPolicy}
|
||||
onCreateRetentionPolicy={actions.createRetentionPolicyAsync}
|
||||
onUpdateRetentionPolicy={actions.updateRetentionPolicyAsync}
|
||||
onRemoveRetentionPolicy={actions.removeRetentionPolicy}
|
||||
onDeleteRetentionPolicy={actions.deleteRetentionPolicyAsync}
|
||||
onDeleteRetentionPolicy={this.handleDeleteRetentionPolicy}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
handleCreateDatabase(database) {
|
||||
handleDeleteRetentionPolicy = (db, rp) => () => {
|
||||
this.props.actions.deleteRetentionPolicyAsync(db, rp)
|
||||
}
|
||||
|
||||
handleStartDeleteDatabase = database => () => {
|
||||
this.props.actions.addDatabaseDeleteCode(database)
|
||||
}
|
||||
|
||||
handleEditDatabase = database => e => {
|
||||
this.props.actions.editDatabase(database, {name: e.target.value})
|
||||
}
|
||||
|
||||
handleCreateDatabase = database => {
|
||||
const {actions, notify, source} = this.props
|
||||
|
||||
if (!database.name) {
|
||||
|
@ -57,7 +66,12 @@ class DatabaseManagerPage extends Component {
|
|||
actions.createDatabaseAsync(source.links.databases, database)
|
||||
}
|
||||
|
||||
handleKeyDownDatabase(e, database) {
|
||||
handleAddRetentionPolicy = database => () => {
|
||||
const {addRetentionPolicy} = this.props.actions
|
||||
addRetentionPolicy(database)
|
||||
}
|
||||
|
||||
handleKeyDownDatabase = database => e => {
|
||||
const {key} = e
|
||||
const {actions, notify, source} = this.props
|
||||
|
||||
|
@ -74,7 +88,7 @@ class DatabaseManagerPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDatabaseDeleteConfirm(database, e) {
|
||||
handleDatabaseDeleteConfirm = database => e => {
|
||||
const {key, target: {value}} = e
|
||||
const {actions, notify} = this.props
|
||||
|
||||
|
|
|
@ -19,18 +19,13 @@ class AlertsTable extends Component {
|
|||
sortDirection: null,
|
||||
sortKey: null,
|
||||
}
|
||||
|
||||
this.filterAlerts = ::this.filterAlerts
|
||||
this.changeSort = ::this.changeSort
|
||||
this.sortableClasses = ::this.sortableClasses
|
||||
this.sort = ::this.sort
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
this.filterAlerts(this.state.searchTerm, newProps.alerts)
|
||||
}
|
||||
|
||||
filterAlerts(searchTerm, newAlerts) {
|
||||
filterAlerts = (searchTerm, newAlerts) => {
|
||||
const alerts = newAlerts || this.props.alerts
|
||||
const filterText = searchTerm.toLowerCase()
|
||||
const filteredAlerts = alerts.filter(h => {
|
||||
|
@ -47,7 +42,7 @@ class AlertsTable extends Component {
|
|||
this.setState({searchTerm, filteredAlerts})
|
||||
}
|
||||
|
||||
changeSort(key) {
|
||||
changeSort = key => () => {
|
||||
// if we're using the key, reverse order; otherwise, set it with ascending
|
||||
if (this.state.sortKey === key) {
|
||||
const reverseDirection =
|
||||
|
@ -58,7 +53,7 @@ class AlertsTable extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
sortableClasses(key) {
|
||||
sortableClasses = key => () => {
|
||||
if (this.state.sortKey === key) {
|
||||
if (this.state.sortDirection === 'asc') {
|
||||
return 'alert-history-table--th sortable-header sorting-ascending'
|
||||
|
@ -68,7 +63,7 @@ class AlertsTable extends Component {
|
|||
return 'alert-history-table--th sortable-header'
|
||||
}
|
||||
|
||||
sort(alerts, key, direction) {
|
||||
sort = (alerts, key, direction) => {
|
||||
switch (direction) {
|
||||
case 'asc':
|
||||
return _.sortBy(alerts, e => e[key])
|
||||
|
@ -91,35 +86,35 @@ class AlertsTable extends Component {
|
|||
? <div className="alert-history-table">
|
||||
<div className="alert-history-table--thead">
|
||||
<div
|
||||
onClick={() => this.changeSort('name')}
|
||||
onClick={this.changeSort('name')}
|
||||
className={this.sortableClasses('name')}
|
||||
style={{width: colName}}
|
||||
>
|
||||
Name
|
||||
</div>
|
||||
<div
|
||||
onClick={() => this.changeSort('level')}
|
||||
onClick={this.changeSort('level')}
|
||||
className={this.sortableClasses('level')}
|
||||
style={{width: colLevel}}
|
||||
>
|
||||
Level
|
||||
</div>
|
||||
<div
|
||||
onClick={() => this.changeSort('time')}
|
||||
onClick={this.changeSort('time')}
|
||||
className={this.sortableClasses('time')}
|
||||
style={{width: colTime}}
|
||||
>
|
||||
Time
|
||||
</div>
|
||||
<div
|
||||
onClick={() => this.changeSort('host')}
|
||||
onClick={this.changeSort('host')}
|
||||
className={this.sortableClasses('host')}
|
||||
style={{width: colHost}}
|
||||
>
|
||||
Host
|
||||
</div>
|
||||
<div
|
||||
onClick={() => this.changeSort('value')}
|
||||
onClick={this.changeSort('value')}
|
||||
className={this.sortableClasses('value')}
|
||||
style={{width: colValue}}
|
||||
>
|
||||
|
|
|
@ -23,19 +23,6 @@ class CellEditorOverlay extends Component {
|
|||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.queryStateReducer = ::this.queryStateReducer
|
||||
this.handleAddQuery = ::this.handleAddQuery
|
||||
this.handleDeleteQuery = ::this.handleDeleteQuery
|
||||
this.handleSaveCell = ::this.handleSaveCell
|
||||
this.handleSelectGraphType = ::this.handleSelectGraphType
|
||||
this.handleClickDisplayOptionsTab = ::this.handleClickDisplayOptionsTab
|
||||
this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex
|
||||
this.handleEditRawText = ::this.handleEditRawText
|
||||
this.handleSetYAxisBoundMin = ::this.handleSetYAxisBoundMin
|
||||
this.handleSetYAxisBoundMax = ::this.handleSetYAxisBoundMax
|
||||
this.handleSetLabel = ::this.handleSetLabel
|
||||
this.getActiveQuery = ::this.getActiveQuery
|
||||
|
||||
const {cell: {name, type, queries, axes}} = props
|
||||
|
||||
const queriesWorkingDraft = _.cloneDeep(
|
||||
|
@ -65,21 +52,19 @@ class CellEditorOverlay extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
queryStateReducer(queryModifier) {
|
||||
return (queryID, payload) => {
|
||||
const {queriesWorkingDraft} = this.state
|
||||
const query = queriesWorkingDraft.find(q => q.id === queryID)
|
||||
queryStateReducer = queryModifier => (queryID, payload) => {
|
||||
const {queriesWorkingDraft} = this.state
|
||||
const query = queriesWorkingDraft.find(q => q.id === queryID)
|
||||
|
||||
const nextQuery = queryModifier(query, payload)
|
||||
const nextQuery = queryModifier(query, payload)
|
||||
|
||||
const nextQueries = queriesWorkingDraft.map(
|
||||
q => (q.id === query.id ? nextQuery : q)
|
||||
)
|
||||
this.setState({queriesWorkingDraft: nextQueries})
|
||||
}
|
||||
const nextQueries = queriesWorkingDraft.map(
|
||||
q => (q.id === query.id ? nextQuery : q)
|
||||
)
|
||||
this.setState({queriesWorkingDraft: nextQueries})
|
||||
}
|
||||
|
||||
handleSetYAxisBoundMin(min) {
|
||||
handleSetYAxisBoundMin = min => {
|
||||
const {axes} = this.state
|
||||
const {y: {bounds: [, max]}} = axes
|
||||
|
||||
|
@ -88,7 +73,7 @@ class CellEditorOverlay extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSetYAxisBoundMax(max) {
|
||||
handleSetYAxisBoundMax = max => {
|
||||
const {axes} = this.state
|
||||
const {y: {bounds: [min]}} = axes
|
||||
|
||||
|
@ -97,13 +82,13 @@ class CellEditorOverlay extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSetLabel(label) {
|
||||
handleSetLabel = label => {
|
||||
const {axes} = this.state
|
||||
|
||||
this.setState({axes: {...axes, y: {...axes.y, label}}})
|
||||
}
|
||||
|
||||
handleAddQuery() {
|
||||
handleAddQuery = () => {
|
||||
const {queriesWorkingDraft} = this.state
|
||||
const newIndex = queriesWorkingDraft.length
|
||||
|
||||
|
@ -116,14 +101,14 @@ class CellEditorOverlay extends Component {
|
|||
this.handleSetActiveQueryIndex(newIndex)
|
||||
}
|
||||
|
||||
handleDeleteQuery(index) {
|
||||
handleDeleteQuery = index => {
|
||||
const nextQueries = this.state.queriesWorkingDraft.filter(
|
||||
(__, i) => i !== index
|
||||
)
|
||||
this.setState({queriesWorkingDraft: nextQueries})
|
||||
}
|
||||
|
||||
handleSaveCell() {
|
||||
handleSaveCell = () => {
|
||||
const {
|
||||
queriesWorkingDraft,
|
||||
cellWorkingType: type,
|
||||
|
@ -152,21 +137,19 @@ class CellEditorOverlay extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSelectGraphType(graphType) {
|
||||
handleSelectGraphType = graphType => () => {
|
||||
this.setState({cellWorkingType: graphType})
|
||||
}
|
||||
|
||||
handleClickDisplayOptionsTab(isDisplayOptionsTabActive) {
|
||||
return () => {
|
||||
this.setState({isDisplayOptionsTabActive})
|
||||
}
|
||||
handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => {
|
||||
this.setState({isDisplayOptionsTabActive})
|
||||
}
|
||||
|
||||
handleSetActiveQueryIndex(activeQueryIndex) {
|
||||
handleSetActiveQueryIndex = activeQueryIndex => {
|
||||
this.setState({activeQueryIndex})
|
||||
}
|
||||
|
||||
getActiveQuery() {
|
||||
getActiveQuery = () => {
|
||||
const {queriesWorkingDraft, activeQueryIndex} = this.state
|
||||
const activeQuery = queriesWorkingDraft[activeQueryIndex]
|
||||
const defaultQuery = queriesWorkingDraft[0]
|
||||
|
@ -174,7 +157,7 @@ class CellEditorOverlay extends Component {
|
|||
return activeQuery || defaultQuery
|
||||
}
|
||||
|
||||
async handleEditRawText(url, id, text) {
|
||||
handleEditRawText = async (url, id, text) => {
|
||||
const templates = removeUnselectedTemplateValues(this.props.templates)
|
||||
|
||||
// use this as the handler passed into fetchTimeSeries to update a query status
|
||||
|
|
|
@ -6,23 +6,22 @@ class DashboardEditHeader extends Component {
|
|||
super(props)
|
||||
|
||||
const {dashboard: {name}} = props
|
||||
this.state = {name}
|
||||
this.handleChange = ::this.handleChange
|
||||
this.handleFormSubmit = ::this.handleFormSubmit
|
||||
this.handleKeyUp = ::this.handleKeyUp
|
||||
this.state = {
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(name) {
|
||||
this.setState({name})
|
||||
handleChange = e => {
|
||||
this.setState({name: e.target.value})
|
||||
}
|
||||
|
||||
handleFormSubmit(e) {
|
||||
handleFormSubmit = e => {
|
||||
e.preventDefault()
|
||||
const name = e.target.name.value
|
||||
this.props.onSave(name)
|
||||
}
|
||||
|
||||
handleKeyUp(e) {
|
||||
handleKeyUp = e => {
|
||||
const {onCancel} = this.props
|
||||
if (e.key === 'Escape') {
|
||||
onCancel()
|
||||
|
@ -46,11 +45,11 @@ class DashboardEditHeader extends Component {
|
|||
name="name"
|
||||
value={name}
|
||||
placeholder="Name this Dashboard"
|
||||
onChange={e => this.handleChange(e.target.value)}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
autoFocus={true}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
<ConfirmButtons item={name} onConfirm={onSave} onCancel={onCancel} />
|
||||
|
|
|
@ -14,7 +14,7 @@ const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) =>
|
|||
active: graphType.type === selectedGraphType,
|
||||
})}
|
||||
>
|
||||
<div onClick={() => onSelectGraphType(graphType.type)}>
|
||||
<div onClick={onSelectGraphType(graphType.type)}>
|
||||
{graphType.graphic}
|
||||
<p>
|
||||
{graphType.menuOption}
|
||||
|
|
|
@ -11,8 +11,6 @@ class MeasurementDropdown extends Component {
|
|||
this.state = {
|
||||
measurements: [],
|
||||
}
|
||||
|
||||
this._getMeasurements = ::this._getMeasurements
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -35,12 +33,12 @@ class MeasurementDropdown extends Component {
|
|||
items={measurements.map(text => ({text}))}
|
||||
selected={measurement || 'Select Measurement'}
|
||||
onChoose={onSelectMeasurement}
|
||||
onClick={() => onStartEdit(null)}
|
||||
onClick={onStartEdit}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
async _getMeasurements() {
|
||||
_getMeasurements = async () => {
|
||||
const {source: {links: {proxy}}} = this.context
|
||||
const {
|
||||
measurement,
|
||||
|
|
|
@ -23,17 +23,6 @@ class QueryTextArea extends Component {
|
|||
},
|
||||
filteredTemplates: this.props.templates,
|
||||
}
|
||||
|
||||
this.handleKeyDown = ::this.handleKeyDown
|
||||
this.handleChange = ::this.handleChange
|
||||
this.handleUpdate = ::this.handleUpdate
|
||||
this.handleChooseTemplate = ::this.handleChooseTemplate
|
||||
this.handleCloseDrawer = ::this.handleCloseDrawer
|
||||
this.findTempVar = ::this.findTempVar
|
||||
this.handleTemplateReplace = ::this.handleTemplateReplace
|
||||
this.handleMouseOverTempVar = ::this.handleMouseOverTempVar
|
||||
this.handleClickTempVar = ::this.handleClickTempVar
|
||||
this.closeDrawer = ::this.closeDrawer
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -42,21 +31,21 @@ class QueryTextArea extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleCloseDrawer() {
|
||||
handleCloseDrawer = () => {
|
||||
this.setState({isTemplating: false})
|
||||
}
|
||||
|
||||
handleMouseOverTempVar(template) {
|
||||
handleMouseOverTempVar = template => () => {
|
||||
this.handleTemplateReplace(template)
|
||||
}
|
||||
|
||||
handleClickTempVar(template) {
|
||||
handleClickTempVar = template => () => {
|
||||
// Clicking a tempVar does the same thing as hitting 'Enter'
|
||||
this.handleTemplateReplace(template, true)
|
||||
this.closeDrawer()
|
||||
}
|
||||
|
||||
closeDrawer() {
|
||||
closeDrawer = () => {
|
||||
this.setState({
|
||||
isTemplating: false,
|
||||
selectedTemplate: {
|
||||
|
@ -65,7 +54,7 @@ class QueryTextArea extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
handleKeyDown = e => {
|
||||
const {isTemplating, value} = this.state
|
||||
|
||||
if (isTemplating) {
|
||||
|
@ -96,7 +85,7 @@ class QueryTextArea extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleTemplateReplace(selectedTemplate, replaceWholeTemplate) {
|
||||
handleTemplateReplace = (selectedTemplate, replaceWholeTemplate) => {
|
||||
const {selectionStart, value} = this.editor
|
||||
const {tempVar} = selectedTemplate
|
||||
const newTempVar = replaceWholeTemplate
|
||||
|
@ -125,7 +114,7 @@ class QueryTextArea extends Component {
|
|||
)
|
||||
}
|
||||
|
||||
findTempVar(direction) {
|
||||
findTempVar = direction => {
|
||||
const {filteredTemplates: templates} = this.state
|
||||
const {selectedTemplate} = this.state
|
||||
|
||||
|
@ -149,7 +138,7 @@ class QueryTextArea extends Component {
|
|||
return templates[0]
|
||||
}
|
||||
|
||||
handleChange() {
|
||||
handleChange = () => {
|
||||
const {templates} = this.props
|
||||
const {selectedTemplate} = this.state
|
||||
const value = this.editor.value
|
||||
|
@ -186,14 +175,10 @@ class QueryTextArea extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleUpdate() {
|
||||
handleUpdate = () => {
|
||||
this.props.onUpdate(this.state.value)
|
||||
}
|
||||
|
||||
handleChooseTemplate(template) {
|
||||
this.setState({value: template.query})
|
||||
}
|
||||
|
||||
handleSelectTempVar(tempVar) {
|
||||
this.setState({selectedTemplate: tempVar})
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ class TagKeyDropdown extends Component {
|
|||
this.state = {
|
||||
tagKeys: [],
|
||||
}
|
||||
|
||||
this._getTags = ::this._getTags
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -32,18 +30,20 @@ class TagKeyDropdown extends Component {
|
|||
|
||||
render() {
|
||||
const {tagKeys} = this.state
|
||||
const {tagKey, onSelectTagKey, onStartEdit} = this.props
|
||||
const {tagKey, onSelectTagKey} = this.props
|
||||
return (
|
||||
<Dropdown
|
||||
items={tagKeys.map(text => ({text}))}
|
||||
selected={tagKey || 'Select Tag Key'}
|
||||
onChoose={onSelectTagKey}
|
||||
onClick={() => onStartEdit(null)}
|
||||
onClick={this.handleStartEdit}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
async _getTags() {
|
||||
handleStartEdit = () => this.props.onStartEdit(null)
|
||||
|
||||
_getTags = async () => {
|
||||
const {
|
||||
database,
|
||||
measurement,
|
||||
|
|
|
@ -4,17 +4,15 @@ import calculateSize from 'calculate-size'
|
|||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
|
||||
import omit from 'lodash/omit'
|
||||
|
||||
const minTempVarDropdownWidth = 146
|
||||
const maxTempVarDropdownWidth = 300
|
||||
const tempVarDropdownPadding = 30
|
||||
|
||||
const TemplateControlBar = ({
|
||||
isOpen,
|
||||
templates,
|
||||
onSelectTemplate,
|
||||
onOpenTemplateManager,
|
||||
isOpen,
|
||||
}) =>
|
||||
<div className={classnames('template-control-bar', {show: isOpen})}>
|
||||
<div className="template-control--container">
|
||||
|
@ -65,8 +63,7 @@ const TemplateControlBar = ({
|
|||
menuClass="dropdown-astronaut"
|
||||
useAutoComplete={true}
|
||||
selected={selectedText || '(No values)'}
|
||||
onChoose={item =>
|
||||
onSelectTemplate(id, [item].map(x => omit(x, 'text')))}
|
||||
onChoose={onSelectTemplate(id)}
|
||||
/>
|
||||
<label className="template-control--label">
|
||||
{tempVar}
|
||||
|
|
|
@ -2,22 +2,22 @@ import React, {Component, PropTypes} from 'react'
|
|||
import classnames from 'classnames'
|
||||
import uuid from 'node-uuid'
|
||||
|
||||
import TemplateVariableTable from 'src/dashboards/components/TemplateVariableTable'
|
||||
import TemplateVariableTable from 'src/dashboards/components/template_variables/Table'
|
||||
|
||||
import {TEMPLATE_VARIABLE_TYPES} from 'src/dashboards/constants'
|
||||
|
||||
const TemplateVariableManager = ({
|
||||
onClose,
|
||||
onEditTemplateVariables,
|
||||
source,
|
||||
onClose,
|
||||
onDelete,
|
||||
isEdited,
|
||||
templates,
|
||||
onAddVariable,
|
||||
onRunQuerySuccess,
|
||||
onRunQueryFailure,
|
||||
onSaveTemplatesSuccess,
|
||||
onAddVariable,
|
||||
onDelete,
|
||||
tempVarAlreadyExists,
|
||||
isEdited,
|
||||
onSaveTemplatesSuccess,
|
||||
onEditTemplateVariables,
|
||||
}) =>
|
||||
<div className="template-variable-manager">
|
||||
<div className="template-variable-manager--header">
|
||||
|
@ -41,19 +41,16 @@ const TemplateVariableManager = ({
|
|||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<span
|
||||
className="page-header__dismiss"
|
||||
onClick={() => onClose(isEdited)}
|
||||
/>
|
||||
<span className="page-header__dismiss" onClick={onClose(isEdited)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="template-variable-manager--body">
|
||||
<TemplateVariableTable
|
||||
source={source}
|
||||
onDelete={onDelete}
|
||||
templates={templates}
|
||||
onRunQuerySuccess={onRunQuerySuccess}
|
||||
onRunQueryFailure={onRunQueryFailure}
|
||||
onDelete={onDelete}
|
||||
tempVarAlreadyExists={tempVarAlreadyExists}
|
||||
/>
|
||||
</div>
|
|
@ -8,8 +8,10 @@ import OnClickOutside from 'react-onclickoutside'
|
|||
import classnames from 'classnames'
|
||||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import DeleteConfirmButtons from 'shared/components/DeleteConfirmButtons'
|
||||
import TemplateQueryBuilder from 'src/dashboards/components/TemplateQueryBuilder'
|
||||
import TemplateQueryBuilder from 'src/dashboards/components/template_variables/TemplateQueryBuilder'
|
||||
import TableInput from 'src/dashboards/components/template_variables/TableInput'
|
||||
import RowValues from 'src/dashboards/components/template_variables/RowValues'
|
||||
import RowButtons from 'src/dashboards/components/template_variables/RowButtons'
|
||||
|
||||
import {runTemplateVariableQuery as runTemplateVariableQueryAJAX} from 'src/dashboards/apis'
|
||||
|
||||
|
@ -23,79 +25,6 @@ import {publishAutoDismissingNotification} from 'shared/dispatchers'
|
|||
|
||||
const compact = values => uniq(values).filter(value => /\S/.test(value))
|
||||
|
||||
const RowValues = ({
|
||||
selectedType,
|
||||
values = [],
|
||||
isEditing,
|
||||
onStartEdit,
|
||||
autoFocusTarget,
|
||||
}) => {
|
||||
const _values = values.map(v => v.value).join(', ')
|
||||
|
||||
if (selectedType === 'csv') {
|
||||
return (
|
||||
<TableInput
|
||||
name="values"
|
||||
defaultValue={_values}
|
||||
isEditing={isEditing}
|
||||
onStartEdit={onStartEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
spellCheck={false}
|
||||
autoComplete={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={values.length ? 'tvm-values' : 'tvm-values-empty'}>
|
||||
{values.length ? _values : 'No values to display'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const RowButtons = ({
|
||||
onStartEdit,
|
||||
isEditing,
|
||||
onCancelEdit,
|
||||
onDelete,
|
||||
id,
|
||||
selectedType,
|
||||
}) => {
|
||||
if (isEditing) {
|
||||
return (
|
||||
<div className="tvm-actions">
|
||||
<button
|
||||
className="btn btn-sm btn-info"
|
||||
type="button"
|
||||
onClick={onCancelEdit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-sm btn-success" type="submit">
|
||||
{selectedType === 'csv' ? 'Save Values' : 'Get Values'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="tvm-actions">
|
||||
<DeleteConfirmButtons onDelete={() => onDelete(id)} />
|
||||
<button
|
||||
className="btn btn-sm btn-info btn-edit btn-square"
|
||||
type="button"
|
||||
onClick={e => {
|
||||
// prevent subsequent 'onSubmit' that is caused by an unknown source,
|
||||
// possible onClickOutside, after 'onClick'. this allows
|
||||
// us to enter 'isEditing' mode
|
||||
e.preventDefault()
|
||||
onStartEdit('tempVar')
|
||||
}}
|
||||
>
|
||||
<span className="icon pencil" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TemplateVariableRow = ({
|
||||
template: {id, tempVar, values},
|
||||
isEditing,
|
||||
|
@ -111,8 +40,8 @@ const TemplateVariableRow = ({
|
|||
onCancelEdit,
|
||||
autoFocusTarget,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
onErrorThrown,
|
||||
onDeleteTempVar,
|
||||
}) =>
|
||||
<form
|
||||
className={classnames('template-variable-manager--table-row', {
|
||||
|
@ -138,7 +67,7 @@ const TemplateVariableRow = ({
|
|||
<Dropdown
|
||||
items={TEMPLATE_TYPES}
|
||||
onChoose={onSelectType}
|
||||
onClick={() => onStartEdit(null)}
|
||||
onClick={onStartEdit}
|
||||
selected={TEMPLATE_TYPES.find(t => t.type === selectedType).text}
|
||||
className="dropdown-140"
|
||||
/>
|
||||
|
@ -168,42 +97,13 @@ const TemplateVariableRow = ({
|
|||
onStartEdit={onStartEdit}
|
||||
isEditing={isEditing}
|
||||
onCancelEdit={onCancelEdit}
|
||||
onDelete={onDelete}
|
||||
onDelete={onDeleteTempVar}
|
||||
id={id}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
const TableInput = ({
|
||||
name,
|
||||
defaultValue,
|
||||
isEditing,
|
||||
onStartEdit,
|
||||
autoFocusTarget,
|
||||
}) => {
|
||||
return isEditing
|
||||
? <div name={name} style={{width: '100%'}}>
|
||||
<input
|
||||
required={true}
|
||||
name={name}
|
||||
autoFocus={name === autoFocusTarget}
|
||||
className="form-control input-sm tvm-input-edit"
|
||||
type="text"
|
||||
defaultValue={
|
||||
name === 'tempVar'
|
||||
? defaultValue.replace(/\u003a/g, '') // remove ':'s
|
||||
: defaultValue
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
: <div style={{width: '100%'}} onClick={() => onStartEdit(name)}>
|
||||
<div className="tvm-input">
|
||||
{defaultValue}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
class RowWrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
@ -220,85 +120,74 @@ class RowWrapper extends Component {
|
|||
autoFocusTarget: 'tempVar',
|
||||
}
|
||||
|
||||
this.handleSubmit = ::this.handleSubmit
|
||||
this.handleSelectType = ::this.handleSelectType
|
||||
this.handleSelectDatabase = ::this.handleSelectDatabase
|
||||
this.handleSelectMeasurement = ::this.handleSelectMeasurement
|
||||
this.handleSelectTagKey = ::this.handleSelectTagKey
|
||||
this.handleStartEdit = ::this.handleStartEdit
|
||||
this.handleCancelEdit = ::this.handleCancelEdit
|
||||
this.runTemplateVariableQuery = ::this.runTemplateVariableQuery
|
||||
}
|
||||
|
||||
handleSubmit({
|
||||
handleSubmit = ({
|
||||
selectedDatabase: database,
|
||||
selectedMeasurement: measurement,
|
||||
selectedTagKey: tagKey,
|
||||
selectedType: type,
|
||||
}) {
|
||||
return async e => {
|
||||
e.preventDefault()
|
||||
}) => async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const {
|
||||
source,
|
||||
template,
|
||||
template: {id},
|
||||
onRunQuerySuccess,
|
||||
onRunQueryFailure,
|
||||
tempVarAlreadyExists,
|
||||
notify,
|
||||
} = this.props
|
||||
const {
|
||||
source,
|
||||
template,
|
||||
template: {id},
|
||||
onRunQuerySuccess,
|
||||
onRunQueryFailure,
|
||||
tempVarAlreadyExists,
|
||||
notify,
|
||||
} = this.props
|
||||
|
||||
const _tempVar = e.target.tempVar.value.replace(/\u003a/g, '')
|
||||
const tempVar = `\u003a${_tempVar}\u003a` // add ':'s
|
||||
const _tempVar = e.target.tempVar.value.replace(/\u003a/g, '')
|
||||
const tempVar = `\u003a${_tempVar}\u003a` // add ':'s
|
||||
|
||||
if (tempVarAlreadyExists(tempVar, id)) {
|
||||
return notify(
|
||||
'error',
|
||||
`Variable '${_tempVar}' already exists. Please enter a new value.`
|
||||
)
|
||||
}
|
||||
if (tempVarAlreadyExists(tempVar, id)) {
|
||||
return notify(
|
||||
'error',
|
||||
`Variable '${_tempVar}' already exists. Please enter a new value.`
|
||||
)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isEditing: false,
|
||||
hasBeenSavedToComponentStateOnce: true,
|
||||
})
|
||||
this.setState({
|
||||
isEditing: false,
|
||||
hasBeenSavedToComponentStateOnce: true,
|
||||
})
|
||||
|
||||
const {query, tempVars} = generateTemplateVariableQuery({
|
||||
type,
|
||||
tempVar,
|
||||
query: {
|
||||
database,
|
||||
// rp, TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
},
|
||||
})
|
||||
|
||||
const queryConfig = {
|
||||
type,
|
||||
tempVars,
|
||||
query,
|
||||
const {query, tempVars} = generateTemplateVariableQuery({
|
||||
type,
|
||||
tempVar,
|
||||
query: {
|
||||
database,
|
||||
// rp: TODO
|
||||
// rp, TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
},
|
||||
})
|
||||
|
||||
const queryConfig = {
|
||||
type,
|
||||
tempVars,
|
||||
query,
|
||||
database,
|
||||
// rp: TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
}
|
||||
|
||||
try {
|
||||
let parsedData
|
||||
if (type === 'csv') {
|
||||
parsedData = e.target.values.value.split(',').map(value => value.trim())
|
||||
} else {
|
||||
parsedData = await this.runTemplateVariableQuery(source, queryConfig)
|
||||
}
|
||||
|
||||
try {
|
||||
let parsedData
|
||||
if (type === 'csv') {
|
||||
parsedData = e.target.values.value
|
||||
.split(',')
|
||||
.map(value => value.trim())
|
||||
} else {
|
||||
parsedData = await this.runTemplateVariableQuery(source, queryConfig)
|
||||
}
|
||||
|
||||
onRunQuerySuccess(template, queryConfig, compact(parsedData), tempVar)
|
||||
} catch (error) {
|
||||
onRunQueryFailure(error)
|
||||
}
|
||||
onRunQuerySuccess(template, queryConfig, compact(parsedData), tempVar)
|
||||
} catch (error) {
|
||||
onRunQueryFailure(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,11 +195,11 @@ class RowWrapper extends Component {
|
|||
this.handleCancelEdit()
|
||||
}
|
||||
|
||||
handleStartEdit(name) {
|
||||
handleStartEdit = name => () => {
|
||||
this.setState({isEditing: true, autoFocusTarget: name})
|
||||
}
|
||||
|
||||
handleCancelEdit() {
|
||||
handleCancelEdit = () => {
|
||||
const {
|
||||
template: {type, query: {db, measurement, tagKey}, id},
|
||||
onDelete,
|
||||
|
@ -329,7 +218,7 @@ class RowWrapper extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSelectType(item) {
|
||||
handleSelectType = item => {
|
||||
this.setState({
|
||||
selectedType: item.type,
|
||||
selectedDatabase: null,
|
||||
|
@ -338,22 +227,22 @@ class RowWrapper extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSelectDatabase(item) {
|
||||
handleSelectDatabase = item => {
|
||||
this.setState({selectedDatabase: item.text})
|
||||
}
|
||||
|
||||
handleSelectMeasurement(item) {
|
||||
handleSelectMeasurement = item => {
|
||||
this.setState({selectedMeasurement: item.text})
|
||||
}
|
||||
|
||||
handleSelectTagKey(item) {
|
||||
handleSelectTagKey = item => {
|
||||
this.setState({selectedTagKey: item.text})
|
||||
}
|
||||
|
||||
async runTemplateVariableQuery(
|
||||
runTemplateVariableQuery = async (
|
||||
source,
|
||||
{query, database, rp, tempVars, type, measurement, tagKey}
|
||||
) {
|
||||
) => {
|
||||
try {
|
||||
const {data} = await runTemplateVariableQueryAJAX(source, {
|
||||
query,
|
||||
|
@ -373,6 +262,10 @@ class RowWrapper extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDelete = id => () => {
|
||||
this.props.onDelete(id)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isEditing,
|
||||
|
@ -399,6 +292,7 @@ class RowWrapper extends Component {
|
|||
onCancelEdit={this.handleCancelEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
onSubmit={this.handleSubmit}
|
||||
onDeleteTempVar={this.handleDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -453,31 +347,6 @@ TemplateVariableRow.propTypes = {
|
|||
onErrorThrown: func.isRequired,
|
||||
}
|
||||
|
||||
TableInput.propTypes = {
|
||||
defaultValue: string,
|
||||
isEditing: bool.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
name: string.isRequired,
|
||||
autoFocusTarget: string,
|
||||
}
|
||||
|
||||
RowValues.propTypes = {
|
||||
selectedType: string.isRequired,
|
||||
values: arrayOf(shape()),
|
||||
isEditing: bool.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
autoFocusTarget: string,
|
||||
}
|
||||
|
||||
RowButtons.propTypes = {
|
||||
onStartEdit: func.isRequired,
|
||||
isEditing: bool.isRequired,
|
||||
onCancelEdit: func.isRequired,
|
||||
onDelete: func.isRequired,
|
||||
id: string.isRequired,
|
||||
selectedType: string.isRequired,
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onErrorThrown: bindActionCreators(errorThrownAction, dispatch),
|
||||
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
|
|
@ -0,0 +1,53 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import DeleteConfirmButtons from 'shared/components/DeleteConfirmButtons'
|
||||
|
||||
const RowButtons = ({
|
||||
onStartEdit,
|
||||
isEditing,
|
||||
onCancelEdit,
|
||||
onDelete,
|
||||
id,
|
||||
selectedType,
|
||||
}) => {
|
||||
if (isEditing) {
|
||||
return (
|
||||
<div className="tvm-actions">
|
||||
<button
|
||||
className="btn btn-sm btn-info"
|
||||
type="button"
|
||||
onClick={onCancelEdit}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-sm btn-success" type="submit">
|
||||
{selectedType === 'csv' ? 'Save Values' : 'Get Values'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="tvm-actions">
|
||||
<DeleteConfirmButtons onDelete={onDelete(id)} />
|
||||
<button
|
||||
className="btn btn-sm btn-info btn-edit btn-square"
|
||||
type="button"
|
||||
onClick={onStartEdit('tempVar')}
|
||||
>
|
||||
<span className="icon pencil" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {bool, func, string} = PropTypes
|
||||
|
||||
RowButtons.propTypes = {
|
||||
onStartEdit: func.isRequired,
|
||||
isEditing: bool.isRequired,
|
||||
onCancelEdit: func.isRequired,
|
||||
onDelete: func.isRequired,
|
||||
id: string.isRequired,
|
||||
selectedType: string.isRequired,
|
||||
}
|
||||
|
||||
export default RowButtons
|
|
@ -0,0 +1,43 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import TableInput from 'src/dashboards/components/template_variables/TableInput'
|
||||
|
||||
const RowValues = ({
|
||||
selectedType,
|
||||
values = [],
|
||||
isEditing,
|
||||
onStartEdit,
|
||||
autoFocusTarget,
|
||||
}) => {
|
||||
const _values = values.map(v => v.value).join(', ')
|
||||
|
||||
if (selectedType === 'csv') {
|
||||
return (
|
||||
<TableInput
|
||||
name="values"
|
||||
defaultValue={_values}
|
||||
isEditing={isEditing}
|
||||
onStartEdit={onStartEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
spellCheck={false}
|
||||
autoComplete={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={values.length ? 'tvm-values' : 'tvm-values-empty'}>
|
||||
{values.length ? _values : 'No values to display'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
RowValues.propTypes = {
|
||||
selectedType: string.isRequired,
|
||||
values: arrayOf(shape()),
|
||||
isEditing: bool.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
autoFocusTarget: string,
|
||||
}
|
||||
|
||||
export default RowValues
|
|
@ -1,6 +1,6 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
import TemplateVariableRow from 'src/dashboards/components/TemplateVariableRow'
|
||||
import TemplateVariableRow from 'src/dashboards/components/template_variables/Row'
|
||||
|
||||
const TemplateVariableTable = ({
|
||||
source,
|
|
@ -0,0 +1,42 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
const TableInput = ({
|
||||
name,
|
||||
defaultValue,
|
||||
isEditing,
|
||||
onStartEdit,
|
||||
autoFocusTarget,
|
||||
}) => {
|
||||
return isEditing
|
||||
? <div name={name} style={{width: '100%'}}>
|
||||
<input
|
||||
required={true}
|
||||
name={name}
|
||||
autoFocus={name === autoFocusTarget}
|
||||
className="form-control input-sm tvm-input-edit"
|
||||
type="text"
|
||||
defaultValue={
|
||||
name === 'tempVar'
|
||||
? defaultValue.replace(/\u003a/g, '') // remove ':'s
|
||||
: defaultValue
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
: <div style={{width: '100%'}} onClick={onStartEdit(name)}>
|
||||
<div className="tvm-input">
|
||||
{defaultValue}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
const {bool, func, string} = PropTypes
|
||||
|
||||
TableInput.propTypes = {
|
||||
defaultValue: string,
|
||||
isEditing: bool.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
name: string.isRequired,
|
||||
autoFocusTarget: string,
|
||||
}
|
||||
|
||||
export default TableInput
|
|
@ -10,7 +10,7 @@ import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay'
|
|||
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
||||
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
|
||||
import Dashboard from 'src/dashboards/components/Dashboard'
|
||||
import TemplateVariableManager from 'src/dashboards/components/TemplateVariableManager'
|
||||
import TemplateVariableManager from 'src/dashboards/components/template_variables/Manager'
|
||||
|
||||
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
||||
|
||||
|
@ -27,33 +27,11 @@ class DashboardPage extends Component {
|
|||
super(props)
|
||||
|
||||
this.state = {
|
||||
selectedCell: null,
|
||||
isEditMode: false,
|
||||
isTemplating: false,
|
||||
dygraphs: [],
|
||||
isEditMode: false,
|
||||
selectedCell: null,
|
||||
isTemplating: false,
|
||||
}
|
||||
|
||||
this.handleAddCell = ::this.handleAddCell
|
||||
this.handleEditDashboard = ::this.handleEditDashboard
|
||||
this.handleSaveEditedCell = ::this.handleSaveEditedCell
|
||||
this.handleDismissOverlay = ::this.handleDismissOverlay
|
||||
this.handleUpdatePosition = ::this.handleUpdatePosition
|
||||
this.handleChooseTimeRange = ::this.handleChooseTimeRange
|
||||
this.handleRenameDashboard = ::this.handleRenameDashboard
|
||||
this.handleEditDashboardCell = ::this.handleEditDashboardCell
|
||||
this.handleCancelEditDashboard = ::this.handleCancelEditDashboard
|
||||
this.handleDeleteDashboardCell = ::this.handleDeleteDashboardCell
|
||||
this.handleOpenTemplateManager = ::this.handleOpenTemplateManager
|
||||
this.handleUpdateDashboardCell = ::this.handleUpdateDashboardCell
|
||||
this.handleCloseTemplateManager = ::this.handleCloseTemplateManager
|
||||
this.handleSummonOverlayTechnologies = ::this
|
||||
.handleSummonOverlayTechnologies
|
||||
this.handleRunTemplateVariableQuery = ::this.handleRunTemplateVariableQuery
|
||||
this.handleSelectTemplate = ::this.handleSelectTemplate
|
||||
this.handleEditTemplateVariables = ::this.handleEditTemplateVariables
|
||||
this.handleRunQueryFailure = ::this.handleRunQueryFailure
|
||||
this.handleToggleTempVarControls = ::this.handleToggleTempVarControls
|
||||
this.synchronizer = ::this.synchronizer
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
|
@ -75,11 +53,11 @@ class DashboardPage extends Component {
|
|||
await putDashboardByID(dashboardID)
|
||||
}
|
||||
|
||||
handleOpenTemplateManager() {
|
||||
handleOpenTemplateManager = () => {
|
||||
this.setState({isTemplating: true})
|
||||
}
|
||||
|
||||
handleCloseTemplateManager(isEdited) {
|
||||
handleCloseTemplateManager = isEdited => () => {
|
||||
if (
|
||||
!isEdited ||
|
||||
(isEdited && confirm('Do you want to close without saving?')) // eslint-disable-line no-alert
|
||||
|
@ -88,43 +66,43 @@ class DashboardPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDismissOverlay() {
|
||||
handleDismissOverlay = () => {
|
||||
this.setState({selectedCell: null})
|
||||
}
|
||||
|
||||
handleSaveEditedCell(newCell) {
|
||||
handleSaveEditedCell = newCell => {
|
||||
this.props.dashboardActions
|
||||
.updateDashboardCell(this.getActiveDashboard(), newCell)
|
||||
.then(this.handleDismissOverlay)
|
||||
}
|
||||
|
||||
handleSummonOverlayTechnologies(cell) {
|
||||
handleSummonOverlayTechnologies = cell => {
|
||||
this.setState({selectedCell: cell})
|
||||
}
|
||||
|
||||
handleChooseTimeRange(timeRange) {
|
||||
handleChooseTimeRange = timeRange => {
|
||||
this.props.dashboardActions.setTimeRange(timeRange)
|
||||
}
|
||||
|
||||
handleUpdatePosition(cells) {
|
||||
handleUpdatePosition = cells => {
|
||||
const newDashboard = {...this.getActiveDashboard(), cells}
|
||||
this.props.dashboardActions.updateDashboard(newDashboard)
|
||||
this.props.dashboardActions.putDashboard(newDashboard)
|
||||
}
|
||||
|
||||
handleAddCell() {
|
||||
handleAddCell = () => {
|
||||
this.props.dashboardActions.addDashboardCellAsync(this.getActiveDashboard())
|
||||
}
|
||||
|
||||
handleEditDashboard() {
|
||||
handleEditDashboard = () => {
|
||||
this.setState({isEditMode: true})
|
||||
}
|
||||
|
||||
handleCancelEditDashboard() {
|
||||
handleCancelEditDashboard = () => {
|
||||
this.setState({isEditMode: false})
|
||||
}
|
||||
|
||||
handleRenameDashboard(name) {
|
||||
handleRenameDashboard = name => {
|
||||
this.setState({isEditMode: false})
|
||||
const newDashboard = {...this.getActiveDashboard(), name}
|
||||
this.props.dashboardActions.updateDashboard(newDashboard)
|
||||
|
@ -132,7 +110,7 @@ class DashboardPage extends Component {
|
|||
}
|
||||
|
||||
// Places cell into editing mode.
|
||||
handleEditDashboardCell(x, y, isEditing) {
|
||||
handleEditDashboardCell = (x, y, isEditing) => {
|
||||
return () => {
|
||||
this.props.dashboardActions.editDashboardCell(
|
||||
this.getActiveDashboard(),
|
||||
|
@ -143,7 +121,7 @@ class DashboardPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleUpdateDashboardCell(newCell) {
|
||||
handleUpdateDashboardCell = newCell => {
|
||||
return () => {
|
||||
this.props.dashboardActions.updateDashboardCell(
|
||||
this.getActiveDashboard(),
|
||||
|
@ -152,60 +130,41 @@ class DashboardPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDeleteDashboardCell(cell) {
|
||||
handleDeleteDashboardCell = cell => {
|
||||
const dashboard = this.getActiveDashboard()
|
||||
this.props.dashboardActions.deleteDashboardCellAsync(dashboard, cell)
|
||||
}
|
||||
|
||||
handleSelectTemplate(templateID, values) {
|
||||
handleSelectTemplate = templateID => values => {
|
||||
const {params: {dashboardID}} = this.props
|
||||
this.props.dashboardActions.templateVariableSelected(
|
||||
+dashboardID,
|
||||
templateID,
|
||||
values
|
||||
[values]
|
||||
)
|
||||
}
|
||||
|
||||
handleRunTemplateVariableQuery(
|
||||
templateVariable,
|
||||
{query, db, tempVars, type, tagKey, measurement}
|
||||
) {
|
||||
const {source} = this.props
|
||||
this.props.dashboardActions.runTemplateVariableQueryAsync(
|
||||
templateVariable,
|
||||
{
|
||||
source,
|
||||
query,
|
||||
db,
|
||||
// rp, TODO
|
||||
tempVars,
|
||||
type,
|
||||
tagKey,
|
||||
measurement,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleEditTemplateVariables(templates, onSaveTemplatesSuccess) {
|
||||
return async () => {
|
||||
try {
|
||||
await this.props.dashboardActions.putDashboard({
|
||||
...this.getActiveDashboard(),
|
||||
templates,
|
||||
})
|
||||
onSaveTemplatesSuccess()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
handleEditTemplateVariables = (
|
||||
templates,
|
||||
onSaveTemplatesSuccess
|
||||
) => async () => {
|
||||
try {
|
||||
await this.props.dashboardActions.putDashboard({
|
||||
...this.getActiveDashboard(),
|
||||
templates,
|
||||
})
|
||||
onSaveTemplatesSuccess()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
handleRunQueryFailure(error) {
|
||||
handleRunQueryFailure = error => {
|
||||
console.error(error)
|
||||
this.props.errorThrown(error)
|
||||
}
|
||||
|
||||
synchronizer(dygraph) {
|
||||
synchronizer = dygraph => {
|
||||
const dygraphs = [...this.state.dygraphs, dygraph]
|
||||
const {dashboards, params} = this.props
|
||||
const dashboard = dashboards.find(d => d.id === +params.dashboardID)
|
||||
|
@ -223,11 +182,11 @@ class DashboardPage extends Component {
|
|||
this.setState({dygraphs})
|
||||
}
|
||||
|
||||
handleToggleTempVarControls() {
|
||||
handleToggleTempVarControls = () => {
|
||||
this.props.templateControlBarVisibilityToggled()
|
||||
}
|
||||
|
||||
handleCancelEditCell(cellID) {
|
||||
handleCancelEditCell = cellID => {
|
||||
this.props.dashboardActions.cancelEditCell(
|
||||
this.getActiveDashboard().id,
|
||||
cellID
|
||||
|
@ -315,49 +274,49 @@ class DashboardPage extends Component {
|
|||
{isTemplating
|
||||
? <OverlayTechnologies>
|
||||
<TemplateVariableManager
|
||||
onClose={this.handleCloseTemplateManager}
|
||||
onEditTemplateVariables={this.handleEditTemplateVariables}
|
||||
source={source}
|
||||
templates={dashboard.templates}
|
||||
onClose={this.handleCloseTemplateManager}
|
||||
onRunQueryFailure={this.handleRunQueryFailure}
|
||||
onEditTemplateVariables={this.handleEditTemplateVariables}
|
||||
/>
|
||||
</OverlayTechnologies>
|
||||
: null}
|
||||
{selectedCell
|
||||
? <CellEditorOverlay
|
||||
source={source}
|
||||
dashboardID={dashboardID}
|
||||
templates={templatesIncludingDashTime}
|
||||
cell={selectedCell}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
dashboardID={dashboardID}
|
||||
queryStatus={cellQueryStatus}
|
||||
onSave={this.handleSaveEditedCell}
|
||||
onCancel={this.handleDismissOverlay}
|
||||
templates={templatesIncludingDashTime}
|
||||
editQueryStatus={dashboardActions.editCellQueryStatus}
|
||||
/>
|
||||
: null}
|
||||
{isEditMode
|
||||
? <DashboardHeaderEdit
|
||||
dashboard={dashboard}
|
||||
onCancel={this.handleCancelEditDashboard}
|
||||
onSave={this.handleRenameDashboard}
|
||||
onCancel={this.handleCancelEditDashboard}
|
||||
/>
|
||||
: <DashboardHeader
|
||||
buttonText={dashboard ? dashboard.name : ''}
|
||||
handleChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
autoRefresh={autoRefresh}
|
||||
timeRange={timeRange}
|
||||
handleChooseTimeRange={this.handleChooseTimeRange}
|
||||
isHidden={inPresentationMode}
|
||||
handleClickPresentationButton={handleClickPresentationButton}
|
||||
dashboard={dashboard}
|
||||
sourceID={sourceID}
|
||||
source={source}
|
||||
sourceID={sourceID}
|
||||
dashboard={dashboard}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
isHidden={inPresentationMode}
|
||||
onAddCell={this.handleAddCell}
|
||||
onEditDashboard={this.handleEditDashboard}
|
||||
onToggleTempVarControls={this.handleToggleTempVarControls}
|
||||
buttonText={dashboard ? dashboard.name : ''}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
handleChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
handleChooseTimeRange={this.handleChooseTimeRange}
|
||||
onToggleTempVarControls={this.handleToggleTempVarControls}
|
||||
handleClickPresentationButton={handleClickPresentationButton}
|
||||
>
|
||||
{dashboards
|
||||
? dashboards.map((d, i) =>
|
||||
|
@ -375,19 +334,19 @@ class DashboardPage extends Component {
|
|||
dashboard={dashboard}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
synchronizer={this.synchronizer}
|
||||
onAddCell={this.handleAddCell}
|
||||
onEditCell={this.handleEditDashboardCell}
|
||||
synchronizer={this.synchronizer}
|
||||
inPresentationMode={inPresentationMode}
|
||||
onEditCell={this.handleEditDashboardCell}
|
||||
onPositionChange={this.handleUpdatePosition}
|
||||
onSelectTemplate={this.handleSelectTemplate}
|
||||
onCancelEditCell={this.handleCancelEditCell}
|
||||
onDeleteCell={this.handleDeleteDashboardCell}
|
||||
onUpdateCell={this.handleUpdateDashboardCell}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
onOpenTemplateManager={this.handleOpenTemplateManager}
|
||||
templatesIncludingDashTime={templatesIncludingDashTime}
|
||||
onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies}
|
||||
onSelectTemplate={this.handleSelectTemplate}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
onCancelEditCell={::this.handleCancelEditCell}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import moment from 'moment'
|
||||
|
||||
const CustomCell = ({data, columnName}) => {
|
||||
if (columnName === 'time') {
|
||||
const date = moment(new Date(data)).format('MM/DD/YY hh:mm:ssA')
|
||||
|
||||
return (
|
||||
<span>
|
||||
{date}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{data}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const {number, oneOfType, string} = PropTypes
|
||||
|
||||
CustomCell.propTypes = {
|
||||
data: oneOfType([number, string]),
|
||||
columnName: string.isRequired,
|
||||
}
|
||||
|
||||
export default CustomCell
|
|
@ -1,84 +0,0 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
import {showDatabases, showRetentionPolicies} from 'shared/apis/metaQuery'
|
||||
import showDatabasesParser from 'shared/parsing/showDatabases'
|
||||
import showRetentionPoliciesParser from 'shared/parsing/showRetentionPolicies'
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
const DatabaseDropdown = React.createClass({
|
||||
propTypes: {
|
||||
query: shape({}).isRequired,
|
||||
onChooseNamespace: func.isRequired,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
namespaces: [],
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
const {source} = this.context
|
||||
const proxy = source.links.proxy
|
||||
showDatabases(proxy).then(resp => {
|
||||
const {errors, databases} = showDatabasesParser(resp.data)
|
||||
if (errors.length) {
|
||||
// do something
|
||||
}
|
||||
|
||||
const namespaces = []
|
||||
showRetentionPolicies(proxy, databases).then(res => {
|
||||
res.data.results.forEach((result, index) => {
|
||||
const {errors: errs, retentionPolicies} = showRetentionPoliciesParser(
|
||||
result
|
||||
)
|
||||
if (errs.length) {
|
||||
// do something
|
||||
}
|
||||
|
||||
retentionPolicies.forEach(rp => {
|
||||
namespaces.push({
|
||||
database: databases[index],
|
||||
retentionPolicy: rp.name,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.setState({namespaces})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
render() {
|
||||
const {query, onChooseNamespace} = this.props
|
||||
const {namespaces} = this.state
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="dropdown-160 query-builder--db-dropdown"
|
||||
items={namespaces.map(n => ({
|
||||
...n,
|
||||
text: `${n.database}.${n.retentionPolicy}`,
|
||||
}))}
|
||||
onChoose={onChooseNamespace}
|
||||
selected={
|
||||
query.database && query.retentionPolicy
|
||||
? `${query.database}.${query.retentionPolicy}`
|
||||
: 'Choose a DB & RP'
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default DatabaseDropdown
|
|
@ -1,75 +1,31 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
import Dimensions from 'react-dimensions'
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||
|
||||
import {Table, Column, Cell} from 'fixed-data-table'
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import CustomCell from 'src/data_explorer/components/CustomCell'
|
||||
import TabItem from 'src/data_explorer/components/TableTabItem'
|
||||
|
||||
const {arrayOf, bool, func, number, oneOfType, shape, string} = PropTypes
|
||||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||
|
||||
const emptySeries = {columns: [], values: []}
|
||||
|
||||
const CustomCell = React.createClass({
|
||||
propTypes: {
|
||||
data: oneOfType([number, string]),
|
||||
columnName: string.isRequired,
|
||||
},
|
||||
class ChronoTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
render() {
|
||||
const {columnName, data} = this.props
|
||||
|
||||
if (columnName === 'time') {
|
||||
const date = moment(new Date(data)).format('MM/DD/YY hh:mm:ssA')
|
||||
|
||||
return (
|
||||
<span>
|
||||
{date}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{data}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const ChronoTable = React.createClass({
|
||||
propTypes: {
|
||||
query: shape({
|
||||
host: arrayOf(string.isRequired).isRequired,
|
||||
text: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}).isRequired,
|
||||
containerWidth: number.isRequired,
|
||||
height: number,
|
||||
editQueryStatus: func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
this.state = {
|
||||
series: [emptySeries],
|
||||
columnWidths: {},
|
||||
activeSeriesIndex: 0,
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
height: 500,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchCellData(this.props.query)
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.query.text === nextProps.query.text) {
|
||||
|
@ -77,9 +33,9 @@ const ChronoTable = React.createClass({
|
|||
}
|
||||
|
||||
this.fetchCellData(nextProps.query)
|
||||
},
|
||||
}
|
||||
|
||||
async fetchCellData(query) {
|
||||
fetchCellData = async query => {
|
||||
if (!query || !query.text) {
|
||||
return
|
||||
}
|
||||
|
@ -105,9 +61,9 @@ const ChronoTable = React.createClass({
|
|||
})
|
||||
throw error
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleColumnResize(newColumnWidth, columnKey) {
|
||||
handleColumnResize = (newColumnWidth, columnKey) => {
|
||||
const columnWidths = {
|
||||
...this.state.columnWidths,
|
||||
[columnKey]: newColumnWidth,
|
||||
|
@ -116,15 +72,21 @@ const ChronoTable = React.createClass({
|
|||
this.setState({
|
||||
columnWidths,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
handleClickTab(activeSeriesIndex) {
|
||||
handleClickTab = activeSeriesIndex => () => {
|
||||
this.setState({activeSeriesIndex})
|
||||
},
|
||||
}
|
||||
|
||||
handleClickDropdown(item) {
|
||||
handleClickDropdown = item => {
|
||||
this.setState({activeSeriesIndex: item.index})
|
||||
},
|
||||
}
|
||||
|
||||
handleCustomCell = (columnName, values, colIndex) => ({rowIndex}) => {
|
||||
return (
|
||||
<CustomCell columnName={columnName} data={values[rowIndex][colIndex]} />
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {containerWidth, height, query} = this.props
|
||||
|
@ -200,11 +162,7 @@ const ChronoTable = React.createClass({
|
|||
{columnName}
|
||||
</Cell>
|
||||
}
|
||||
cell={({rowIndex}) =>
|
||||
<CustomCell
|
||||
columnName={columnName}
|
||||
data={values[rowIndex][colIndex]}
|
||||
/>}
|
||||
cell={this.handleCustomCell(columnName, values, colIndex)}
|
||||
width={columnWidths[columnName] || width}
|
||||
minWidth={minWidth}
|
||||
/>
|
||||
|
@ -214,22 +172,24 @@ const ChronoTable = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const TabItem = ({name, index, onClickTab, isActive}) =>
|
||||
<div
|
||||
className={classnames('table--tab', {active: isActive})}
|
||||
onClick={() => onClickTab(index)}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
ChronoTable.defaultProps = {
|
||||
height: 500,
|
||||
}
|
||||
|
||||
TabItem.propTypes = {
|
||||
name: string,
|
||||
onClickTab: func.isRequired,
|
||||
index: number.isRequired,
|
||||
isActive: bool,
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
|
||||
ChronoTable.propTypes = {
|
||||
query: shape({
|
||||
host: arrayOf(string.isRequired).isRequired,
|
||||
text: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}).isRequired,
|
||||
containerWidth: number.isRequired,
|
||||
height: number,
|
||||
editQueryStatus: func.isRequired,
|
||||
}
|
||||
|
||||
export default Dimensions({elementResize: true})(ChronoTable)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
const TableTabItem = ({name, index, onClickTab, isActive}) =>
|
||||
<div
|
||||
className={classnames('table--tab', {active: isActive})}
|
||||
onClick={onClickTab(index)}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
|
||||
const {bool, func, number, string} = PropTypes
|
||||
|
||||
TableTabItem.propTypes = {
|
||||
name: string,
|
||||
onClickTab: func.isRequired,
|
||||
index: number.isRequired,
|
||||
isActive: bool,
|
||||
}
|
||||
|
||||
export default TableTabItem
|
|
@ -9,7 +9,7 @@ const VisHeader = ({views, view, onToggleView, name}) =>
|
|||
{views.map(v =>
|
||||
<li
|
||||
key={v}
|
||||
onClick={() => onToggleView(v)}
|
||||
onClick={onToggleView(v)}
|
||||
className={classnames({active: view === v})}
|
||||
data-test={`data-${v}`}
|
||||
>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import buildInfluxQLQuery from 'utils/influxql'
|
||||
import classnames from 'classnames'
|
||||
import VisHeader from 'src/data_explorer/components/VisHeader'
|
||||
|
@ -6,57 +6,19 @@ import VisView from 'src/data_explorer/components/VisView'
|
|||
import {GRAPH, TABLE} from 'shared/constants'
|
||||
import _ from 'lodash'
|
||||
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
const META_QUERY_REGEX = /^show/i
|
||||
|
||||
const Visualization = React.createClass({
|
||||
propTypes: {
|
||||
cellName: string,
|
||||
cellType: string,
|
||||
autoRefresh: number.isRequired,
|
||||
templates: arrayOf(shape()),
|
||||
isInDataExplorer: bool,
|
||||
timeRange: shape({
|
||||
upper: string,
|
||||
lower: string,
|
||||
}).isRequired,
|
||||
queryConfigs: arrayOf(shape({})).isRequired,
|
||||
activeQueryIndex: number,
|
||||
height: string,
|
||||
heightPixels: number,
|
||||
editQueryStatus: func.isRequired,
|
||||
views: arrayOf(string).isRequired,
|
||||
axes: shape({
|
||||
y: shape({
|
||||
bounds: arrayOf(string),
|
||||
}),
|
||||
}),
|
||||
resizerBottomHeight: number,
|
||||
},
|
||||
class Visualization extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
contextTypes: {
|
||||
source: PropTypes.shape({
|
||||
links: PropTypes.shape({
|
||||
proxy: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
const {activeQueryIndex, queryConfigs} = this.props
|
||||
const activeQueryText = this.getQueryText(queryConfigs, activeQueryIndex)
|
||||
|
||||
return activeQueryText.match(META_QUERY_REGEX)
|
||||
this.state = activeQueryText.match(META_QUERY_REGEX)
|
||||
? {view: TABLE}
|
||||
: {view: GRAPH}
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
cellName: '',
|
||||
cellType: '',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {activeQueryIndex, queryConfigs} = nextProps
|
||||
|
@ -75,11 +37,11 @@ const Visualization = React.createClass({
|
|||
}
|
||||
|
||||
this.setState({view: GRAPH})
|
||||
},
|
||||
}
|
||||
|
||||
handleToggleView(view) {
|
||||
handleToggleView = view => () => {
|
||||
this.setState({view})
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -141,12 +103,51 @@ const Visualization = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
getQueryText(queryConfigs, index) {
|
||||
// rawText can be null
|
||||
return _.get(queryConfigs, [`${index}`, 'rawText'], '') || ''
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Visualization.defaultProps = {
|
||||
cellName: '',
|
||||
cellType: '',
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
Visualization.contextTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
Visualization.propTypes = {
|
||||
cellName: string,
|
||||
cellType: string,
|
||||
autoRefresh: number.isRequired,
|
||||
templates: arrayOf(shape()),
|
||||
isInDataExplorer: bool,
|
||||
timeRange: shape({
|
||||
upper: string,
|
||||
lower: string,
|
||||
}).isRequired,
|
||||
queryConfigs: arrayOf(shape({})).isRequired,
|
||||
activeQueryIndex: number,
|
||||
height: string,
|
||||
heightPixels: number,
|
||||
editQueryStatus: func.isRequired,
|
||||
views: arrayOf(string).isRequired,
|
||||
axes: shape({
|
||||
y: shape({
|
||||
bounds: arrayOf(string),
|
||||
}),
|
||||
}),
|
||||
resizerBottomHeight: number,
|
||||
}
|
||||
|
||||
export default Visualization
|
||||
|
|
|
@ -32,7 +32,7 @@ const WriteDataBody = ({
|
|||
<p>OR</p>
|
||||
<input
|
||||
type="file"
|
||||
onChange={e => handleFile(e, false)}
|
||||
onChange={handleFile(false)}
|
||||
className="write-data-form--upload"
|
||||
ref={fileInput}
|
||||
accept="text/*, application/gzip"
|
||||
|
|
|
@ -21,26 +21,17 @@ class WriteDataForm extends Component {
|
|||
dragClass: 'drag-none',
|
||||
isUploading: false,
|
||||
}
|
||||
|
||||
this.handleSelectDatabase = ::this.handleSelectDatabase
|
||||
this.handleSubmit = ::this.handleSubmit
|
||||
this.handleClickOutside = ::this.handleClickOutside
|
||||
this.handleKeyUp = ::this.handleKeyUp
|
||||
this.handleEdit = ::this.handleEdit
|
||||
this.handleFile = ::this.handleFile
|
||||
this.toggleWriteView = ::this.toggleWriteView
|
||||
this.handleFileOpen = ::this.handleFileOpen
|
||||
}
|
||||
|
||||
toggleWriteView(isManual) {
|
||||
toggleWriteView = isManual => () => {
|
||||
this.setState({isManual})
|
||||
}
|
||||
|
||||
handleSelectDatabase(item) {
|
||||
handleSelectDatabase = item => {
|
||||
this.setState({selectedDatabase: item.text})
|
||||
}
|
||||
|
||||
handleClickOutside(e) {
|
||||
handleClickOutside = e => {
|
||||
// guard against clicking to close error notification
|
||||
if (e.target.className === OVERLAY_TECHNOLOGY) {
|
||||
const {onClose} = this.props
|
||||
|
@ -48,7 +39,7 @@ class WriteDataForm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleKeyUp(e) {
|
||||
handleKeyUp = e => {
|
||||
e.stopPropagation()
|
||||
if (e.key === 'Escape') {
|
||||
const {onClose} = this.props
|
||||
|
@ -56,7 +47,7 @@ class WriteDataForm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async handleSubmit() {
|
||||
handleSubmit = async () => {
|
||||
const {onClose, source, writeLineProtocol} = this.props
|
||||
const {inputContent, uploadContent, selectedDatabase, isManual} = this.state
|
||||
const content = isManual ? inputContent : uploadContent
|
||||
|
@ -73,11 +64,11 @@ class WriteDataForm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleEdit(e) {
|
||||
handleEdit = e => {
|
||||
this.setState({inputContent: e.target.value.trim()})
|
||||
}
|
||||
|
||||
handleFile(e, drop) {
|
||||
handleFile = drop => e => {
|
||||
let file
|
||||
if (drop) {
|
||||
file = e.dataTransfer.files[0]
|
||||
|
@ -101,33 +92,18 @@ class WriteDataForm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDragOver(e) {
|
||||
handleDragOver = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
handleDragClass(entering) {
|
||||
return e => {
|
||||
e.preventDefault()
|
||||
if (entering) {
|
||||
this.setState({
|
||||
dragClass: 'drag-over',
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
dragClass: 'drag-none',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleDragEnter(e) {
|
||||
handleDragEnter = e => {
|
||||
dragCounter += 1
|
||||
e.preventDefault()
|
||||
this.setState({dragClass: 'drag-over'})
|
||||
}
|
||||
|
||||
handleDragLeave(e) {
|
||||
handleDragLeave = e => {
|
||||
dragCounter -= 1
|
||||
e.preventDefault()
|
||||
if (dragCounter === 0) {
|
||||
|
@ -135,21 +111,23 @@ class WriteDataForm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleFileOpen() {
|
||||
handleFileOpen = () => {
|
||||
this.fileInput.click()
|
||||
}
|
||||
|
||||
handleFileInputRef = el => (this.fileInput = el)
|
||||
|
||||
render() {
|
||||
const {onClose, errorThrown} = this.props
|
||||
const {dragClass} = this.state
|
||||
|
||||
return (
|
||||
<div
|
||||
onDrop={e => this.handleFile(e, true)}
|
||||
onDrop={this.handleFile(true)}
|
||||
onDragOver={this.handleDragOver}
|
||||
onDragEnter={e => this.handleDragEnter(e)}
|
||||
onDragExit={e => this.handleDragLeave(e)}
|
||||
onDragLeave={e => this.handleDragLeave(e)}
|
||||
onDragEnter={this.handleDragEnter}
|
||||
onDragExit={this.handleDragLeave}
|
||||
onDragLeave={this.handleDragLeave}
|
||||
className={classnames(OVERLAY_TECHNOLOGY, dragClass)}
|
||||
>
|
||||
<div className="write-data-form">
|
||||
|
@ -162,7 +140,7 @@ class WriteDataForm extends Component {
|
|||
/>
|
||||
<WriteDataBody
|
||||
{...this.state}
|
||||
fileInput={el => (this.fileInput = el)}
|
||||
fileInput={this.handleFileInputRef}
|
||||
handleEdit={this.handleEdit}
|
||||
handleFile={this.handleFile}
|
||||
handleKeyUp={this.handleKeyUp}
|
||||
|
|
|
@ -19,13 +19,13 @@ const WriteDataHeader = ({
|
|||
/>
|
||||
<ul className="nav nav-tablist nav-tablist-sm">
|
||||
<li
|
||||
onClick={() => toggleWriteView(false)}
|
||||
onClick={toggleWriteView(false)}
|
||||
className={isManual ? '' : 'active'}
|
||||
>
|
||||
File Upload
|
||||
</li>
|
||||
<li
|
||||
onClick={() => toggleWriteView(true)}
|
||||
onClick={toggleWriteView(true)}
|
||||
className={isManual ? 'active' : ''}
|
||||
data-test="manual-entry-button"
|
||||
>
|
||||
|
|
|
@ -28,27 +28,23 @@ class DataExplorer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {source: this.props.source}
|
||||
}
|
||||
|
||||
handleSetActiveQueryIndex(index) {
|
||||
handleSetActiveQueryIndex = index => {
|
||||
this.setState({activeQueryIndex: index})
|
||||
}
|
||||
|
||||
handleDeleteQuery(index) {
|
||||
handleDeleteQuery = index => {
|
||||
const {queryConfigs, queryConfigActions} = this.props
|
||||
const query = queryConfigs[index]
|
||||
queryConfigActions.deleteQuery(query.id)
|
||||
}
|
||||
|
||||
handleAddQuery() {
|
||||
handleAddQuery = () => {
|
||||
const newIndex = this.props.queryConfigs.length
|
||||
this.props.queryConfigActions.addQuery()
|
||||
this.handleSetActiveQueryIndex(newIndex)
|
||||
}
|
||||
|
||||
getActiveQuery() {
|
||||
getActiveQuery = () => {
|
||||
const {activeQueryIndex} = this.state
|
||||
const {queryConfigs} = this.props
|
||||
const activeQuery = queryConfigs[activeQueryIndex]
|
||||
|
@ -57,6 +53,14 @@ class DataExplorer extends Component {
|
|||
return activeQuery || defaultQuery
|
||||
}
|
||||
|
||||
handleCloseWriteData = () => {
|
||||
this.setState({showWriteForm: false})
|
||||
}
|
||||
|
||||
handleOpenWriteData = () => {
|
||||
this.setState({showWriteForm: true})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
autoRefresh,
|
||||
|
@ -82,19 +86,19 @@ class DataExplorer extends Component {
|
|||
{showWriteForm
|
||||
? <OverlayTechnologies>
|
||||
<WriteDataForm
|
||||
selectedDatabase={selectedDatabase}
|
||||
errorThrown={errorThrownAction}
|
||||
onClose={() => this.setState({showWriteForm: false})}
|
||||
source={source}
|
||||
errorThrown={errorThrownAction}
|
||||
selectedDatabase={selectedDatabase}
|
||||
onClose={this.handleCloseWriteData}
|
||||
writeLineProtocol={writeLineProtocol}
|
||||
/>
|
||||
</OverlayTechnologies>
|
||||
: null}
|
||||
<Header
|
||||
actions={{handleChooseAutoRefresh, setTimeRange}}
|
||||
autoRefresh={autoRefresh}
|
||||
timeRange={timeRange}
|
||||
showWriteForm={() => this.setState({showWriteForm: true})}
|
||||
autoRefresh={autoRefresh}
|
||||
actions={{handleChooseAutoRefresh, setTimeRange}}
|
||||
showWriteForm={this.handleOpenWriteData}
|
||||
/>
|
||||
<ResizeContainer
|
||||
containerClass="page-contents"
|
||||
|
@ -109,11 +113,11 @@ class DataExplorer extends Component {
|
|||
actions={queryConfigActions}
|
||||
autoRefresh={autoRefresh}
|
||||
timeRange={timeRange}
|
||||
setActiveQueryIndex={::this.handleSetActiveQueryIndex}
|
||||
onDeleteQuery={::this.handleDeleteQuery}
|
||||
onAddQuery={::this.handleAddQuery}
|
||||
setActiveQueryIndex={this.handleSetActiveQueryIndex}
|
||||
onDeleteQuery={this.handleDeleteQuery}
|
||||
onAddQuery={this.handleAddQuery}
|
||||
activeQueryIndex={activeQueryIndex}
|
||||
activeQuery={::this.getActiveQuery()}
|
||||
activeQuery={this.getActiveQuery()}
|
||||
/>
|
||||
<Visualization
|
||||
isInDataExplorer={true}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import React, {PropTypes, Component} from 'react'
|
||||
import shallowCompare from 'react-addons-shallow-compare'
|
||||
import {Link} from 'react-router'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import {HOSTS_TABLE} from 'src/hosts/constants/tableSizing'
|
||||
|
||||
class HostRow extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return shallowCompare(this, nextProps)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {host, source} = this.props
|
||||
const {name, cpu, load, apps = []} = host
|
||||
const {colName, colStatus, colCPU, colLoad} = HOSTS_TABLE
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td style={{width: colName}}>
|
||||
<Link to={`/sources/${source.id}/hosts/${name}`}>
|
||||
{name}
|
||||
</Link>
|
||||
</td>
|
||||
<td style={{width: colStatus}}>
|
||||
<div
|
||||
className={classnames(
|
||||
'table-dot',
|
||||
Math.max(host.deltaUptime || 0, host.winDeltaUptime || 0) > 0
|
||||
? 'dot-success'
|
||||
: 'dot-critical'
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td style={{width: colCPU}} className="monotype">
|
||||
{isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
|
||||
</td>
|
||||
<td style={{width: colLoad}} className="monotype">
|
||||
{isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}
|
||||
</td>
|
||||
<td>
|
||||
{apps.map((app, index) => {
|
||||
return (
|
||||
<span key={app}>
|
||||
<Link
|
||||
style={{marginLeft: '2px'}}
|
||||
to={{
|
||||
pathname: `/sources/${source.id}/hosts/${name}`,
|
||||
query: {app},
|
||||
}}
|
||||
>
|
||||
{app}
|
||||
</Link>
|
||||
{index === apps.length - 1 ? null : ', '}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HostRow.propTypes = {
|
||||
source: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
host: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
cpu: PropTypes.number,
|
||||
load: PropTypes.number,
|
||||
deltaUptime: PropTypes.number.required,
|
||||
apps: PropTypes.arrayOf(PropTypes.string.isRequired),
|
||||
}),
|
||||
}
|
||||
|
||||
export default HostRow
|
|
@ -1,38 +1,21 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import shallowCompare from 'react-addons-shallow-compare'
|
||||
import classnames from 'classnames'
|
||||
import SearchBar from 'src/hosts/components/SearchBar'
|
||||
import HostRow from 'src/hosts/components/HostRow'
|
||||
|
||||
import {HOSTS_TABLE} from 'src/hosts/constants/tableSizing'
|
||||
|
||||
const {arrayOf, bool, number, shape, string} = PropTypes
|
||||
class HostsTable extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const HostsTable = React.createClass({
|
||||
propTypes: {
|
||||
hosts: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
cpu: number,
|
||||
load: number,
|
||||
apps: arrayOf(string.isRequired),
|
||||
})
|
||||
),
|
||||
hostsLoading: bool,
|
||||
hostsError: string,
|
||||
source: shape({
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
this.state = {
|
||||
searchTerm: '',
|
||||
sortDirection: null,
|
||||
sortKey: null,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
filter(allHosts, searchTerm) {
|
||||
const filterText = searchTerm.toLowerCase()
|
||||
|
@ -53,7 +36,7 @@ const HostsTable = React.createClass({
|
|||
tagResult
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
sort(hosts, key, direction) {
|
||||
switch (direction) {
|
||||
|
@ -64,13 +47,13 @@ const HostsTable = React.createClass({
|
|||
default:
|
||||
return hosts
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
updateSearchTerm(term) {
|
||||
updateSearchTerm = term => {
|
||||
this.setState({searchTerm: term})
|
||||
},
|
||||
}
|
||||
|
||||
updateSort(key) {
|
||||
updateSort = key => () => {
|
||||
// if we're using the key, reverse order; otherwise, set it with ascending
|
||||
if (this.state.sortKey === key) {
|
||||
const reverseDirection =
|
||||
|
@ -79,9 +62,9 @@ const HostsTable = React.createClass({
|
|||
} else {
|
||||
this.setState({sortKey: key, sortDirection: 'asc'})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
sortableClasses(key) {
|
||||
sortableClasses = key => {
|
||||
if (this.state.sortKey === key) {
|
||||
if (this.state.sortDirection === 'asc') {
|
||||
return 'sortable-header sorting-ascending'
|
||||
|
@ -89,7 +72,7 @@ const HostsTable = React.createClass({
|
|||
return 'sortable-header sorting-descending'
|
||||
}
|
||||
return 'sortable-header'
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {searchTerm, sortKey, sortDirection} = this.state
|
||||
|
@ -128,28 +111,28 @@ const HostsTable = React.createClass({
|
|||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
onClick={() => this.updateSort('name')}
|
||||
onClick={this.updateSort('name')}
|
||||
className={this.sortableClasses('name')}
|
||||
style={{width: colName}}
|
||||
>
|
||||
Host
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('deltaUptime')}
|
||||
onClick={this.updateSort('deltaUptime')}
|
||||
className={this.sortableClasses('deltaUptime')}
|
||||
style={{width: colStatus}}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('cpu')}
|
||||
onClick={this.updateSort('cpu')}
|
||||
className={this.sortableClasses('cpu')}
|
||||
style={{width: colCPU}}
|
||||
>
|
||||
CPU
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('load')}
|
||||
onClick={this.updateSort('load')}
|
||||
className={this.sortableClasses('load')}
|
||||
style={{width: colLoad}}
|
||||
>
|
||||
|
@ -160,9 +143,9 @@ const HostsTable = React.createClass({
|
|||
</thead>
|
||||
|
||||
<tbody>
|
||||
{sortedHosts.map(h => {
|
||||
return <HostRow key={h.name} host={h} source={source} />
|
||||
})}
|
||||
{sortedHosts.map(h =>
|
||||
<HostRow key={h.name} host={h} source={source} />
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
: <div className="generic-empty-state">
|
||||
|
@ -171,119 +154,26 @@ const HostsTable = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const HostRow = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
host: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
cpu: PropTypes.number,
|
||||
load: PropTypes.number,
|
||||
deltaUptime: PropTypes.number.required,
|
||||
apps: PropTypes.arrayOf(PropTypes.string.isRequired),
|
||||
}),
|
||||
},
|
||||
const {arrayOf, bool, number, shape, string} = PropTypes
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return shallowCompare(this, nextProps)
|
||||
},
|
||||
|
||||
render() {
|
||||
const {host, source} = this.props
|
||||
const {name, cpu, load, apps = []} = host
|
||||
const {colName, colStatus, colCPU, colLoad} = HOSTS_TABLE
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td style={{width: colName}}>
|
||||
<Link to={`/sources/${source.id}/hosts/${name}`}>
|
||||
{name}
|
||||
</Link>
|
||||
</td>
|
||||
<td style={{width: colStatus}}>
|
||||
<div
|
||||
className={classnames(
|
||||
'table-dot',
|
||||
Math.max(host.deltaUptime || 0, host.winDeltaUptime || 0) > 0
|
||||
? 'dot-success'
|
||||
: 'dot-critical'
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td style={{width: colCPU}} className="monotype">
|
||||
{isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
|
||||
</td>
|
||||
<td style={{width: colLoad}} className="monotype">
|
||||
{isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}
|
||||
</td>
|
||||
<td>
|
||||
{apps.map((app, index) => {
|
||||
return (
|
||||
<span key={app}>
|
||||
<Link
|
||||
style={{marginLeft: '2px'}}
|
||||
to={{
|
||||
pathname: `/sources/${source.id}/hosts/${name}`,
|
||||
query: {app},
|
||||
}}
|
||||
>
|
||||
{app}
|
||||
</Link>
|
||||
{index === apps.length - 1 ? null : ', '}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const SearchBar = React.createClass({
|
||||
propTypes: {
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
const waitPeriod = 300
|
||||
this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.props.onSearch(this.state.searchTerm)
|
||||
},
|
||||
|
||||
handleChange() {
|
||||
this.setState({searchTerm: this.refs.searchInput.value}, this.handleSearch)
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="users__search-widget input-group">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Filter by Host..."
|
||||
ref="searchInput"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
HostsTable.propTypes = {
|
||||
hosts: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
cpu: number,
|
||||
load: number,
|
||||
apps: arrayOf(string.isRequired),
|
||||
})
|
||||
),
|
||||
hostsLoading: bool,
|
||||
hostsError: string,
|
||||
source: shape({
|
||||
id: string.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default HostsTable
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import React, {PropTypes, Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
class SearchBar extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
searchTerm: '',
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const waitPeriod = 300
|
||||
this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
|
||||
}
|
||||
|
||||
handleSearch = () => {
|
||||
this.props.onSearch(this.state.searchTerm)
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
this.setState({searchTerm: this.refs.searchInput.value}, this.handleSearch)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="users__search-widget input-group">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Filter by Host..."
|
||||
ref="searchInput"
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func} = PropTypes
|
||||
|
||||
SearchBar.propTypes = {
|
||||
onSearch: func.isRequired,
|
||||
}
|
||||
|
||||
export default SearchBar
|
|
@ -2,11 +2,7 @@ import React, {Component, PropTypes} from 'react'
|
|||
import _ from 'lodash'
|
||||
|
||||
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
|
||||
import {
|
||||
getKapacitorConfig,
|
||||
updateKapacitorConfigSection,
|
||||
testAlertOutput,
|
||||
} from 'shared/apis'
|
||||
import {getKapacitorConfig, updateKapacitorConfigSection} from 'shared/apis'
|
||||
|
||||
import {
|
||||
AlertaConfig,
|
||||
|
@ -25,15 +21,11 @@ import {
|
|||
class AlertTabs extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
selectedEndpoint: 'smtp',
|
||||
configSections: null,
|
||||
}
|
||||
this.refreshKapacitorConfig = ::this.refreshKapacitorConfig
|
||||
this.getSection = ::this.getSection
|
||||
this.handleSaveConfig = ::this.handleSaveConfig
|
||||
this.handleTest = ::this.handleTest
|
||||
this.sanitizeProperties = ::this.sanitizeProperties
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -46,7 +38,7 @@ class AlertTabs extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
refreshKapacitorConfig(kapacitor) {
|
||||
refreshKapacitorConfig = kapacitor => {
|
||||
getKapacitorConfig(kapacitor)
|
||||
.then(({data: {sections}}) => {
|
||||
this.setState({configSections: sections})
|
||||
|
@ -60,11 +52,15 @@ class AlertTabs extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
getSection(sections, section) {
|
||||
getSection = (sections, section) => {
|
||||
return _.get(sections, [section, 'elements', '0'], null)
|
||||
}
|
||||
|
||||
handleSaveConfig(section, properties) {
|
||||
handleGetSection = (sections, section) => () => {
|
||||
return this.getSection(sections, section)
|
||||
}
|
||||
|
||||
handleSaveConfig = section => properties => {
|
||||
if (section !== '') {
|
||||
const propsToSend = this.sanitizeProperties(section, properties)
|
||||
updateKapacitorConfigSection(this.props.kapacitor, section, propsToSend)
|
||||
|
@ -84,25 +80,8 @@ class AlertTabs extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleTest(section, properties) {
|
||||
const propsToSend = this.sanitizeProperties(section, properties)
|
||||
testAlertOutput(this.props.kapacitor, section, propsToSend)
|
||||
.then(() => {
|
||||
this.props.addFlashMessage({
|
||||
type: 'success',
|
||||
text: 'Slack test message sent',
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was an error testing the slack alert',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
sanitizeProperties(section, properties) {
|
||||
const cleanProps = Object.assign({}, properties, {enabled: true})
|
||||
sanitizeProperties = (section, properties) => {
|
||||
const cleanProps = {...properties, enabled: true}
|
||||
const {redacted} = this.getSection(this.state.configSections, section)
|
||||
if (redacted && redacted.length) {
|
||||
redacted.forEach(badProp => {
|
||||
|
@ -111,6 +90,7 @@ class AlertTabs extends Component {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
return cleanProps
|
||||
}
|
||||
|
||||
|
@ -121,16 +101,12 @@ class AlertTabs extends Component {
|
|||
return null
|
||||
}
|
||||
|
||||
const test = properties => {
|
||||
this.handleTest('slack', properties)
|
||||
}
|
||||
|
||||
const supportedConfigs = {
|
||||
alerta: {
|
||||
type: 'Alerta',
|
||||
renderComponent: () =>
|
||||
<AlertaConfig
|
||||
onSave={p => this.handleSaveConfig('alerta', p)}
|
||||
onSave={this.handleSaveConfig('alerta')}
|
||||
config={this.getSection(configSections, 'alerta')}
|
||||
/>,
|
||||
},
|
||||
|
@ -138,7 +114,7 @@ class AlertTabs extends Component {
|
|||
type: 'HipChat',
|
||||
renderComponent: () =>
|
||||
<HipChatConfig
|
||||
onSave={p => this.handleSaveConfig('hipchat', p)}
|
||||
onSave={this.handleSaveConfig('hipchat')}
|
||||
config={this.getSection(configSections, 'hipchat')}
|
||||
/>,
|
||||
},
|
||||
|
@ -146,7 +122,7 @@ class AlertTabs extends Component {
|
|||
type: 'OpsGenie',
|
||||
renderComponent: () =>
|
||||
<OpsGenieConfig
|
||||
onSave={p => this.handleSaveConfig('opsgenie', p)}
|
||||
onSave={this.handleSaveConfig('opsgenie')}
|
||||
config={this.getSection(configSections, 'opsgenie')}
|
||||
/>,
|
||||
},
|
||||
|
@ -154,7 +130,7 @@ class AlertTabs extends Component {
|
|||
type: 'PagerDuty',
|
||||
renderComponent: () =>
|
||||
<PagerDutyConfig
|
||||
onSave={p => this.handleSaveConfig('pagerduty', p)}
|
||||
onSave={this.handleSaveConfig('pagerduty')}
|
||||
config={this.getSection(configSections, 'pagerduty')}
|
||||
/>,
|
||||
},
|
||||
|
@ -162,7 +138,7 @@ class AlertTabs extends Component {
|
|||
type: 'Pushover',
|
||||
renderComponent: () =>
|
||||
<PushoverConfig
|
||||
onSave={p => this.handleSaveConfig('pushover', p)}
|
||||
onSave={this.handleSaveConfig('pushover')}
|
||||
config={this.getSection(configSections, 'pushover')}
|
||||
/>,
|
||||
},
|
||||
|
@ -170,7 +146,7 @@ class AlertTabs extends Component {
|
|||
type: 'Sensu',
|
||||
renderComponent: () =>
|
||||
<SensuConfig
|
||||
onSave={p => this.handleSaveConfig('sensu', p)}
|
||||
onSave={this.handleSaveConfig('sensu')}
|
||||
config={this.getSection(configSections, 'sensu')}
|
||||
/>,
|
||||
},
|
||||
|
@ -178,8 +154,7 @@ class AlertTabs extends Component {
|
|||
type: 'Slack',
|
||||
renderComponent: () =>
|
||||
<SlackConfig
|
||||
onSave={p => this.handleSaveConfig('slack', p)}
|
||||
onTest={test}
|
||||
onSave={this.handleSaveConfig('slack')}
|
||||
config={this.getSection(configSections, 'slack')}
|
||||
/>,
|
||||
},
|
||||
|
@ -187,7 +162,7 @@ class AlertTabs extends Component {
|
|||
type: 'SMTP',
|
||||
renderComponent: () =>
|
||||
<SMTPConfig
|
||||
onSave={p => this.handleSaveConfig('smtp', p)}
|
||||
onSave={this.handleSaveConfig('smtp')}
|
||||
config={this.getSection(configSections, 'smtp')}
|
||||
/>,
|
||||
},
|
||||
|
@ -195,7 +170,7 @@ class AlertTabs extends Component {
|
|||
type: 'Talk',
|
||||
renderComponent: () =>
|
||||
<TalkConfig
|
||||
onSave={p => this.handleSaveConfig('talk', p)}
|
||||
onSave={this.handleSaveConfig('talk')}
|
||||
config={this.getSection(configSections, 'talk')}
|
||||
/>,
|
||||
},
|
||||
|
@ -203,7 +178,7 @@ class AlertTabs extends Component {
|
|||
type: 'Telegram',
|
||||
renderComponent: () =>
|
||||
<TelegramConfig
|
||||
onSave={p => this.handleSaveConfig('telegram', p)}
|
||||
onSave={this.handleSaveConfig('telegram')}
|
||||
config={this.getSection(configSections, 'telegram')}
|
||||
/>,
|
||||
},
|
||||
|
@ -211,7 +186,7 @@ class AlertTabs extends Component {
|
|||
type: 'VictorOps',
|
||||
renderComponent: () =>
|
||||
<VictorOpsConfig
|
||||
onSave={p => this.handleSaveConfig('victorops', p)}
|
||||
onSave={this.handleSaveConfig('victorops')}
|
||||
config={this.getSection(configSections, 'victorops')}
|
||||
/>,
|
||||
},
|
||||
|
|
|
@ -76,16 +76,16 @@ const RuleRow = ({rule, source, onRead, onDelete, onChangeRuleStatus}) =>
|
|||
className="form-control-static"
|
||||
type="checkbox"
|
||||
defaultChecked={rule.status === 'enabled'}
|
||||
onClick={() => onChangeRuleStatus(rule)}
|
||||
onClick={onChangeRuleStatus(rule)}
|
||||
/>
|
||||
<label htmlFor={`kapacitor-enabled ${rule.id}`} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
||||
<button className="btn btn-info btn-xs" onClick={() => onRead(rule)}>
|
||||
<button className="btn btn-info btn-xs" onClick={onRead(rule)}>
|
||||
View TICKscript
|
||||
</button>
|
||||
<button className="btn btn-danger btn-xs" onClick={() => onDelete(rule)}>
|
||||
<button className="btn btn-danger btn-xs" onClick={onDelete(rule)}>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
|
|
|
@ -1,142 +1,85 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
|
||||
import SourceIndicator from 'shared/components/SourceIndicator'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import RuleHeaderEdit from 'src/kapacitor/components/RuleHeaderEdit'
|
||||
import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave'
|
||||
|
||||
export const RuleHeader = React.createClass({
|
||||
propTypes: {
|
||||
source: PropTypes.shape({}).isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
rule: PropTypes.shape({}).isRequired,
|
||||
actions: PropTypes.shape({
|
||||
updateRuleName: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
validationError: PropTypes.string.isRequired,
|
||||
onChooseTimeRange: PropTypes.func.isRequired,
|
||||
timeRange: PropTypes.shape({}).isRequired,
|
||||
},
|
||||
class RuleHeader extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
this.state = {
|
||||
isEditingName: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleEditName(e, rule) {
|
||||
toggleEditName = () => {
|
||||
this.setState({isEditingName: !this.state.isEditingName})
|
||||
}
|
||||
|
||||
handleEditName = rule => e => {
|
||||
if (e.key === 'Enter') {
|
||||
const {updateRuleName} = this.props.actions
|
||||
const name = this.ruleName.value
|
||||
updateRuleName(rule.id, name)
|
||||
updateRuleName(rule.id, e.target.value)
|
||||
this.toggleEditName()
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
this.toggleEditName()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleEditNameBlur(rule) {
|
||||
handleEditNameBlur = rule => e => {
|
||||
const {updateRuleName} = this.props.actions
|
||||
const name = this.ruleName.value
|
||||
updateRuleName(rule.id, name)
|
||||
updateRuleName(rule.id, e.target.value)
|
||||
this.toggleEditName()
|
||||
},
|
||||
|
||||
toggleEditName() {
|
||||
this.setState({isEditingName: !this.state.isEditingName})
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
rule,
|
||||
source,
|
||||
onSave,
|
||||
timeRange,
|
||||
validationError,
|
||||
onChooseTimeRange,
|
||||
} = this.props
|
||||
|
||||
const {isEditingName} = this.state
|
||||
|
||||
return (
|
||||
<div className="page-header">
|
||||
<div className="page-header__container">
|
||||
{this.renderEditName()}
|
||||
{this.renderSave()}
|
||||
<RuleHeaderEdit
|
||||
rule={rule}
|
||||
isEditing={isEditingName}
|
||||
onToggleEdit={this.toggleEditName}
|
||||
onEditName={this.handleEditName}
|
||||
onEditNameBlur={this.handleEditNameBlur}
|
||||
/>
|
||||
<RuleHeaderSave
|
||||
source={source}
|
||||
onSave={onSave}
|
||||
timeRange={timeRange}
|
||||
validationError={validationError}
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
renderSave() {
|
||||
const {
|
||||
validationError,
|
||||
onSave,
|
||||
timeRange,
|
||||
onChooseTimeRange,
|
||||
source,
|
||||
} = this.props
|
||||
const saveButton = validationError
|
||||
? <button
|
||||
className="btn btn-success btn-sm disabled"
|
||||
data-for="save-kapacitor-tooltip"
|
||||
data-tip={validationError}
|
||||
>
|
||||
Save Rule
|
||||
</button>
|
||||
: <button className="btn btn-success btn-sm" onClick={onSave}>
|
||||
Save Rule
|
||||
</button>
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
return (
|
||||
<div className="page-header__right">
|
||||
<SourceIndicator sourceName={source.name} />
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
selected={timeRange}
|
||||
preventCustomTimeRange={true}
|
||||
/>
|
||||
{saveButton}
|
||||
<ReactTooltip
|
||||
id="save-kapacitor-tooltip"
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{bottom: 4}}
|
||||
place="bottom"
|
||||
class="influx-tooltip kapacitor-tooltip place-bottom"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
renderEditName() {
|
||||
const {rule} = this.props
|
||||
const {isEditingName} = this.state
|
||||
|
||||
const name = isEditingName
|
||||
? <input
|
||||
className="page-header--editing kapacitor-theme"
|
||||
autoFocus={true}
|
||||
defaultValue={rule.name}
|
||||
ref={r => (this.ruleName = r)}
|
||||
onKeyDown={e => this.handleEditName(e, rule)}
|
||||
onBlur={() => this.handleEditNameBlur(rule)}
|
||||
placeholder="Name your rule"
|
||||
spellCheck={false}
|
||||
autoComplete={false}
|
||||
/>
|
||||
: <div className="page-header__left">
|
||||
<h1
|
||||
className="page-header__title page-header--editable kapacitor-theme"
|
||||
onClick={this.toggleEditName}
|
||||
data-for="rename-kapacitor-tooltip"
|
||||
data-tip="Click to Rename"
|
||||
>
|
||||
{rule.name}
|
||||
<span className="icon pencil" />
|
||||
<ReactTooltip
|
||||
id="rename-kapacitor-tooltip"
|
||||
delayShow={200}
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: 2}}
|
||||
place="bottom"
|
||||
class="influx-tooltip kapacitor-tooltip place-bottom"
|
||||
/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
return name
|
||||
},
|
||||
})
|
||||
RuleHeader.propTypes = {
|
||||
source: shape({}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
rule: shape({}).isRequired,
|
||||
actions: shape({
|
||||
updateRuleName: func.isRequired,
|
||||
}).isRequired,
|
||||
validationError: string.isRequired,
|
||||
onChooseTimeRange: func.isRequired,
|
||||
timeRange: shape({}).isRequired,
|
||||
}
|
||||
|
||||
export default RuleHeader
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
const RuleHeaderEdit = ({
|
||||
rule,
|
||||
isEditing,
|
||||
onToggleEdit,
|
||||
onEditName,
|
||||
onEditNameBlur,
|
||||
}) =>
|
||||
isEditing
|
||||
? <input
|
||||
className="page-header--editing kapacitor-theme"
|
||||
autoFocus={true}
|
||||
defaultValue={rule.name}
|
||||
onKeyDown={onEditName(rule)}
|
||||
onBlur={onEditNameBlur(rule)}
|
||||
placeholder="Name your rule"
|
||||
spellCheck={false}
|
||||
autoComplete={false}
|
||||
/>
|
||||
: <div className="page-header__left">
|
||||
<h1
|
||||
className="page-header__title page-header--editable kapacitor-theme"
|
||||
onClick={onToggleEdit}
|
||||
data-for="rename-kapacitor-tooltip"
|
||||
data-tip="Click to Rename"
|
||||
>
|
||||
{rule.name}
|
||||
<span className="icon pencil" />
|
||||
<ReactTooltip
|
||||
id="rename-kapacitor-tooltip"
|
||||
delayShow={200}
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: 2}}
|
||||
place="bottom"
|
||||
class="influx-tooltip kapacitor-tooltip place-bottom"
|
||||
/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
const {bool, func, shape} = PropTypes
|
||||
|
||||
RuleHeaderEdit.propTypes = {
|
||||
rule: shape(),
|
||||
isEditing: bool.isRequired,
|
||||
onToggleEdit: func.isRequired,
|
||||
onEditName: func.isRequired,
|
||||
onEditNameBlur: func.isRequired,
|
||||
}
|
||||
|
||||
export default RuleHeaderEdit
|
|
@ -0,0 +1,51 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
|
||||
import SourceIndicator from 'shared/components/SourceIndicator'
|
||||
|
||||
const RuleHeaderSave = ({
|
||||
source,
|
||||
onSave,
|
||||
timeRange,
|
||||
validationError,
|
||||
onChooseTimeRange,
|
||||
}) =>
|
||||
<div className="page-header__right">
|
||||
<SourceIndicator sourceName={source.name} />
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
selected={timeRange}
|
||||
preventCustomTimeRange={true}
|
||||
/>
|
||||
{validationError
|
||||
? <button
|
||||
className="btn btn-success btn-sm disabled"
|
||||
data-for="save-kapacitor-tooltip"
|
||||
data-tip={validationError}
|
||||
>
|
||||
Save Rule
|
||||
</button>
|
||||
: <button className="btn btn-success btn-sm" onClick={onSave}>
|
||||
Save Rule
|
||||
</button>}
|
||||
<ReactTooltip
|
||||
id="save-kapacitor-tooltip"
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{bottom: 4}}
|
||||
place="bottom"
|
||||
class="influx-tooltip kapacitor-tooltip place-bottom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
RuleHeaderSave.propTypes = {
|
||||
source: shape({}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
validationError: string.isRequired,
|
||||
onChooseTimeRange: func.isRequired,
|
||||
timeRange: shape({}).isRequired,
|
||||
}
|
||||
|
||||
export default RuleHeaderSave
|
|
@ -15,17 +15,14 @@ class RuleMessage extends Component {
|
|||
this.state = {
|
||||
selectedAlertNodeName: null,
|
||||
}
|
||||
|
||||
this.handleChangeMessage = ::this.handleChangeMessage
|
||||
this.handleChooseAlert = ::this.handleChooseAlert
|
||||
}
|
||||
|
||||
handleChangeMessage() {
|
||||
handleChangeMessage = e => {
|
||||
const {actions, rule} = this.props
|
||||
actions.updateMessage(rule.id, this.message.value)
|
||||
actions.updateMessage(rule.id, e.target.value)
|
||||
}
|
||||
|
||||
handleChooseAlert(item) {
|
||||
handleChooseAlert = item => () => {
|
||||
const {actions} = this.props
|
||||
actions.updateAlerts(item.ruleID, [item.text])
|
||||
actions.updateAlertNodes(item.ruleID, item.text, '')
|
||||
|
@ -63,7 +60,7 @@ class RuleMessage extends Component {
|
|||
className={classnames({
|
||||
active: alert.text === selectedAlertNodeName,
|
||||
})}
|
||||
onClick={() => this.handleChooseAlert(alert)}
|
||||
onClick={this.handleChooseAlert(alert)}
|
||||
>
|
||||
{alert.text}
|
||||
</li>
|
||||
|
@ -77,7 +74,10 @@ class RuleMessage extends Component {
|
|||
updateDetails={actions.updateDetails}
|
||||
updateAlertProperty={actions.updateAlertProperty}
|
||||
/>
|
||||
<RuleMessageText rule={rule} updateMessage={actions.updateMessage} />
|
||||
<RuleMessageText
|
||||
rule={rule}
|
||||
updateMessage={this.handleChangeMessage}
|
||||
/>
|
||||
<RuleMessageTemplates
|
||||
rule={rule}
|
||||
updateMessage={actions.updateMessage}
|
||||
|
|
|
@ -8,11 +8,12 @@ import {
|
|||
class RuleMessageOptions extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.getAlertPropertyValue = ::this.getAlertPropertyValue
|
||||
}
|
||||
|
||||
getAlertPropertyValue(properties, name) {
|
||||
getAlertPropertyValue = name => {
|
||||
const {rule} = this.props
|
||||
const {properties} = rule.alertNodes[0]
|
||||
|
||||
if (properties) {
|
||||
const alertNodeProperty = properties.find(
|
||||
property => property.name === name
|
||||
|
@ -24,14 +25,26 @@ class RuleMessageOptions extends Component {
|
|||
return ''
|
||||
}
|
||||
|
||||
handleUpdateDetails = e => {
|
||||
const {updateDetails, rule} = this.props
|
||||
updateDetails(rule.id, e.target.value)
|
||||
}
|
||||
|
||||
handleUpdateAlertNodes = e => {
|
||||
const {updateAlertNodes, alertNodeName, rule} = this.props
|
||||
updateAlertNodes(rule.id, alertNodeName, e.target.value)
|
||||
}
|
||||
|
||||
handleUpdateAlertProperty = propertyName => e => {
|
||||
const {updateAlertProperty, alertNodeName, rule} = this.props
|
||||
updateAlertProperty(rule.id, alertNodeName, {
|
||||
name: propertyName,
|
||||
args: [e.target.value],
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
rule,
|
||||
alertNodeName,
|
||||
updateAlertNodes,
|
||||
updateDetails,
|
||||
updateAlertProperty,
|
||||
} = this.props
|
||||
const {rule, alertNodeName} = this.props
|
||||
const {args, details, properties} = RULE_ALERT_OPTIONS[alertNodeName]
|
||||
|
||||
return (
|
||||
|
@ -47,8 +60,7 @@ class RuleMessageOptions extends Component {
|
|||
style={{flex: '1 0 0%'}}
|
||||
type="text"
|
||||
placeholder={args.placeholder}
|
||||
onChange={e =>
|
||||
updateAlertNodes(rule.id, alertNodeName, e.target.value)}
|
||||
onChange={this.handleUpdateAlertNodes}
|
||||
value={ALERT_NODES_ACCESSORS[alertNodeName](rule)}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
|
@ -87,15 +99,8 @@ class RuleMessageOptions extends Component {
|
|||
}}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
onChange={e =>
|
||||
updateAlertProperty(rule.id, alertNodeName, {
|
||||
name: propertyName,
|
||||
args: [e.target.value],
|
||||
})}
|
||||
value={this.getAlertPropertyValue(
|
||||
rule.alertNodes[0].properties,
|
||||
propertyName
|
||||
)}
|
||||
onChange={this.handleUpdateAlertProperty(propertyName)}
|
||||
value={this.getAlertPropertyValue(propertyName)}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
/>
|
||||
|
@ -110,8 +115,7 @@ class RuleMessageOptions extends Component {
|
|||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
placeholder={details.placeholder ? details.placeholder : ''}
|
||||
ref={r => (this.details = r)}
|
||||
onChange={() => updateDetails(rule.id, this.details.value)}
|
||||
onChange={this.handleUpdateDetails}
|
||||
value={rule.details}
|
||||
spellCheck={false}
|
||||
/>
|
||||
|
|
|
@ -12,9 +12,12 @@ class RuleMessageTemplates extends Component {
|
|||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {rule, updateMessage} = this.props
|
||||
handleClickTemplate = template => () => {
|
||||
const {updateMessage, rule} = this.props
|
||||
updateMessage(rule.id, `${rule.message} ${template.label}`)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="rule-section--row rule-section--row-last rule-section--border-top">
|
||||
<p>Templates:</p>
|
||||
|
@ -23,8 +26,7 @@ class RuleMessageTemplates extends Component {
|
|||
<CodeData
|
||||
key={key}
|
||||
template={template}
|
||||
onClickTemplate={() =>
|
||||
updateMessage(rule.id, `${rule.message} ${template.label}`)}
|
||||
onClickTemplate={this.handleClickTemplate(template)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import React, {PropTypes} from 'react'
|
||||
|
||||
class RuleMessageText extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {rule, updateMessage} = this.props
|
||||
|
||||
return (
|
||||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
ref={r => (this.message = r)}
|
||||
onChange={() => updateMessage(rule.id, this.message.value)}
|
||||
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields "value" }}"
|
||||
value={rule.message}
|
||||
spellCheck={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
const RuleMessageText = ({rule, updateMessage}) =>
|
||||
<textarea
|
||||
className="form-control form-malachite monotype rule-builder--message"
|
||||
onChange={updateMessage}
|
||||
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields "value" }}"
|
||||
value={rule.message}
|
||||
spellCheck={false}
|
||||
/>
|
||||
|
||||
const {func, shape} = PropTypes
|
||||
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
class AlertaConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
const AlertaConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: shape({
|
||||
options: shape({
|
||||
environment: string,
|
||||
origin: string,
|
||||
token: bool,
|
||||
url: string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
},
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -28,7 +18,9 @@ const AlertaConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleTokenRef = r => (this.token = r)
|
||||
|
||||
render() {
|
||||
const {environment, origin, token, url} = this.props.config.options
|
||||
|
@ -62,7 +54,7 @@ const AlertaConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => (this.token = r)}
|
||||
refFunc={this.handleTokenRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -84,7 +76,21 @@ const AlertaConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
AlertaConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
environment: string,
|
||||
origin: string,
|
||||
token: bool,
|
||||
url: string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default AlertaConfig
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
|
||||
import {HIPCHAT_TOKEN_TIP} from 'src/kapacitor/copy'
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
class HipchatConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
const HipchatConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: shape({
|
||||
options: shape({
|
||||
room: string.isRequired,
|
||||
token: bool.isRequired,
|
||||
url: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
},
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -28,7 +19,9 @@ const HipchatConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleTokenRef = r => (this.token = r)
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
|
@ -72,7 +65,7 @@ const HipchatConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => (this.token = r)}
|
||||
refFunc={this.handleTokenRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -83,7 +76,20 @@ const HipchatConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
HipchatConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
room: string.isRequired,
|
||||
token: bool.isRequired,
|
||||
url: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default HipchatConfig
|
||||
|
|
|
@ -1,31 +1,21 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const {array, arrayOf, bool, func, shape, string} = PropTypes
|
||||
class OpsGenieConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const OpsGenieConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: shape({
|
||||
options: shape({
|
||||
'api-key': bool,
|
||||
teams: array,
|
||||
recipients: array,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
},
|
||||
const {teams, recipients} = props.config.options
|
||||
|
||||
getInitialState() {
|
||||
const {teams, recipients} = this.props.config.options
|
||||
return {
|
||||
this.state = {
|
||||
currentTeams: teams || [],
|
||||
currentRecipients: recipients || [],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -35,31 +25,33 @@ const OpsGenieConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleAddTeam(team) {
|
||||
handleAddTeam = team => {
|
||||
this.setState({currentTeams: this.state.currentTeams.concat(team)})
|
||||
},
|
||||
}
|
||||
|
||||
handleAddRecipient(recipient) {
|
||||
handleAddRecipient = recipient => {
|
||||
this.setState({
|
||||
currentRecipients: this.state.currentRecipients.concat(recipient),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
handleDeleteTeam(team) {
|
||||
handleDeleteTeam = team => () => {
|
||||
this.setState({
|
||||
currentTeams: this.state.currentTeams.filter(t => t !== team),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
handleDeleteRecipient(recipient) {
|
||||
handleDeleteRecipient = recipient => () => {
|
||||
this.setState({
|
||||
currentRecipients: this.state.currentRecipients.filter(
|
||||
r => r !== recipient
|
||||
),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
handleApiKeyRef = r => (this.apiKey = r)
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
|
@ -73,7 +65,7 @@ const OpsGenieConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={apiKey}
|
||||
id="api-key"
|
||||
refFunc={r => (this.apiKey = r)}
|
||||
refFunc={this.handleApiKeyRef}
|
||||
/>
|
||||
<label className="form-helper">
|
||||
Note: a value of <code>true</code> indicates the OpsGenie API key
|
||||
|
@ -101,18 +93,28 @@ const OpsGenieConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const TagInput = React.createClass({
|
||||
propTypes: {
|
||||
onAddTag: func.isRequired,
|
||||
onDeleteTag: func.isRequired,
|
||||
tags: arrayOf(string).isRequired,
|
||||
title: string.isRequired,
|
||||
},
|
||||
const {array, arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
handleAddTag(e) {
|
||||
OpsGenieConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
'api-key': bool,
|
||||
teams: array,
|
||||
recipients: array,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
class TagInput extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleAddTag = e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
const newItem = e.target.value.trim()
|
||||
|
@ -124,11 +126,11 @@ const TagInput = React.createClass({
|
|||
this.input.value = ''
|
||||
onAddTag(newItem)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
shouldAddToList(item, tags) {
|
||||
return !_.isEmpty(item) && !tags.find(l => l === item)
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {title, tags, onDeleteTag} = this.props
|
||||
|
@ -150,45 +152,39 @@ const TagInput = React.createClass({
|
|||
<Tags tags={tags} onDeleteTag={onDeleteTag} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const Tags = React.createClass({
|
||||
propTypes: {
|
||||
tags: arrayOf(string),
|
||||
onDeleteTag: func,
|
||||
},
|
||||
TagInput.propTypes = {
|
||||
onAddTag: func.isRequired,
|
||||
onDeleteTag: func.isRequired,
|
||||
tags: arrayOf(string).isRequired,
|
||||
title: string.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {tags, onDeleteTag} = this.props
|
||||
return (
|
||||
<div className="input-tag-list">
|
||||
{tags.map(item => {
|
||||
return <Tag key={item} item={item} onDelete={onDeleteTag} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
const Tags = ({tags, onDeleteTag}) =>
|
||||
<div className="input-tag-list">
|
||||
{tags.map(item => {
|
||||
return <Tag key={item} item={item} onDelete={onDeleteTag} />
|
||||
})}
|
||||
</div>
|
||||
|
||||
const Tag = React.createClass({
|
||||
propTypes: {
|
||||
item: string,
|
||||
onDelete: func,
|
||||
},
|
||||
Tags.propTypes = {
|
||||
tags: arrayOf(string),
|
||||
onDeleteTag: func,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {item, onDelete} = this.props
|
||||
const Tag = ({item, onDelete}) =>
|
||||
<span key={item} className="input-tag-item">
|
||||
<span>
|
||||
{item}
|
||||
</span>
|
||||
<span className="icon remove" onClick={onDelete(item)} />
|
||||
</span>
|
||||
|
||||
return (
|
||||
<span key={item} className="input-tag-item">
|
||||
<span>
|
||||
{item}
|
||||
</span>
|
||||
<span className="icon remove" onClick={() => onDelete(item)} />
|
||||
</span>
|
||||
)
|
||||
},
|
||||
})
|
||||
Tag.propTypes = {
|
||||
item: string,
|
||||
onDelete: func,
|
||||
}
|
||||
|
||||
export default OpsGenieConfig
|
||||
|
|
|
@ -1,26 +1,20 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
const PagerDutyConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
'service-key': PropTypes.bool.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
},
|
||||
class PagerDutyConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
serviceKey: this.serviceKey.value,
|
||||
'service-key': this.serviceKey.value,
|
||||
url: this.url.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
|
@ -62,7 +56,19 @@ const PagerDutyConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
PagerDutyConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
'service-key': bool.isRequired,
|
||||
url: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default PagerDutyConfig
|
||||
|
|
|
@ -8,11 +8,9 @@ import {PUSHOVER_DOCS_LINK} from 'src/kapacitor/copy'
|
|||
class PushoverConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleSaveAlert = ::this.handleSaveAlert
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -24,6 +22,10 @@ class PushoverConfig extends Component {
|
|||
this.props.onSave(properties)
|
||||
}
|
||||
|
||||
handleUserKeyRef = r => (this.userKey = r)
|
||||
|
||||
handleTokenRef = r => (this.token = r)
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
const {token, url} = options
|
||||
|
@ -42,7 +44,7 @@ class PushoverConfig extends Component {
|
|||
<RedactedInput
|
||||
defaultValue={userKey}
|
||||
id="user-key"
|
||||
refFunc={r => (this.userKey = r)}
|
||||
refFunc={this.handleUserKeyRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -57,7 +59,7 @@ class PushoverConfig extends Component {
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => (this.token = r)}
|
||||
refFunc={this.handleTokenRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@ class RedactedInput extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.setState({editing: true})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {defaultValue, id, refFunc} = this.props
|
||||
const {editing} = this.state
|
||||
|
@ -18,12 +22,7 @@ class RedactedInput extends Component {
|
|||
<span className="alert-value-set">
|
||||
<span className="icon checkmark" /> Value set
|
||||
</span>
|
||||
<button
|
||||
className="btn btn-xs btn-link"
|
||||
onClick={() => {
|
||||
this.setState({editing: true})
|
||||
}}
|
||||
>
|
||||
<button className="btn btn-xs btn-link" onClick={this.handleClick}>
|
||||
Change
|
||||
</button>
|
||||
<input
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
const SMTPConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
host: PropTypes.string,
|
||||
port: PropTypes.number,
|
||||
username: PropTypes.string,
|
||||
password: PropTypes.bool,
|
||||
from: PropTypes.string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
},
|
||||
class SMTPConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -26,7 +17,7 @@ const SMTPConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {host, port, from, username, password} = this.props.config.options
|
||||
|
@ -96,7 +87,22 @@ const SMTPConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, number, shape, string} = PropTypes
|
||||
|
||||
SMTPConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
host: string,
|
||||
port: number,
|
||||
username: string,
|
||||
password: bool,
|
||||
from: string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default SMTPConfig
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
const SensuConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
source: PropTypes.string.isRequired,
|
||||
addr: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
},
|
||||
class SensuConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -20,7 +14,7 @@ const SensuConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const {source, addr} = this.props.config.options
|
||||
|
@ -56,7 +50,19 @@ const SensuConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
SensuConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
source: string.isRequired,
|
||||
addr: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default SensuConfig
|
||||
|
|
|
@ -1,32 +1,22 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const SlackConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
url: PropTypes.bool.isRequired,
|
||||
channel: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onTest: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
class SlackConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
testEnabled: !!this.props.config.options.url,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
testEnabled: !!nextProps.config.options.url,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -35,15 +25,9 @@ const SlackConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleTest(e) {
|
||||
e.preventDefault()
|
||||
this.props.onTest({
|
||||
url: this.url.value,
|
||||
channel: this.channel.value,
|
||||
})
|
||||
},
|
||||
handleUrlRef = r => (this.url = r)
|
||||
|
||||
render() {
|
||||
const {url, channel} = this.props.config.options
|
||||
|
@ -61,7 +45,7 @@ const SlackConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={url}
|
||||
id="url"
|
||||
refFunc={r => (this.url = r)}
|
||||
refFunc={this.handleUrlRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -84,7 +68,19 @@ const SlackConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
SlackConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
url: bool.isRequired,
|
||||
channel: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default SlackConfig
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const {bool, string, shape, func} = PropTypes
|
||||
class TalkConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
const TalkConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: shape({
|
||||
options: shape({
|
||||
url: bool.isRequired,
|
||||
author_name: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
},
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -24,7 +16,9 @@ const TalkConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleUrlRef = r => (this.url = r)
|
||||
|
||||
render() {
|
||||
const {url, author_name: author} = this.props.config.options
|
||||
|
@ -36,7 +30,7 @@ const TalkConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={url}
|
||||
id="url"
|
||||
refFunc={r => (this.url = r)}
|
||||
refFunc={this.handleUrlRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -58,7 +52,19 @@ const TalkConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, string, shape, func} = PropTypes
|
||||
|
||||
TalkConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
url: bool.isRequired,
|
||||
author_name: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default TalkConfig
|
||||
|
|
|
@ -1,26 +1,15 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
|
||||
import {TELEGRAM_CHAT_ID_TIP, TELEGRAM_TOKEN_TIP} from 'src/kapacitor/copy'
|
||||
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
class TelegramConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
const TelegramConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: shape({
|
||||
options: shape({
|
||||
'chat-id': string.isRequired,
|
||||
'disable-notification': bool.isRequired,
|
||||
'disable-web-page-preview': bool.isRequired,
|
||||
'parse-mode': string.isRequired,
|
||||
token: bool.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
},
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
let parseMode
|
||||
|
@ -40,7 +29,9 @@ const TelegramConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleTokenRef = r => (this.token = r)
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
|
@ -76,7 +67,7 @@ const TelegramConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => (this.token = r)}
|
||||
refFunc={this.handleTokenRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -166,7 +157,22 @@ const TelegramConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
TelegramConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
'chat-id': string.isRequired,
|
||||
'disable-notification': bool.isRequired,
|
||||
'disable-web-page-preview': bool.isRequired,
|
||||
'parse-mode': string.isRequired,
|
||||
token: bool.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default TelegramConfig
|
||||
|
|
|
@ -1,20 +1,13 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
|
||||
import RedactedInput from './RedactedInput'
|
||||
|
||||
const VictorOpsConfig = React.createClass({
|
||||
propTypes: {
|
||||
config: PropTypes.shape({
|
||||
options: PropTypes.shape({
|
||||
'api-key': PropTypes.bool,
|
||||
'routing-key': PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
},
|
||||
class VictorOpsConfig extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleSaveAlert(e) {
|
||||
handleSaveAlert = e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -24,7 +17,9 @@ const VictorOpsConfig = React.createClass({
|
|||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
},
|
||||
}
|
||||
|
||||
handleApiRef = r => (this.apiKey = r)
|
||||
|
||||
render() {
|
||||
const {options} = this.props.config
|
||||
|
@ -39,7 +34,7 @@ const VictorOpsConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={apiKey}
|
||||
id="api-key"
|
||||
refFunc={r => (this.apiKey = r)}
|
||||
refFunc={this.handleApiRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -72,7 +67,20 @@ const VictorOpsConfig = React.createClass({
|
|||
</div>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {bool, shape, string, func} = PropTypes
|
||||
|
||||
VictorOpsConfig.propTypes = {
|
||||
config: shape({
|
||||
options: shape({
|
||||
'api-key': bool,
|
||||
'routing-key': string,
|
||||
url: string,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
onSave: func.isRequired,
|
||||
}
|
||||
|
||||
export default VictorOpsConfig
|
||||
|
|
|
@ -164,7 +164,7 @@ KapacitorPage.propTypes = {
|
|||
source: shape({
|
||||
id: string.isRequired,
|
||||
url: string.isRequired,
|
||||
kapacitors: array.isRequired,
|
||||
kapacitors: array,
|
||||
}),
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,6 @@ class KapacitorRulesPage extends Component {
|
|||
loading: true,
|
||||
tickscript: null,
|
||||
}
|
||||
|
||||
this.handleDeleteRule = ::this.handleDeleteRule
|
||||
this.handleRuleStatus = ::this.handleRuleStatus
|
||||
this.handleReadTickscript = ::this.handleReadTickscript
|
||||
this.handleCloseTickscript = ::this.handleCloseTickscript
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
|
@ -30,12 +25,12 @@ class KapacitorRulesPage extends Component {
|
|||
this.setState({loading: false, hasKapacitor: !!kapacitor})
|
||||
}
|
||||
|
||||
handleDeleteRule(rule) {
|
||||
handleDeleteRule = rule => () => {
|
||||
const {actions} = this.props
|
||||
actions.deleteRule(rule)
|
||||
}
|
||||
|
||||
handleRuleStatus(rule) {
|
||||
handleRuleStatus = rule => () => {
|
||||
const {actions} = this.props
|
||||
const status = rule.status === 'enabled' ? 'disabled' : 'enabled'
|
||||
|
||||
|
@ -43,11 +38,11 @@ class KapacitorRulesPage extends Component {
|
|||
actions.updateRuleStatusSuccess(rule.id, status)
|
||||
}
|
||||
|
||||
handleReadTickscript({tickscript}) {
|
||||
handleReadTickscript = ({tickscript}) => () => {
|
||||
this.setState({tickscript})
|
||||
}
|
||||
|
||||
handleCloseTickscript() {
|
||||
handleCloseTickscript = () => {
|
||||
this.setState({tickscript: null})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +1,42 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import OnClickOutside from 'shared/components/OnClickOutside'
|
||||
|
||||
import autoRefreshItems from 'hson!shared/data/autoRefreshes.hson'
|
||||
|
||||
const {number, func} = PropTypes
|
||||
|
||||
const AutoRefreshDropdown = React.createClass({
|
||||
autobind: false,
|
||||
|
||||
propTypes: {
|
||||
selected: number.isRequired,
|
||||
onChoose: func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
class AutoRefreshDropdown extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
findAutoRefreshItem(milliseconds) {
|
||||
return autoRefreshItems.find(values => values.milliseconds === milliseconds)
|
||||
},
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.setState({isOpen: false})
|
||||
},
|
||||
}
|
||||
|
||||
handleSelection(milliseconds) {
|
||||
handleSelection = milliseconds => () => {
|
||||
this.props.onChoose(milliseconds)
|
||||
this.setState({isOpen: false})
|
||||
},
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
},
|
||||
toggleMenu = () => this.setState({isOpen: !this.state.isOpen})
|
||||
|
||||
render() {
|
||||
const self = this
|
||||
const {selected} = self.props
|
||||
const {isOpen} = self.state
|
||||
const {selected} = this.props
|
||||
const {isOpen} = this.state
|
||||
const {milliseconds, inputValue} = this.findAutoRefreshItem(selected)
|
||||
|
||||
return (
|
||||
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
|
||||
<div
|
||||
className="btn btn-sm btn-default dropdown-toggle"
|
||||
onClick={() => self.toggleMenu()}
|
||||
onClick={this.toggleMenu}
|
||||
>
|
||||
<span
|
||||
className={classnames(
|
||||
|
@ -62,22 +51,24 @@ const AutoRefreshDropdown = React.createClass({
|
|||
</div>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="dropdown-header">AutoRefresh Interval</li>
|
||||
{autoRefreshItems.map(item => {
|
||||
return (
|
||||
<li className="dropdown-item" key={item.menuOption}>
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => self.handleSelection(item.milliseconds)}
|
||||
>
|
||||
{item.menuOption}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{autoRefreshItems.map(item =>
|
||||
<li className="dropdown-item" key={item.menuOption}>
|
||||
<a href="#" onClick={this.handleSelection(item.milliseconds)}>
|
||||
{item.menuOption}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const {number, func} = PropTypes
|
||||
|
||||
AutoRefreshDropdown.propTypes = {
|
||||
selected: number.isRequired,
|
||||
onChoose: func.isRequired,
|
||||
}
|
||||
|
||||
export default OnClickOutside(AutoRefreshDropdown)
|
||||
|
|
|
@ -5,24 +5,22 @@ import onClickOutside from 'shared/components/OnClickOutside'
|
|||
class ClickOutsideInput extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleClickOutside = ::this.handleClickOutside
|
||||
}
|
||||
|
||||
handleClickOutside(e) {
|
||||
this.props.handleClickOutsideCustomValueInput(e)
|
||||
handleClickOutside = e => {
|
||||
this.props.handleClickOutsideInput(e)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
customPlaceholder,
|
||||
onGetRef,
|
||||
customValue,
|
||||
onFocus,
|
||||
onChange,
|
||||
onGetRef,
|
||||
onKeyDown,
|
||||
customValue,
|
||||
customPlaceholder,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -53,7 +51,7 @@ ClickOutsideInput.propTypes = {
|
|||
onFocus: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
onKeyDown: func.isRequired,
|
||||
handleClickOutsideCustomValueInput: func.isRequired,
|
||||
handleClickOutsideInput: func.isRequired,
|
||||
}
|
||||
|
||||
export default onClickOutside(ClickOutsideInput)
|
||||
|
|
|
@ -1,27 +1,46 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
const ConfirmButtons = ({onConfirm, item, onCancel, buttonSize, isDisabled}) =>
|
||||
<div className="confirm-buttons">
|
||||
<button
|
||||
className={classnames('btn btn-info btn-square', {
|
||||
[buttonSize]: buttonSize,
|
||||
})}
|
||||
onClick={() => onCancel(item)}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</button>
|
||||
<button
|
||||
className={classnames('btn btn-success btn-square', {
|
||||
[buttonSize]: buttonSize,
|
||||
})}
|
||||
disabled={isDisabled}
|
||||
title={isDisabled ? 'Cannot Save' : 'Save'}
|
||||
onClick={() => onConfirm(item)}
|
||||
>
|
||||
<span className="icon checkmark" />
|
||||
</button>
|
||||
</div>
|
||||
class ConfirmButtons extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
handleConfirm = item => () => {
|
||||
this.props.onConfirm(item)
|
||||
}
|
||||
|
||||
handleCancel = item => () => {
|
||||
this.props.onCancel(item)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {item, buttonSize, isDisabled} = this.props
|
||||
|
||||
return (
|
||||
<div className="confirm-buttons">
|
||||
<button
|
||||
className={classnames('btn btn-info btn-square', {
|
||||
[buttonSize]: buttonSize,
|
||||
})}
|
||||
onClick={this.handleCancel(item)}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</button>
|
||||
<button
|
||||
className={classnames('btn btn-success btn-square', {
|
||||
[buttonSize]: buttonSize,
|
||||
})}
|
||||
disabled={isDisabled}
|
||||
title={isDisabled ? 'Cannot Save' : 'Save'}
|
||||
onClick={this.handleConfirm(item)}
|
||||
>
|
||||
<span className="icon checkmark" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, oneOfType, shape, string, bool} = PropTypes
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@ const ContextMenu = OnClickOutside(
|
|||
<span className="icon caret-down" />
|
||||
</button>
|
||||
<ul className="dash-graph--options-menu">
|
||||
<li onClick={() => onEdit(cell)}>Edit</li>
|
||||
<li onClick={onEdit(cell)}>Edit</li>
|
||||
<li onClick={onRename(cell.x, cell.y, cell.isEditing)}>Rename</li>
|
||||
<li onClick={() => onDelete(cell)}>Delete</li>
|
||||
<li onClick={onDelete(cell)}>Delete</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -2,12 +2,15 @@ import React, {PropTypes, Component} from 'react'
|
|||
import rome from 'rome'
|
||||
import moment from 'moment'
|
||||
|
||||
import shortcuts from 'hson!shared/data/timeRangeShortcuts.hson'
|
||||
|
||||
class CustomTimeRange extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleClick = ::this.handleClick
|
||||
this._formatTimeRange = ::this._formatTimeRange
|
||||
this.handleTimeRangeShortcut = ::this.handleTimeRangeShortcut
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -39,15 +42,29 @@ class CustomTimeRange extends Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="custom-time--container">
|
||||
<div className="custom-time--dates">
|
||||
<div className="custom-time--lower" ref={r => (this.lower = r)} />
|
||||
<div className="custom-time--upper" ref={r => (this.upper = r)} />
|
||||
<div className="custom-time--shortcuts">
|
||||
<div className="custom-time--shortcuts-header">Shortcuts</div>
|
||||
{shortcuts.map(({id, name}) =>
|
||||
<div
|
||||
key={id}
|
||||
className="custom-time--shortcut"
|
||||
onClick={this.handleTimeRangeShortcut(id)}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="custom-time--apply btn btn-sm btn-primary"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
Apply
|
||||
<div className="custom-time--wrap">
|
||||
<div className="custom-time--dates">
|
||||
<div className="custom-time--lower" ref={r => (this.lower = r)} />
|
||||
<div className="custom-time--upper" ref={r => (this.upper = r)} />
|
||||
</div>
|
||||
<div
|
||||
className="custom-time--apply btn btn-sm btn-primary"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
Apply
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -78,10 +95,48 @@ class CustomTimeRange extends Component {
|
|||
const upper = this.upperCal.getDate().toISOString()
|
||||
|
||||
onApplyTimeRange({lower, upper})
|
||||
|
||||
if (onClose) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
handleTimeRangeShortcut(shortcut) {
|
||||
return () => {
|
||||
let lower
|
||||
const upper = moment()
|
||||
|
||||
switch (shortcut) {
|
||||
case 'pastWeek': {
|
||||
lower = moment().subtract(1, 'week')
|
||||
break
|
||||
}
|
||||
case 'pastMonth': {
|
||||
lower = moment().subtract(1, 'month')
|
||||
break
|
||||
}
|
||||
case 'pastYear': {
|
||||
lower = moment().subtract(1, 'year')
|
||||
break
|
||||
}
|
||||
case 'thisWeek': {
|
||||
lower = moment().startOf('week')
|
||||
break
|
||||
}
|
||||
case 'thisMonth': {
|
||||
lower = moment().startOf('month')
|
||||
break
|
||||
}
|
||||
case 'thisYear': {
|
||||
lower = moment().startOf('year')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.lowerCal.setValue(lower)
|
||||
this.upperCal.setValue(upper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
|
|
@ -11,8 +11,6 @@ class DatabaseDropdown extends Component {
|
|||
this.state = {
|
||||
databases: [],
|
||||
}
|
||||
|
||||
this._getDatabases = ::this._getDatabases
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -32,12 +30,12 @@ class DatabaseDropdown extends Component {
|
|||
items={databases.map(text => ({text}))}
|
||||
selected={database || 'Loading...'}
|
||||
onChoose={onSelectDatabase}
|
||||
onClick={onStartEdit ? () => onStartEdit(null) : null}
|
||||
onClick={onStartEdit ? onStartEdit : null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
async _getDatabases() {
|
||||
_getDatabases = async () => {
|
||||
const {source} = this.context
|
||||
const {database, onSelectDatabase, onErrorThrown} = this.props
|
||||
const proxy = source.links.proxy
|
||||
|
|
|
@ -14,16 +14,6 @@ class Dropdown extends Component {
|
|||
filteredItems: this.props.items,
|
||||
highlightedItemIndex: null,
|
||||
}
|
||||
|
||||
this.handleClickOutside = ::this.handleClickOutside
|
||||
this.handleClick = ::this.handleClick
|
||||
this.handleSelection = ::this.handleSelection
|
||||
this.toggleMenu = ::this.toggleMenu
|
||||
this.handleAction = ::this.handleAction
|
||||
this.handleFilterChange = ::this.handleFilterChange
|
||||
this.applyFilter = ::this.applyFilter
|
||||
this.handleFilterKeyPress = ::this.handleFilterKeyPress
|
||||
this.handleHighlight = ::this.handleHighlight
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -34,27 +24,27 @@ class Dropdown extends Component {
|
|||
useAutoComplete: false,
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
handleClickOutside = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
handleClick = e => {
|
||||
this.toggleMenu(e)
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleSelection(item) {
|
||||
handleSelection = item => () => {
|
||||
this.toggleMenu()
|
||||
this.props.onChoose(item)
|
||||
}
|
||||
|
||||
handleHighlight(itemIndex) {
|
||||
handleHighlight = itemIndex => () => {
|
||||
this.setState({highlightedItemIndex: itemIndex})
|
||||
}
|
||||
|
||||
toggleMenu(e) {
|
||||
toggleMenu = e => {
|
||||
if (e) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
@ -68,12 +58,12 @@ class Dropdown extends Component {
|
|||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
handleAction(e, action, item) {
|
||||
handleAction = (action, item) => e => {
|
||||
e.stopPropagation()
|
||||
action.handler(item)
|
||||
}
|
||||
|
||||
handleFilterKeyPress(e) {
|
||||
handleFilterKeyPress = e => {
|
||||
const {filteredItems, highlightedItemIndex} = this.state
|
||||
|
||||
if (e.key === 'Enter' && filteredItems.length) {
|
||||
|
@ -96,7 +86,7 @@ class Dropdown extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleFilterChange(e) {
|
||||
handleFilterChange = e => {
|
||||
if (e.target.value === null || e.target.value === '') {
|
||||
this.setState({
|
||||
searchTerm: '',
|
||||
|
@ -110,7 +100,7 @@ class Dropdown extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
applyFilter(searchTerm) {
|
||||
applyFilter = searchTerm => {
|
||||
const {items} = this.props
|
||||
const filterText = searchTerm.toLowerCase()
|
||||
const matchingItems = items.filter(item =>
|
||||
|
@ -169,8 +159,8 @@ class Dropdown extends Component {
|
|||
>
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => this.handleSelection(item)}
|
||||
onMouseOver={() => this.handleHighlight(i)}
|
||||
onClick={this.handleSelection(item)}
|
||||
onMouseOver={this.handleHighlight(i)}
|
||||
>
|
||||
{item.text}
|
||||
</a>
|
||||
|
@ -181,7 +171,7 @@ class Dropdown extends Component {
|
|||
<button
|
||||
key={action.text}
|
||||
className="dropdown-action"
|
||||
onClick={e => this.handleAction(e, action, item)}
|
||||
onClick={this.handleAction(action, item)}
|
||||
>
|
||||
<span
|
||||
title={action.text}
|
||||
|
|
|
@ -33,16 +33,6 @@ export default class Dygraph extends Component {
|
|||
isSnipped: false,
|
||||
isFilterVisible: false,
|
||||
}
|
||||
|
||||
this.sync = ::this.sync
|
||||
this.getTimeSeries = ::this.getTimeSeries
|
||||
this.handleSortLegend = ::this.handleSortLegend
|
||||
this.handleLegendInputChange = ::this.handleLegendInputChange
|
||||
this.handleSnipLabel = ::this.handleSnipLabel
|
||||
this.handleHideLegend = ::this.handleHideLegend
|
||||
this.handleToggleFilter = ::this.handleToggleFilter
|
||||
this.visibility = ::this.visibility
|
||||
this.getLabel = ::this.getLabel
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -52,25 +42,6 @@ export default class Dygraph extends Component {
|
|||
dygraphRef: () => {},
|
||||
}
|
||||
|
||||
getTimeSeries() {
|
||||
const {timeSeries} = this.props
|
||||
// Avoid 'Can't plot empty data set' errors by falling back to a
|
||||
// default dataset that's valid for Dygraph.
|
||||
return timeSeries.length ? timeSeries : [[0]]
|
||||
}
|
||||
|
||||
getLabel(axis) {
|
||||
const {axes, queries} = this.props
|
||||
const label = _.get(axes, [axis, 'label'], '')
|
||||
const queryConfig = _.get(queries, ['0', 'queryConfig'], false)
|
||||
|
||||
if (label || !queryConfig) {
|
||||
return label
|
||||
}
|
||||
|
||||
return buildDefaultYLabel(queryConfig)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const timeSeries = this.getTimeSeries()
|
||||
// dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'};
|
||||
|
@ -248,22 +219,6 @@ export default class Dygraph extends Component {
|
|||
return shallowCompare(this, nextProps, nextState)
|
||||
}
|
||||
|
||||
visibility() {
|
||||
const timeSeries = this.getTimeSeries()
|
||||
const {filterText, legend} = this.state
|
||||
const series = _.get(timeSeries, '0', [])
|
||||
const numSeries = series.length
|
||||
return Array(numSeries ? numSeries - 1 : numSeries)
|
||||
.fill(true)
|
||||
.map((s, i) => {
|
||||
if (!legend.series[i]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !!legend.series[i].label.match(filterText)
|
||||
})
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const {
|
||||
labels,
|
||||
|
@ -325,33 +280,33 @@ export default class Dygraph extends Component {
|
|||
this.props.setResolution(w)
|
||||
}
|
||||
|
||||
sync() {
|
||||
sync = () => {
|
||||
if (!this.state.isSynced) {
|
||||
this.props.synchronizer(this.dygraph)
|
||||
this.setState({isSynced: true})
|
||||
}
|
||||
}
|
||||
|
||||
handleSortLegend(sortType) {
|
||||
handleSortLegend = sortType => () => {
|
||||
this.setState({sortType, isAscending: !this.state.isAscending})
|
||||
}
|
||||
|
||||
handleLegendInputChange(e) {
|
||||
handleLegendInputChange = e => {
|
||||
this.setState({filterText: e.target.value})
|
||||
}
|
||||
|
||||
handleSnipLabel() {
|
||||
handleSnipLabel = () => {
|
||||
this.setState({isSnipped: !this.state.isSnipped})
|
||||
}
|
||||
|
||||
handleToggleFilter() {
|
||||
handleToggleFilter = () => {
|
||||
this.setState({
|
||||
isFilterVisible: !this.state.isFilterVisible,
|
||||
filterText: '',
|
||||
})
|
||||
}
|
||||
|
||||
handleHideLegend(e) {
|
||||
handleHideLegend = e => {
|
||||
const {top, bottom, left, right} = this.graphRef.getBoundingClientRect()
|
||||
|
||||
const mouseY = e.clientY
|
||||
|
@ -369,6 +324,43 @@ export default class Dygraph extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
visibility = () => {
|
||||
const timeSeries = this.getTimeSeries()
|
||||
const {filterText, legend} = this.state
|
||||
const series = _.get(timeSeries, '0', [])
|
||||
const numSeries = series.length
|
||||
return Array(numSeries ? numSeries - 1 : numSeries)
|
||||
.fill(true)
|
||||
.map((s, i) => {
|
||||
if (!legend.series[i]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !!legend.series[i].label.match(filterText)
|
||||
})
|
||||
}
|
||||
|
||||
getTimeSeries = () => {
|
||||
const {timeSeries} = this.props
|
||||
// Avoid 'Can't plot empty data set' errors by falling back to a
|
||||
// default dataset that's valid for Dygraph.
|
||||
return timeSeries.length ? timeSeries : [[0]]
|
||||
}
|
||||
|
||||
getLabel = axis => {
|
||||
const {axes, queries} = this.props
|
||||
const label = _.get(axes, [axis, 'label'], '')
|
||||
const queryConfig = _.get(queries, ['0', 'queryConfig'], false)
|
||||
|
||||
if (label || !queryConfig) {
|
||||
return label
|
||||
}
|
||||
|
||||
return buildDefaultYLabel(queryConfig)
|
||||
}
|
||||
|
||||
handleLegendRef = el => (this.legendRef = el)
|
||||
|
||||
render() {
|
||||
const {
|
||||
legend,
|
||||
|
@ -393,7 +385,7 @@ export default class Dygraph extends Component {
|
|||
isAscending={isAscending}
|
||||
onSnip={this.handleSnipLabel}
|
||||
onSort={this.handleSortLegend}
|
||||
legendRef={el => (this.legendRef = el)}
|
||||
legendRef={this.handleLegendRef}
|
||||
onInputChange={this.handleLegendInputChange}
|
||||
onToggleFilter={this.handleToggleFilter}
|
||||
/>
|
||||
|
|
|
@ -39,7 +39,7 @@ const DygraphLegend = ({
|
|||
'sort-btn--asc': isAscending && sortType !== 'numeric',
|
||||
'sort-btn--desc': !isAscending && sortType !== 'numeric',
|
||||
})}
|
||||
onClick={() => onSort('alphabetic')}
|
||||
onClick={onSort('alphabetic')}
|
||||
>
|
||||
<div className="sort-btn--arrow" />
|
||||
<div className="sort-btn--top">A</div>
|
||||
|
@ -54,7 +54,7 @@ const DygraphLegend = ({
|
|||
'sort-btn--asc': isAscending && sortType === 'numeric',
|
||||
'sort-btn--desc': !isAscending && sortType === 'numeric',
|
||||
})}
|
||||
onClick={() => onSort('numeric')}
|
||||
onClick={onSort('numeric')}
|
||||
>
|
||||
<div className="sort-btn--arrow" />
|
||||
<div className="sort-btn--top">0</div>
|
||||
|
|
|
@ -12,6 +12,10 @@ class FancyScrollbar extends Component {
|
|||
autoHeight: false,
|
||||
}
|
||||
|
||||
handleMakeDiv = className => props => {
|
||||
return <div {...props} className={`fancy-scroll--${className}`} />
|
||||
}
|
||||
|
||||
render() {
|
||||
const {autoHide, autoHeight, children, className, maxHeight} = this.props
|
||||
|
||||
|
@ -25,15 +29,11 @@ class FancyScrollbar extends Component {
|
|||
autoHideDuration={250}
|
||||
autoHeight={autoHeight}
|
||||
autoHeightMax={maxHeight}
|
||||
renderTrackHorizontal={props =>
|
||||
<div {...props} className="fancy-scroll--track-h" />}
|
||||
renderTrackVertical={props =>
|
||||
<div {...props} className="fancy-scroll--track-v" />}
|
||||
renderThumbHorizontal={props =>
|
||||
<div {...props} className="fancy-scroll--thumb-h" />}
|
||||
renderThumbVertical={props =>
|
||||
<div {...props} className="fancy-scroll--thumb-v" />}
|
||||
renderView={props => <div {...props} className="fancy-scroll--view" />}
|
||||
renderTrackHorizontal={this.handleMakeDiv('track-h')}
|
||||
renderTrackVertical={this.handleMakeDiv('track-v')}
|
||||
renderThumbHorizontal={this.handleMakeDiv('thumb-h')}
|
||||
renderThumbVertical={this.handleMakeDiv('thumb-v')}
|
||||
renderView={this.handleMakeDiv('view')}
|
||||
>
|
||||
{children}
|
||||
</Scrollbars>
|
||||
|
|
|
@ -38,7 +38,7 @@ class MultiSelectDropdown extends Component {
|
|||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
toggleMenu(e) {
|
||||
toggleMenu = e => {
|
||||
e.stopPropagation()
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class MultiSelectDropdown extends Component {
|
|||
return (
|
||||
<div className={classnames(`dropdown ${customClass}`, {open: isOpen})}>
|
||||
<div
|
||||
onClick={::this.toggleMenu}
|
||||
onClick={this.toggleMenu}
|
||||
className={`dropdown-toggle btn ${buttonSize} ${buttonColor}`}
|
||||
>
|
||||
{iconName ? <span className={`icon ${iconName}`} /> : null}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import NameableGraphHeader from 'shared/components/NameableGraphHeader'
|
||||
import ContextMenu from 'shared/components/ContextMenu'
|
||||
|
@ -12,41 +13,42 @@ class NameableGraph extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
toggleMenu = () => {
|
||||
this.setState({
|
||||
isMenuOpen: !this.state.isMenuOpen,
|
||||
})
|
||||
}
|
||||
|
||||
handleRenameCell(e) {
|
||||
handleRenameCell = e => {
|
||||
const cellName = e.target.value
|
||||
this.setState({cellName})
|
||||
}
|
||||
|
||||
handleCancelEdit(cellID) {
|
||||
handleCancelEdit = cellID => {
|
||||
const {cell, onCancelEditCell} = this.props
|
||||
this.setState({cellName: cell.name})
|
||||
onCancelEditCell(cellID)
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
closeMenu = () => {
|
||||
this.setState({
|
||||
isMenuOpen: false,
|
||||
})
|
||||
}
|
||||
|
||||
handleDeleteCell = cell => () => {
|
||||
this.props.onDeleteCell(cell)
|
||||
}
|
||||
|
||||
handleSummonOverlay = cell => () => {
|
||||
this.props.onSummonOverlayTechnologies(cell)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
cell,
|
||||
onEditCell,
|
||||
onUpdateCell,
|
||||
onDeleteCell,
|
||||
onSummonOverlayTechnologies,
|
||||
isEditable,
|
||||
children,
|
||||
} = this.props
|
||||
const {cell, children, isEditable, onEditCell, onUpdateCell} = this.props
|
||||
|
||||
const {cellName, isMenuOpen} = this.state
|
||||
const queries = _.get(cell, ['queries'], [])
|
||||
|
||||
return (
|
||||
<div className="dash-graph">
|
||||
|
@ -55,26 +57,26 @@ class NameableGraph extends Component {
|
|||
cellName={cellName}
|
||||
isEditable={isEditable}
|
||||
onUpdateCell={onUpdateCell}
|
||||
onRenameCell={::this.handleRenameCell}
|
||||
onCancelEditCell={::this.handleCancelEdit}
|
||||
onRenameCell={this.handleRenameCell}
|
||||
onCancelEditCell={this.handleCancelEdit}
|
||||
/>
|
||||
<ContextMenu
|
||||
cell={cell}
|
||||
onDelete={onDeleteCell}
|
||||
onDelete={this.handleDeleteCell}
|
||||
onRename={!cell.isEditing && isEditable ? onEditCell : () => {}}
|
||||
toggleMenu={::this.toggleMenu}
|
||||
toggleMenu={this.toggleMenu}
|
||||
isOpen={isMenuOpen}
|
||||
isEditable={isEditable}
|
||||
handleClickOutside={::this.closeMenu}
|
||||
onEdit={onSummonOverlayTechnologies}
|
||||
handleClickOutside={this.closeMenu}
|
||||
onEdit={this.handleSummonOverlay}
|
||||
/>
|
||||
<div className="dash-graph--container">
|
||||
{cell.queries.length
|
||||
{queries.length
|
||||
? children
|
||||
: <div className="graph-empty">
|
||||
<button
|
||||
className="no-query--button btn btn-md btn-primary"
|
||||
onClick={() => onSummonOverlayTechnologies(cell)}
|
||||
onClick={this.handleSummonOverlay(cell)}
|
||||
>
|
||||
Add Graph
|
||||
</button>
|
||||
|
|
|
@ -41,15 +41,15 @@ class Notifications extends Component {
|
|||
)
|
||||
}
|
||||
|
||||
renderDismiss(type) {
|
||||
const {dismissNotification} = this.props
|
||||
handleDismiss = type => () => this.props.dismissNotification(type)
|
||||
|
||||
renderDismiss(type) {
|
||||
return (
|
||||
<button
|
||||
className="close"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close"
|
||||
onClick={() => dismissNotification(type)}
|
||||
onClick={this.handleDismiss(type)}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</button>
|
||||
|
|
|
@ -19,77 +19,57 @@ class OptIn extends Component {
|
|||
|
||||
this.id = uuid.v4()
|
||||
this.isCustomValueInputFocused = false
|
||||
|
||||
this.useFixedValue = ::this.useFixedValue
|
||||
this.useCustomValue = ::this.useCustomValue
|
||||
this.considerResetCustomValue = ::this.considerResetCustomValue
|
||||
this.setCustomValue = ::this.setCustomValue
|
||||
this.setValue = ::this.setValue
|
||||
}
|
||||
|
||||
useFixedValue() {
|
||||
useFixedValue = () => {
|
||||
this.setState({useCustomValue: false, customValue: ''}, () =>
|
||||
this.setValue()
|
||||
)
|
||||
}
|
||||
|
||||
useCustomValue() {
|
||||
useCustomValue = () => {
|
||||
this.setState({useCustomValue: true}, () => this.setValue())
|
||||
}
|
||||
|
||||
handleClickFixedValueField() {
|
||||
return () => this.useFixedValue()
|
||||
}
|
||||
|
||||
handleClickToggle() {
|
||||
return () => {
|
||||
const useCustomValueNext = !this.state.useCustomValue
|
||||
if (useCustomValueNext) {
|
||||
this.useCustomValue()
|
||||
this.customValueInput.focus()
|
||||
} else {
|
||||
this.useFixedValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleFocusCustomValueInput() {
|
||||
return () => {
|
||||
this.isCustomValueInputFocused = true
|
||||
handleClickToggle = () => {
|
||||
const useCustomValueNext = !this.state.useCustomValue
|
||||
if (useCustomValueNext) {
|
||||
this.useCustomValue()
|
||||
this.customValueInput.focus()
|
||||
} else {
|
||||
this.useFixedValue()
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeCustomValue() {
|
||||
return e => {
|
||||
this.setCustomValue(e.target.value)
|
||||
}
|
||||
handleFocusCustomValueInput = () => {
|
||||
this.isCustomValueInputFocused = true
|
||||
this.useCustomValue()
|
||||
}
|
||||
|
||||
handleKeyDownCustomValueInput() {
|
||||
return e => {
|
||||
if (e.key === 'Enter' || e.key === 'Tab') {
|
||||
if (e.key === 'Enter') {
|
||||
this.customValueInput.blur()
|
||||
}
|
||||
this.considerResetCustomValue()
|
||||
handleChangeCustomValue = e => {
|
||||
this.setCustomValue(e.target.value)
|
||||
}
|
||||
|
||||
handleKeyDownCustomValueInput = e => {
|
||||
if (e.key === 'Enter' || e.key === 'Tab') {
|
||||
if (e.key === 'Enter') {
|
||||
this.customValueInput.blur()
|
||||
}
|
||||
this.considerResetCustomValue()
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOutsideCustomValueInput() {
|
||||
return e => {
|
||||
if (
|
||||
e.target.id !== this.grooveKnob.id &&
|
||||
e.target.id !== this.grooveKnobContainer.id &&
|
||||
this.isCustomValueInputFocused
|
||||
) {
|
||||
this.considerResetCustomValue()
|
||||
}
|
||||
handleClickOutsideInput = e => {
|
||||
if (
|
||||
e.target.id !== this.grooveKnob.id &&
|
||||
e.target.id !== this.grooveKnobContainer.id &&
|
||||
this.isCustomValueInputFocused
|
||||
) {
|
||||
this.considerResetCustomValue()
|
||||
}
|
||||
}
|
||||
|
||||
considerResetCustomValue() {
|
||||
considerResetCustomValue = () => {
|
||||
const customValue = this.customValueInput.value.trim()
|
||||
|
||||
this.setState({customValue})
|
||||
|
@ -101,11 +81,11 @@ class OptIn extends Component {
|
|||
this.isCustomValueInputFocused = false
|
||||
}
|
||||
|
||||
setCustomValue(value) {
|
||||
setCustomValue = value => {
|
||||
this.setState({customValue: value}, this.setValue)
|
||||
}
|
||||
|
||||
setValue() {
|
||||
setValue = () => {
|
||||
const {onSetValue} = this.props
|
||||
const {useCustomValue, fixedValue, customValue} = this.state
|
||||
|
||||
|
@ -116,6 +96,8 @@ class OptIn extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleInputRef = el => (this.customValueInput = el)
|
||||
|
||||
render() {
|
||||
const {fixedPlaceholder, customPlaceholder, type} = this.props
|
||||
const {useCustomValue, customValue} = this.state
|
||||
|
@ -129,20 +111,20 @@ class OptIn extends Component {
|
|||
<ClickOutsideInput
|
||||
id={this.id}
|
||||
type={type}
|
||||
customPlaceholder={customPlaceholder}
|
||||
customValue={customValue}
|
||||
onGetRef={el => (this.customValueInput = el)}
|
||||
onFocus={this.handleFocusCustomValueInput()}
|
||||
onChange={this.handleChangeCustomValue()}
|
||||
onKeyDown={this.handleKeyDownCustomValueInput()}
|
||||
handleClickOutsideCustomValueInput={this.handleClickOutsideCustomValueInput()}
|
||||
onGetRef={this.handleInputRef}
|
||||
customPlaceholder={customPlaceholder}
|
||||
onChange={this.handleChangeCustomValue}
|
||||
onFocus={this.handleFocusCustomValueInput}
|
||||
onKeyDown={this.handleKeyDownCustomValueInput}
|
||||
handleClickOutsideInput={this.handleClickOutsideInput}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="opt-in--groove-knob-container"
|
||||
id={this.id}
|
||||
ref={el => (this.grooveKnobContainer = el)}
|
||||
onClick={this.handleClickToggle()}
|
||||
onClick={this.handleClickToggle}
|
||||
>
|
||||
<div
|
||||
className="opt-in--groove-knob"
|
||||
|
@ -150,10 +132,7 @@ class OptIn extends Component {
|
|||
ref={el => (this.grooveKnob = el)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="opt-in--left-label"
|
||||
onClick={this.handleClickFixedValueField()}
|
||||
>
|
||||
<div className="opt-in--left-label" onClick={this.useFixedValue}>
|
||||
{fixedPlaceholder}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,10 +14,8 @@ const TemplateDrawer = ({
|
|||
className={classnames('template-drawer--item', {
|
||||
'template-drawer--selected': t.tempVar === selected.tempVar,
|
||||
})}
|
||||
onMouseOver={() => {
|
||||
onMouseOverTempVar(t)
|
||||
}}
|
||||
onClick={() => onClickTempVar(t)}
|
||||
onMouseOver={onMouseOverTempVar(t)}
|
||||
onClick={onClickTempVar(t)}
|
||||
key={t.tempVar}
|
||||
>
|
||||
{' '}{t.tempVar}{' '}
|
||||
|
|
|
@ -31,17 +31,9 @@ class TimeRangeDropdown extends Component {
|
|||
upper: '',
|
||||
},
|
||||
}
|
||||
|
||||
this.findTimeRangeInputValue = ::this.findTimeRangeInputValue
|
||||
this.handleSelection = ::this.handleSelection
|
||||
this.toggleMenu = ::this.toggleMenu
|
||||
this.showCustomTimeRange = ::this.showCustomTimeRange
|
||||
this.handleApplyCustomTimeRange = ::this.handleApplyCustomTimeRange
|
||||
this.handleToggleCustomTimeRange = ::this.handleToggleCustomTimeRange
|
||||
this.handleCloseCustomTimeRange = ::this.handleCloseCustomTimeRange
|
||||
}
|
||||
|
||||
findTimeRangeInputValue({upper, lower}) {
|
||||
findTimeRangeInputValue = ({upper, lower}) => {
|
||||
if (upper && lower) {
|
||||
const format = t =>
|
||||
moment(t.replace(/\'/g, '')).format('YYYY-MM-DD HH:mm')
|
||||
|
@ -52,46 +44,53 @@ class TimeRangeDropdown extends Component {
|
|||
return selected ? selected.inputValue : 'Custom'
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
handleClickOutside = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
handleSelection(timeRange) {
|
||||
handleSelection = timeRange => () => {
|
||||
this.props.onChooseTimeRange(timeRange)
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
toggleMenu = () => {
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
showCustomTimeRange() {
|
||||
showCustomTimeRange = () => {
|
||||
this.setState({isCustomTimeRangeOpen: true})
|
||||
}
|
||||
|
||||
handleApplyCustomTimeRange(customTimeRange) {
|
||||
this.setState({customTimeRange})
|
||||
this.handleSelection({...customTimeRange})
|
||||
handleApplyCustomTimeRange = customTimeRange => {
|
||||
this.props.onChooseTimeRange({...customTimeRange})
|
||||
this.setState({customTimeRange, isOpen: false})
|
||||
}
|
||||
|
||||
handleToggleCustomTimeRange() {
|
||||
handleToggleCustomTimeRange = () => {
|
||||
this.setState({isCustomTimeRangeOpen: !this.state.isCustomTimeRangeOpen})
|
||||
}
|
||||
|
||||
handleCloseCustomTimeRange() {
|
||||
handleCloseCustomTimeRange = () => {
|
||||
this.setState({isCustomTimeRangeOpen: false})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {selected, preventCustomTimeRange} = this.props
|
||||
const {isOpen, customTimeRange, isCustomTimeRangeOpen} = this.state
|
||||
const isRelativeTimeRange = selected.upper === null
|
||||
|
||||
return (
|
||||
<div className="time-range-dropdown">
|
||||
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
|
||||
<div
|
||||
className={classnames('dropdown', {
|
||||
'dropdown-160': isRelativeTimeRange,
|
||||
'dropdown-290': !isRelativeTimeRange,
|
||||
open: isOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className="btn btn-sm btn-default dropdown-toggle"
|
||||
onClick={() => this.toggleMenu()}
|
||||
onClick={this.toggleMenu}
|
||||
>
|
||||
<span className="icon clock" />
|
||||
<span className="dropdown-selected">
|
||||
|
@ -105,24 +104,29 @@ class TimeRangeDropdown extends Component {
|
|||
autoHeight={true}
|
||||
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
|
||||
>
|
||||
<li className="dropdown-header">Time Range</li>
|
||||
{preventCustomTimeRange
|
||||
? null
|
||||
: <li
|
||||
className={
|
||||
isCustomTimeRangeOpen
|
||||
? 'active dropdown-item custom-timerange'
|
||||
: 'dropdown-item custom-timerange'
|
||||
}
|
||||
>
|
||||
<a href="#" onClick={this.showCustomTimeRange}>
|
||||
Custom Time Range
|
||||
</a>
|
||||
</li>}
|
||||
: <div>
|
||||
<li className="dropdown-header">Absolute Time Ranges</li>
|
||||
<li
|
||||
className={
|
||||
isCustomTimeRangeOpen
|
||||
? 'active dropdown-item custom-timerange'
|
||||
: 'dropdown-item custom-timerange'
|
||||
}
|
||||
>
|
||||
<a href="#" onClick={this.showCustomTimeRange}>
|
||||
Custom Date Picker
|
||||
</a>
|
||||
</li>
|
||||
</div>}
|
||||
<li className="dropdown-header">
|
||||
{preventCustomTimeRange ? '' : 'Relative '}Time Ranges
|
||||
</li>
|
||||
{timeRanges.map(item => {
|
||||
return (
|
||||
<li className="dropdown-item" key={item.menuOption}>
|
||||
<a href="#" onClick={() => this.handleSelection(item)}>
|
||||
<a href="#" onClick={this.handleSelection(item)}>
|
||||
{item.menuOption}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
[
|
||||
{
|
||||
"id": "pastWeek",
|
||||
"name": "Past Week"
|
||||
},
|
||||
{
|
||||
"id": "pastMonth",
|
||||
"name": "Past Month"
|
||||
},
|
||||
{
|
||||
"id": "pastYear",
|
||||
"name": "Past Year"
|
||||
},
|
||||
{
|
||||
"id": "thisWeek",
|
||||
"name": "This Week"
|
||||
},
|
||||
{
|
||||
"id": "thisMonth",
|
||||
"name": "This Month"
|
||||
},
|
||||
{
|
||||
"id": "thisYear",
|
||||
"name": "This Year"
|
||||
}
|
||||
]
|
|
@ -44,7 +44,7 @@ const kapacitorDropdown = (
|
|||
buttonColor="btn-primary"
|
||||
buttonSize="btn-xs"
|
||||
items={kapacitorItems}
|
||||
onChoose={item => setActiveKapacitor(item.kapacitor)}
|
||||
onChoose={setActiveKapacitor}
|
||||
addNew={{
|
||||
url: `/sources/${source.id}/kapacitors/new`,
|
||||
text: 'Add Kapacitor',
|
||||
|
@ -72,12 +72,12 @@ const kapacitorDropdown = (
|
|||
}
|
||||
|
||||
const InfluxTable = ({
|
||||
sources,
|
||||
source,
|
||||
handleDeleteSource,
|
||||
location,
|
||||
router,
|
||||
sources,
|
||||
location,
|
||||
setActiveKapacitor,
|
||||
handleDeleteSource,
|
||||
handleDeleteKapacitor,
|
||||
}) =>
|
||||
<div className="row">
|
||||
|
@ -147,7 +147,7 @@ const InfluxTable = ({
|
|||
<a
|
||||
className="btn btn-xs btn-danger table--show-on-row-hover"
|
||||
href="#"
|
||||
onClick={() => handleDeleteSource(s)}
|
||||
onClick={handleDeleteSource(s)}
|
||||
>
|
||||
Delete Source
|
||||
</a>
|
||||
|
|
|
@ -18,8 +18,6 @@ const V_NUMBER = VERSION // eslint-disable-line no-undef
|
|||
class ManageSources extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.handleDeleteSource = ::this.handleDeleteSource
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -36,7 +34,7 @@ class ManageSources extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDeleteSource(source) {
|
||||
handleDeleteSource = source => () => {
|
||||
const {addFlashMessage} = this.props
|
||||
|
||||
try {
|
||||
|
@ -49,8 +47,12 @@ class ManageSources extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSetActiveKapacitor = ({kapacitor}) => {
|
||||
this.props.setActiveKapacitor(kapacitor)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {sources, source, setActiveKapacitor, deleteKapacitor} = this.props
|
||||
const {sources, source, deleteKapacitor} = this.props
|
||||
|
||||
return (
|
||||
<div className="page" id="manage-sources-page">
|
||||
|
@ -67,11 +69,11 @@ class ManageSources extends Component {
|
|||
<FancyScrollbar className="page-contents">
|
||||
<div className="container-fluid">
|
||||
<InfluxTable
|
||||
handleDeleteSource={this.handleDeleteSource}
|
||||
source={source}
|
||||
sources={sources}
|
||||
setActiveKapacitor={setActiveKapacitor}
|
||||
handleDeleteKapacitor={deleteKapacitor}
|
||||
handleDeleteSource={this.handleDeleteSource}
|
||||
setActiveKapacitor={this.handleSetActiveKapacitor}
|
||||
/>
|
||||
<p className="version-number">
|
||||
Chronograf Version: {V_NUMBER}
|
||||
|
|
|
@ -47,6 +47,22 @@ export const fixtureStatusPageCells = [
|
|||
y: 5,
|
||||
w: 6.5,
|
||||
h: 6,
|
||||
queries: [
|
||||
{
|
||||
query: '',
|
||||
queryConfig: {
|
||||
database: '',
|
||||
measurement: '',
|
||||
retentionPolicy: '',
|
||||
fields: [],
|
||||
tags: {},
|
||||
groupBy: {},
|
||||
areTagsAccepted: false,
|
||||
rawText: null,
|
||||
range: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
i: 'news-feed',
|
||||
|
@ -57,6 +73,22 @@ export const fixtureStatusPageCells = [
|
|||
y: 5,
|
||||
w: 3,
|
||||
h: 6,
|
||||
queries: [
|
||||
{
|
||||
query: '',
|
||||
queryConfig: {
|
||||
database: '',
|
||||
measurement: '',
|
||||
retentionPolicy: '',
|
||||
fields: [],
|
||||
tags: {},
|
||||
groupBy: {},
|
||||
areTagsAccepted: false,
|
||||
rawText: null,
|
||||
range: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
i: 'getting-started',
|
||||
|
@ -67,5 +99,21 @@ export const fixtureStatusPageCells = [
|
|||
y: 5,
|
||||
w: 2.5,
|
||||
h: 6,
|
||||
queries: [
|
||||
{
|
||||
query: '',
|
||||
queryConfig: {
|
||||
database: '',
|
||||
measurement: '',
|
||||
retentionPolicy: '',
|
||||
fields: [],
|
||||
tags: {},
|
||||
groupBy: {},
|
||||
areTagsAccepted: false,
|
||||
rawText: null,
|
||||
range: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
@ -9,16 +9,25 @@
|
|||
.custom-time--container {
|
||||
display: none;
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
top: 35px;
|
||||
right: 0;
|
||||
background: $g5-pepper;
|
||||
border-radius: $radius;
|
||||
padding: 8px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 5px 0.6px rgba(15, 14, 21, 0.2);
|
||||
}
|
||||
.custom-time--wrap,
|
||||
.custom-time--moving-dates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.custom-time--wrap {
|
||||
padding: 8px;
|
||||
align-items: center;
|
||||
border-radius: 0 $radius $radius 0;
|
||||
background: $g5-pepper;
|
||||
}
|
||||
.custom-time--dates {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
@ -30,6 +39,33 @@
|
|||
.custom-time--upper {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.custom-time--shortcuts {
|
||||
@include no-user-select();
|
||||
align-items: stretch;
|
||||
background-color: $g6-smoke;
|
||||
border-radius: $radius 0 0 $radius;
|
||||
}
|
||||
.custom-time--shortcuts-header {
|
||||
white-space: nowrap;
|
||||
padding: 16px;
|
||||
color: $g15-platinum;
|
||||
font-weight: 700;
|
||||
}
|
||||
.custom-time--shortcut {
|
||||
white-space: nowrap;
|
||||
padding: 6px 16px;
|
||||
transition:
|
||||
color 0.25s ease,
|
||||
background-color 0.25s ease;
|
||||
color: $g11-sidewalk;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: $g7-graphite;
|
||||
color: $g17-whisper;
|
||||
}
|
||||
}
|
||||
|
||||
$custom-time-arrow: 28px;
|
||||
$rd-cell-size: 30px;
|
||||
|
@ -222,7 +258,7 @@ $rd-cell-size: 30px;
|
|||
|
||||
.custom-time--container .btn.custom-time--apply {
|
||||
margin-top: 8px;
|
||||
width: 120px;
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
/* Open State */
|
||||
|
|
|
@ -2778,6 +2778,10 @@ eslint-loader@1.6.1:
|
|||
object-assign "^4.0.1"
|
||||
object-hash "^1.1.4"
|
||||
|
||||
eslint-plugin-babel@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-4.1.2.tgz#79202a0e35757dd92780919b2336f1fa2fe53c1e"
|
||||
|
||||
eslint-plugin-prettier@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.1.2.tgz#4b90f4ee7f92bfbe2e926017e1ca40eb628965ea"
|
||||
|
|
Loading…
Reference in New Issue