Merge branch 'master' into presentational-page-components

pull/10616/head
Alex P 2018-06-20 11:36:35 -07:00
commit dda369c24b
86 changed files with 2005 additions and 1459 deletions

View File

@ -22,6 +22,7 @@
1. [#3649](https://github.com/influxdata/chronograf/pull/3649): Fix erroneous icons in Date Picker widget 1. [#3649](https://github.com/influxdata/chronograf/pull/3649): Fix erroneous icons in Date Picker widget
1. [#3697](https://github.com/influxdata/chronograf/pull/3697): Fix allowing hyphens in basepath 1. [#3697](https://github.com/influxdata/chronograf/pull/3697): Fix allowing hyphens in basepath
1. [#3698](https://github.com/influxdata/chronograf/pull/3698): Fix error in cell when tempVar returns no values 1. [#3698](https://github.com/influxdata/chronograf/pull/3698): Fix error in cell when tempVar returns no values
1. [#3733](https://github.com/influxdata/chronograf/pull/3733): Change arrows in table columns so that ascending sort points up and descending points down
## v1.5.0.0 [2018-05-15-RC] ## v1.5.0.0 [2018-05-15-RC]

View File

@ -341,6 +341,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Selected: v.Selected, Selected: v.Selected,
Type: v.Type, Type: v.Type,
Value: v.Value, Value: v.Value,
Key: v.Key,
} }
} }
@ -522,6 +523,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
Selected: v.Selected, Selected: v.Selected,
Type: v.Type, Type: v.Type,
Value: v.Value, Value: v.Value,
Key: v.Key,
} }
} }

View File

@ -640,6 +640,7 @@ type TemplateValue struct {
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"` Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"`
Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
} }
func (m *TemplateValue) Reset() { *m = TemplateValue{} } func (m *TemplateValue) Reset() { *m = TemplateValue{} }
@ -668,6 +669,13 @@ func (m *TemplateValue) GetSelected() bool {
return false return false
} }
func (m *TemplateValue) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
type TemplateQuery struct { type TemplateQuery struct {
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"` Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"`
@ -1388,111 +1396,111 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{ var fileDescriptorInternal = []byte{
// 1685 bytes of a gzipped FileDescriptorProto // 1691 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x8f, 0xe3, 0x48, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdd, 0x8f, 0xe3, 0x48,
0x15, 0x96, 0x93, 0x38, 0x89, 0x4f, 0xd2, 0xbd, 0x2d, 0x33, 0xda, 0x35, 0x0b, 0x42, 0xc1, 0xe2, 0x11, 0x97, 0x13, 0x3b, 0x89, 0x2b, 0x99, 0xb9, 0x91, 0x59, 0xdd, 0x99, 0x03, 0xa1, 0x60, 0xf1,
0xd2, 0x5c, 0x76, 0x58, 0xf5, 0x0a, 0x09, 0xad, 0x76, 0x57, 0xea, 0xcb, 0xce, 0xd0, 0x73, 0xed, 0x31, 0x7c, 0xdc, 0x72, 0x9a, 0x13, 0x12, 0x3a, 0xdd, 0x9d, 0x34, 0x1f, 0xb7, 0xcb, 0xec, 0xe7,
0xa9, 0x74, 0x0f, 0x4f, 0x68, 0x55, 0xb1, 0x2b, 0x49, 0x69, 0x1d, 0xdb, 0x94, 0xcb, 0xdd, 0x6d, 0x6c, 0x67, 0x76, 0x78, 0x42, 0xa7, 0x4e, 0xdc, 0x49, 0x5a, 0xe7, 0xd8, 0xa6, 0xdd, 0x9e, 0x19,
0x9e, 0xf9, 0x1d, 0x48, 0x48, 0xf0, 0x8e, 0x10, 0x8f, 0x48, 0xbc, 0xf3, 0x03, 0xf8, 0x2b, 0xbc, 0xf3, 0xcc, 0xdf, 0x81, 0x84, 0x04, 0xef, 0x08, 0xf1, 0x88, 0xc4, 0x3b, 0x7f, 0x00, 0xff, 0x0a,
0xa2, 0x53, 0x17, 0xa7, 0xdc, 0x9d, 0x19, 0x0d, 0x12, 0xda, 0xb7, 0xfa, 0xce, 0x39, 0x39, 0x55, 0xaf, 0xa8, 0xfa, 0xc3, 0x69, 0xcf, 0x64, 0x57, 0x8b, 0x84, 0xee, 0xad, 0x7f, 0x55, 0x95, 0xea,
0x75, 0x2e, 0x5f, 0x1d, 0x07, 0xf6, 0x79, 0x2e, 0x99, 0xc8, 0x69, 0xf6, 0xb0, 0x14, 0x85, 0x2c, 0xea, 0xea, 0xaa, 0x5f, 0x97, 0x03, 0xfb, 0x3c, 0x97, 0x4c, 0xe4, 0x34, 0x7b, 0x58, 0x8a, 0x42,
0xc2, 0xb1, 0xc5, 0xf1, 0x1f, 0xfa, 0x30, 0x9c, 0x17, 0xb5, 0x48, 0x58, 0xb8, 0x0f, 0xbd, 0xf3, 0x16, 0xd1, 0xc8, 0xe2, 0xe4, 0x0f, 0x7d, 0x18, 0xcc, 0x8a, 0x5a, 0x2c, 0x58, 0xb4, 0x0f, 0xbd,
0xb3, 0xc8, 0x9b, 0x79, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x0b, 0x43, 0x18, 0xbc, 0xa0, 0x1b, 0x16, 0xf3, 0xb3, 0xd8, 0x9b, 0x7a, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x8b, 0x22, 0xf0, 0x5f, 0xd0, 0x0d,
0xf5, 0x66, 0xde, 0x61, 0x40, 0xd4, 0x1a, 0x65, 0x97, 0x4d, 0xc9, 0xa2, 0xbe, 0x96, 0xe1, 0x3a, 0x8b, 0x7b, 0x53, 0xef, 0x30, 0x24, 0x6a, 0x8d, 0xb2, 0xcb, 0xa6, 0x64, 0x71, 0x5f, 0xcb, 0x70,
0xfc, 0x10, 0xc6, 0x57, 0x15, 0x7a, 0xdb, 0xb0, 0x68, 0xa0, 0xe4, 0x2d, 0x46, 0xdd, 0x05, 0xad, 0x1d, 0x7d, 0x08, 0xa3, 0xd7, 0x15, 0x7a, 0xdb, 0xb0, 0xd8, 0x57, 0xf2, 0x16, 0xa3, 0xee, 0x82,
0xaa, 0x9b, 0x42, 0xa4, 0x91, 0xaf, 0x75, 0x16, 0x87, 0x07, 0xd0, 0xbf, 0x22, 0xcf, 0xa2, 0xa1, 0x56, 0xd5, 0x4d, 0x21, 0xd2, 0x38, 0xd0, 0x3a, 0x8b, 0xa3, 0x03, 0xe8, 0xbf, 0x26, 0xcf, 0xe2,
0x12, 0xe3, 0x32, 0x8c, 0x60, 0x74, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x34, 0x9a, 0x79, 0x87, 0x63, 0x81, 0x12, 0xe3, 0x32, 0x8a, 0x61, 0x78, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x3c, 0x9c, 0x7a, 0x87,
0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0xc6, 0xda, 0x8f, 0xc5, 0xe1, 0x43, 0x23, 0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0x47, 0xda, 0x8f, 0xc5, 0xd1,
0x08, 0xcf, 0xf3, 0x8a, 0x25, 0xb5, 0x60, 0xf3, 0xaf, 0x79, 0xf9, 0x9a, 0x09, 0xbe, 0x6c, 0xa2, 0x43, 0x88, 0xce, 0xf3, 0x8a, 0x2d, 0x6a, 0xc1, 0x66, 0x5f, 0xf3, 0xf2, 0x8a, 0x09, 0xbe, 0x6c,
0x40, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x0c, 0x63, 0xe2, 0x50, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x8c,
0x98, 0xce, 0xd7, 0x54, 0xb0, 0x74, 0xce, 0x12, 0xc1, 0x64, 0x34, 0x51, 0xea, 0x8e, 0x0c, 0x6d, 0x12, 0x98, 0xcc, 0xd6, 0x54, 0xb0, 0x74, 0xc6, 0x16, 0x82, 0xc9, 0x78, 0xac, 0xd4, 0x1d, 0x19,
0x5e, 0x8a, 0x15, 0xcd, 0xf9, 0xef, 0xa9, 0xe4, 0x45, 0x1e, 0x4d, 0xb5, 0x8d, 0x2b, 0xc3, 0x28, 0xda, 0xbc, 0x14, 0x2b, 0x9a, 0xf3, 0xdf, 0x53, 0xc9, 0x8b, 0x3c, 0x9e, 0x68, 0x1b, 0x57, 0x86,
0x91, 0x22, 0x63, 0xd1, 0x9e, 0x8e, 0x12, 0xae, 0xc3, 0xef, 0x42, 0x60, 0x2e, 0x43, 0x2e, 0xa2, 0x59, 0x22, 0x45, 0xc6, 0xe2, 0x3d, 0x9d, 0x25, 0x5c, 0x47, 0xdf, 0x85, 0xd0, 0x1c, 0x86, 0x5c,
0x7d, 0xa5, 0xd8, 0x0a, 0xe2, 0xbf, 0x7b, 0x10, 0x9c, 0xd1, 0x6a, 0xbd, 0x28, 0xa8, 0x48, 0xdf, 0xc4, 0xfb, 0x4a, 0xb1, 0x15, 0x24, 0x7f, 0xf7, 0x20, 0x3c, 0xa3, 0xd5, 0x7a, 0x5e, 0x50, 0x91,
0x29, 0x13, 0x1f, 0x81, 0x9f, 0xb0, 0x2c, 0xab, 0xa2, 0xfe, 0xac, 0x7f, 0x38, 0x39, 0xfa, 0xe0, 0xbe, 0xd3, 0x4d, 0x7c, 0x04, 0xc1, 0x82, 0x65, 0x59, 0x15, 0xf7, 0xa7, 0xfd, 0xc3, 0xf1, 0xd1,
0x61, 0x9b, 0xe2, 0xd6, 0xcf, 0x29, 0xcb, 0x32, 0xa2, 0xad, 0xc2, 0x8f, 0x21, 0x90, 0x6c, 0x53, 0x07, 0x0f, 0xdb, 0x2b, 0x6e, 0xfd, 0x9c, 0xb2, 0x2c, 0x23, 0xda, 0x2a, 0xfa, 0x18, 0x42, 0xc9,
0x66, 0x54, 0xb2, 0x2a, 0x1a, 0xa8, 0x9f, 0x84, 0xdb, 0x9f, 0x5c, 0x1a, 0x15, 0xd9, 0x1a, 0xdd, 0x36, 0x65, 0x46, 0x25, 0xab, 0x62, 0x5f, 0xfd, 0x24, 0xda, 0xfe, 0xe4, 0xd2, 0xa8, 0xc8, 0xd6,
0xbb, 0xa8, 0x7f, 0xff, 0xa2, 0xf1, 0xbf, 0x07, 0xb0, 0xd7, 0xd9, 0x2e, 0x9c, 0x82, 0x77, 0xab, 0xe8, 0xde, 0x41, 0x83, 0xfb, 0x07, 0x4d, 0xfe, 0xed, 0xc3, 0x5e, 0x67, 0xbb, 0x68, 0x02, 0xde,
0x4e, 0xee, 0x13, 0xef, 0x16, 0x51, 0xa3, 0x4e, 0xed, 0x13, 0xaf, 0x41, 0x74, 0xa3, 0x2a, 0xc7, 0xad, 0x8a, 0x3c, 0x20, 0xde, 0x2d, 0xa2, 0x46, 0x45, 0x1d, 0x10, 0xaf, 0x41, 0x74, 0xa3, 0x2a,
0x27, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x17, 0x9f, 0x78, 0xeb, 0xf0, 0x27, 0x30, 0xfa, 0x5d, 0xcd, 0x27, 0x20, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x97, 0x80, 0x78, 0xeb, 0xe8, 0x27, 0x30, 0xfc, 0x5d,
0x04, 0x67, 0x55, 0xe4, 0xab, 0xd3, 0xbd, 0xb7, 0x3d, 0xdd, 0xab, 0x9a, 0x89, 0x86, 0x58, 0x3d, 0xcd, 0x04, 0x67, 0x55, 0x1c, 0xa8, 0xe8, 0xde, 0xdb, 0x46, 0xf7, 0xaa, 0x66, 0xa2, 0x21, 0x56,
0x46, 0x43, 0xd5, 0x9a, 0x2e, 0x1c, 0xb5, 0x46, 0x99, 0xc4, 0xba, 0x1c, 0x69, 0x19, 0xae, 0x4d, 0x8f, 0xd9, 0x50, 0xb5, 0xa6, 0x0b, 0x47, 0xad, 0x51, 0x26, 0xb1, 0x2e, 0x87, 0x5a, 0x86, 0x6b,
0x14, 0x75, 0xb5, 0x60, 0x14, 0x7f, 0x09, 0x03, 0x7a, 0xcb, 0xaa, 0x28, 0x50, 0xfe, 0xbf, 0xff, 0x93, 0x45, 0x5d, 0x2d, 0x98, 0xc5, 0x5f, 0x82, 0x4f, 0x6f, 0x59, 0x15, 0x87, 0xca, 0xff, 0xf7,
0x86, 0x80, 0x3d, 0x3c, 0xbe, 0x65, 0xd5, 0x97, 0xb9, 0x14, 0x0d, 0x51, 0xe6, 0xe1, 0x8f, 0x61, 0xdf, 0x90, 0xb0, 0x87, 0xc7, 0xb7, 0xac, 0xfa, 0x32, 0x97, 0xa2, 0x21, 0xca, 0x3c, 0xfa, 0x31,
0x98, 0x14, 0x59, 0x21, 0xaa, 0x08, 0xee, 0x1e, 0xec, 0x14, 0xe5, 0xc4, 0xa8, 0xc3, 0x43, 0x18, 0x0c, 0x16, 0x45, 0x56, 0x88, 0x2a, 0x86, 0xbb, 0x81, 0x9d, 0xa2, 0x9c, 0x18, 0x75, 0x74, 0x08,
0x66, 0x6c, 0xc5, 0xf2, 0x54, 0xd5, 0xcd, 0xe4, 0xe8, 0x60, 0x6b, 0xf8, 0x4c, 0xc9, 0x89, 0xd1, 0x83, 0x8c, 0xad, 0x58, 0x9e, 0xaa, 0xba, 0x19, 0x1f, 0x1d, 0x6c, 0x0d, 0x9f, 0x29, 0x39, 0x31,
0x87, 0x9f, 0xc2, 0x54, 0xd2, 0x45, 0xc6, 0x5e, 0x96, 0x18, 0xc5, 0x4a, 0xd5, 0xd0, 0xe4, 0xe8, 0xfa, 0xe8, 0x53, 0x98, 0x48, 0x3a, 0xcf, 0xd8, 0xcb, 0x12, 0xb3, 0x58, 0xa9, 0x1a, 0x1a, 0x1f,
0x7d, 0x27, 0x1f, 0x8e, 0x96, 0x74, 0x6c, 0xc3, 0xcf, 0x60, 0xba, 0xe4, 0x2c, 0x4b, 0xed, 0x6f, 0xbd, 0xef, 0xdc, 0x87, 0xa3, 0x25, 0x1d, 0xdb, 0xe8, 0x33, 0x98, 0x2c, 0x39, 0xcb, 0x52, 0xfb,
0xf7, 0xd4, 0xa1, 0xa2, 0xed, 0x6f, 0x09, 0xcb, 0xe9, 0x06, 0x7f, 0xf1, 0x08, 0xcd, 0x48, 0xc7, 0xdb, 0x3d, 0x15, 0x54, 0xbc, 0xfd, 0x2d, 0x61, 0x39, 0xdd, 0xe0, 0x2f, 0x1e, 0xa1, 0x19, 0xe9,
0x3a, 0xfc, 0x1e, 0x80, 0xe4, 0x1b, 0xf6, 0xa8, 0x10, 0x1b, 0x2a, 0x4d, 0x19, 0x3a, 0x92, 0xf0, 0x58, 0x47, 0xdf, 0x03, 0x90, 0x7c, 0xc3, 0x1e, 0x15, 0x62, 0x43, 0xa5, 0x29, 0x43, 0x47, 0x12,
0x73, 0xd8, 0x4b, 0x59, 0xc2, 0x37, 0x34, 0xbb, 0xc8, 0x68, 0xc2, 0xaa, 0xe8, 0x3d, 0x75, 0x34, 0x7d, 0x0e, 0x7b, 0x29, 0x5b, 0xf0, 0x0d, 0xcd, 0x2e, 0x32, 0xba, 0x60, 0x55, 0xfc, 0x9e, 0x0a,
0xb7, 0xba, 0x5c, 0x35, 0xe9, 0x5a, 0x7f, 0xf8, 0x18, 0x82, 0x36, 0x7c, 0xd8, 0xdf, 0x5f, 0xb3, 0xcd, 0xad, 0x2e, 0x57, 0x4d, 0xba, 0xd6, 0x1f, 0x3e, 0x86, 0xb0, 0x4d, 0x1f, 0xf6, 0xf7, 0xd7,
0x46, 0x15, 0x43, 0x40, 0x70, 0x19, 0xfe, 0x00, 0xfc, 0x6b, 0x9a, 0xd5, 0xba, 0x90, 0x27, 0x47, 0xac, 0x51, 0xc5, 0x10, 0x12, 0x5c, 0x46, 0x3f, 0x80, 0xe0, 0x9a, 0x66, 0xb5, 0x2e, 0xe4, 0xf1,
0xfb, 0x5b, 0xaf, 0xc7, 0xb7, 0xbc, 0x22, 0x5a, 0xf9, 0x69, 0xef, 0x57, 0x5e, 0xfc, 0x18, 0xf6, 0xd1, 0xfe, 0xd6, 0xeb, 0xf1, 0x2d, 0xaf, 0x88, 0x56, 0x7e, 0xda, 0xfb, 0x95, 0x97, 0x3c, 0x86,
0x3a, 0x1b, 0xe1, 0xc1, 0x79, 0xf5, 0x65, 0xbe, 0x2c, 0x44, 0xc2, 0x52, 0xe5, 0x73, 0x4c, 0x1c, 0xbd, 0xce, 0x46, 0x18, 0x38, 0xaf, 0xbe, 0xcc, 0x97, 0x85, 0x58, 0xb0, 0x54, 0xf9, 0x1c, 0x11,
0x49, 0xf8, 0x3e, 0x0c, 0x53, 0xbe, 0xe2, 0xb2, 0x32, 0xe5, 0x66, 0x50, 0xfc, 0x0f, 0x0f, 0xa6, 0x47, 0x12, 0xbd, 0x0f, 0x83, 0x94, 0xaf, 0xb8, 0xac, 0x4c, 0xb9, 0x19, 0x94, 0xfc, 0xc3, 0x83,
0x6e, 0x34, 0xc3, 0x9f, 0xc2, 0xc1, 0x35, 0x13, 0x92, 0x27, 0x34, 0xbb, 0xe4, 0x1b, 0x86, 0x1b, 0x89, 0x9b, 0xcd, 0xe8, 0xa7, 0x70, 0x70, 0xcd, 0x84, 0xe4, 0x0b, 0x9a, 0x5d, 0xf2, 0x0d, 0xc3,
0xab, 0x9f, 0x8c, 0xc9, 0x3d, 0x79, 0xf8, 0x31, 0x0c, 0xab, 0x42, 0xc8, 0x93, 0x46, 0x55, 0xed, 0x8d, 0xd5, 0x4f, 0x46, 0xe4, 0x9e, 0x3c, 0xfa, 0x18, 0x06, 0x55, 0x21, 0xe4, 0x49, 0xa3, 0xaa,
0xdb, 0xa2, 0x6c, 0xec, 0x90, 0xa7, 0x6e, 0x04, 0x2d, 0x4b, 0x9e, 0xaf, 0x2c, 0x17, 0x5a, 0x1c, 0xf6, 0x6d, 0x59, 0x36, 0x76, 0xc8, 0x53, 0x37, 0x82, 0x96, 0x25, 0xcf, 0x57, 0x96, 0x0b, 0x2d,
0xfe, 0x08, 0xf6, 0x97, 0xfc, 0xf6, 0x11, 0x17, 0x95, 0x3c, 0x2d, 0xb2, 0x7a, 0x93, 0xab, 0x0a, 0x8e, 0x7e, 0x04, 0xfb, 0x4b, 0x7e, 0xfb, 0x88, 0x8b, 0x4a, 0x9e, 0x16, 0x59, 0xbd, 0xc9, 0x55,
0x1e, 0x93, 0x3b, 0xd2, 0x27, 0x83, 0xb1, 0x77, 0xd0, 0x7b, 0x32, 0x18, 0xfb, 0x07, 0xc3, 0xb8, 0x05, 0x8f, 0xc8, 0x1d, 0xe9, 0x13, 0x7f, 0xe4, 0x1d, 0xf4, 0x9e, 0xf8, 0xa3, 0xe0, 0x60, 0x90,
0x84, 0xfd, 0xee, 0x4e, 0xd8, 0x96, 0xf6, 0x10, 0x8a, 0x13, 0x74, 0x78, 0x3b, 0xb2, 0x70, 0x06, 0x94, 0xb0, 0xdf, 0xdd, 0x09, 0xdb, 0xd2, 0x06, 0xa1, 0x38, 0x41, 0xa7, 0xb7, 0x23, 0x8b, 0xa6,
0x93, 0x94, 0x57, 0x65, 0x46, 0x1b, 0x87, 0x36, 0x5c, 0x11, 0x72, 0xe0, 0x35, 0xaf, 0xf8, 0x22, 0x30, 0x4e, 0x79, 0x55, 0x66, 0xb4, 0x71, 0x68, 0xc3, 0x15, 0x21, 0x07, 0x5e, 0xf3, 0x8a, 0xcf,
0xd3, 0x54, 0x3e, 0x26, 0x16, 0xc6, 0x2b, 0xf0, 0x55, 0x59, 0x3b, 0x24, 0x14, 0x58, 0x12, 0x52, 0x33, 0x4d, 0xe5, 0x23, 0x62, 0x61, 0xb2, 0x82, 0x40, 0x95, 0xb5, 0x43, 0x42, 0xa1, 0x25, 0x21,
0xd4, 0xdf, 0x73, 0xa8, 0xff, 0x00, 0xfa, 0xbf, 0x66, 0xb7, 0xe6, 0x35, 0xc0, 0x65, 0x4b, 0x55, 0x45, 0xfd, 0x3d, 0x87, 0xfa, 0x0f, 0xa0, 0xff, 0x6b, 0x76, 0x6b, 0x5e, 0x03, 0x5c, 0xb6, 0x54,
0x03, 0x87, 0xaa, 0x1e, 0x80, 0xff, 0x5a, 0xa5, 0x5d, 0x53, 0x88, 0x06, 0xf1, 0x17, 0x30, 0xd4, 0xe5, 0x3b, 0x54, 0xf5, 0x00, 0x82, 0x2b, 0x75, 0xed, 0x9a, 0x42, 0x34, 0x48, 0xbe, 0x80, 0x81,
0x6d, 0xd1, 0x7a, 0xf6, 0x1c, 0xcf, 0x33, 0x98, 0xbc, 0x14, 0x9c, 0xe5, 0x52, 0x93, 0x8f, 0xb9, 0x6e, 0x8b, 0xd6, 0xb3, 0xe7, 0x78, 0x9e, 0xc2, 0xf8, 0xa5, 0xe0, 0x2c, 0x97, 0x9a, 0x7c, 0xcc,
0x82, 0x23, 0x8a, 0xff, 0xe6, 0xc1, 0x40, 0x65, 0x29, 0x86, 0x69, 0xc6, 0x56, 0x34, 0x69, 0x4e, 0x11, 0x1c, 0x51, 0xf2, 0x37, 0x0f, 0x7c, 0x75, 0x4b, 0x09, 0x4c, 0x32, 0xb6, 0xa2, 0x8b, 0xe6,
0x8a, 0x3a, 0x4f, 0xab, 0xc8, 0x9b, 0xf5, 0x0f, 0xfb, 0xa4, 0x23, 0xc3, 0xf2, 0x58, 0x68, 0x6d, 0xa4, 0xa8, 0xf3, 0xb4, 0x8a, 0xbd, 0x69, 0xff, 0xb0, 0x4f, 0x3a, 0x32, 0x2c, 0x8f, 0xb9, 0xd6,
0x6f, 0xd6, 0x3f, 0x0c, 0x88, 0x41, 0x78, 0xb4, 0x8c, 0x2e, 0x58, 0x66, 0xae, 0xa0, 0x01, 0x5a, 0xf6, 0xa6, 0xfd, 0xc3, 0x90, 0x18, 0x84, 0xa1, 0x65, 0x74, 0xce, 0x32, 0x73, 0x04, 0x0d, 0xd0,
0x97, 0x82, 0x2d, 0xf9, 0xad, 0xb9, 0x86, 0x41, 0x28, 0xaf, 0xea, 0x25, 0xca, 0xf5, 0x4d, 0x0c, 0xba, 0x14, 0x6c, 0xc9, 0x6f, 0xcd, 0x31, 0x0c, 0x42, 0x79, 0x55, 0x2f, 0x51, 0xae, 0x4f, 0x62,
0xc2, 0x0b, 0x2c, 0x68, 0xd5, 0x32, 0x12, 0xae, 0xd1, 0x73, 0x95, 0xd0, 0xcc, 0x52, 0x92, 0x06, 0x10, 0x1e, 0x60, 0x4e, 0xab, 0x96, 0x91, 0x70, 0x8d, 0x9e, 0xab, 0x05, 0xcd, 0x2c, 0x25, 0x69,
0xf1, 0x3f, 0x3d, 0x7c, 0xc8, 0x34, 0xc5, 0xde, 0x8b, 0xf0, 0xb7, 0x61, 0x8c, 0xf4, 0xfb, 0xd5, 0x90, 0xfc, 0xd3, 0xc3, 0x87, 0x4c, 0x53, 0xec, 0xbd, 0x0c, 0x7f, 0x1b, 0x46, 0x48, 0xbf, 0x5f,
0x35, 0x15, 0xe6, 0xc2, 0x23, 0xc4, 0xaf, 0xa9, 0x08, 0x7f, 0x01, 0x43, 0xd5, 0x1c, 0x3b, 0xe8, 0x5d, 0x53, 0x61, 0x0e, 0x3c, 0x44, 0x7c, 0x45, 0x45, 0xf4, 0x0b, 0x18, 0xa8, 0xe6, 0xd8, 0x41,
0xde, 0xba, 0x53, 0x51, 0x25, 0xc6, 0xac, 0x25, 0xc4, 0x81, 0x43, 0x88, 0xed, 0x65, 0x7d, 0xf7, 0xf7, 0xd6, 0x9d, 0xca, 0x2a, 0x31, 0x66, 0x2d, 0x21, 0xfa, 0x0e, 0x21, 0xb6, 0x87, 0x0d, 0xdc,
0xb2, 0x1f, 0x81, 0x8f, 0xcc, 0xda, 0xa8, 0xd3, 0xef, 0xf4, 0xac, 0xf9, 0x57, 0x5b, 0xc5, 0x57, 0xc3, 0x7e, 0x04, 0x01, 0x32, 0x6b, 0xa3, 0xa2, 0xdf, 0xe9, 0x59, 0xf3, 0xaf, 0xb6, 0x4a, 0x56,
0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xc0, 0x34, 0x36, 0x36, 0x47, 0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xd0, 0x34, 0x36, 0x36, 0x47,
0xc5, 0x32, 0x96, 0x48, 0x96, 0x9a, 0xaa, 0x6b, 0x71, 0xfc, 0x27, 0x6f, 0xeb, 0x57, 0xed, 0x87, 0xc5, 0x32, 0xb6, 0x90, 0x2c, 0x35, 0x55, 0xd7, 0x62, 0x4b, 0x16, 0x7e, 0x4b, 0x16, 0xc9, 0x9f,
0x25, 0x9a, 0x14, 0x9b, 0x0d, 0xcd, 0x53, 0xe3, 0xda, 0x42, 0x8c, 0x5b, 0xba, 0x30, 0xae, 0x7b, 0xbc, 0xed, 0x4e, 0x2a, 0x02, 0x2c, 0xda, 0x45, 0xb1, 0xd9, 0xd0, 0x3c, 0x35, 0x9b, 0x59, 0x88,
0xe9, 0x02, 0xb1, 0x28, 0x4d, 0x06, 0x7b, 0xa2, 0xc4, 0xda, 0xd9, 0x30, 0x5a, 0xd5, 0x82, 0x6d, 0x99, 0x4c, 0xe7, 0x66, 0xb3, 0x5e, 0x3a, 0x47, 0x2c, 0x4a, 0x73, 0xa7, 0x3d, 0x51, 0x62, 0x35,
0x58, 0x2e, 0x4d, 0x08, 0x5c, 0x51, 0xf8, 0x01, 0x8c, 0x24, 0x5d, 0x7d, 0x85, 0xf4, 0x64, 0x32, 0x6d, 0x18, 0xad, 0x6a, 0xc1, 0x36, 0x2c, 0x97, 0x66, 0x17, 0x57, 0x14, 0x7d, 0x00, 0x43, 0x49,
0x29, 0xe9, 0xea, 0x29, 0x6b, 0xc2, 0xef, 0x40, 0xa0, 0xf8, 0x52, 0xa9, 0x74, 0x3a, 0xc7, 0x4a, 0x57, 0x5f, 0x61, 0x0c, 0xe6, 0x6e, 0x25, 0x5d, 0x3d, 0x65, 0x4d, 0xf4, 0x1d, 0x08, 0x15, 0x83,
0xf0, 0x94, 0x35, 0xf1, 0x5f, 0x7b, 0x30, 0x9c, 0x33, 0x71, 0xcd, 0xc4, 0x3b, 0xbd, 0xd0, 0xee, 0x2a, 0x95, 0xbe, 0xe0, 0x91, 0x12, 0x3c, 0x65, 0x4d, 0xf2, 0xd7, 0x1e, 0x0c, 0x66, 0x4c, 0x5c,
0x5c, 0xd4, 0x7f, 0xcb, 0x5c, 0x34, 0xd8, 0x3d, 0x17, 0xf9, 0xdb, 0xb9, 0xe8, 0x01, 0xf8, 0x73, 0x33, 0xf1, 0x4e, 0x6f, 0xb6, 0x3b, 0x29, 0xf5, 0xdf, 0x32, 0x29, 0xf9, 0xbb, 0x27, 0xa5, 0x60,
0x91, 0x9c, 0x9f, 0xa9, 0x13, 0xf5, 0x89, 0x06, 0x58, 0x8d, 0xc7, 0x89, 0xe4, 0xd7, 0xcc, 0x0c, 0x3b, 0x29, 0x3d, 0x80, 0x60, 0x26, 0x16, 0xe7, 0x67, 0x2a, 0xa2, 0x3e, 0xd1, 0x00, 0xeb, 0xf3,
0x4b, 0x06, 0xdd, 0x7b, 0xb8, 0xc7, 0x3b, 0x26, 0x94, 0xff, 0x75, 0x66, 0xb2, 0x2d, 0x0a, 0x4e, 0x78, 0x21, 0xf9, 0x35, 0x33, 0xe3, 0x93, 0x41, 0xf7, 0x9e, 0xf2, 0xd1, 0x8e, 0x99, 0xe5, 0x7f,
0x8b, 0xc6, 0x30, 0xc5, 0xc1, 0x29, 0xa5, 0x92, 0x3e, 0x99, 0xbf, 0x7c, 0x61, 0xa7, 0x25, 0x57, 0x9d, 0xa2, 0x6c, 0xd3, 0x82, 0xd3, 0xb4, 0x09, 0x4c, 0x70, 0x94, 0x4a, 0xa9, 0xa4, 0x4f, 0x66,
0x16, 0xff, 0xd1, 0x83, 0xe1, 0x33, 0xda, 0x14, 0xb5, 0xbc, 0x57, 0xed, 0x33, 0x98, 0x1c, 0x97, 0x2f, 0x5f, 0xd8, 0xf9, 0xc9, 0x95, 0x25, 0x7f, 0xf4, 0x60, 0xf0, 0x8c, 0x36, 0x45, 0x2d, 0xef,
0x65, 0xc6, 0x93, 0x4e, 0x87, 0x3b, 0x22, 0xb4, 0x78, 0xee, 0xe4, 0x51, 0xc7, 0xd0, 0x15, 0xe1, 0xd5, 0xff, 0x14, 0xc6, 0xc7, 0x65, 0x99, 0xf1, 0x45, 0xa7, 0xe7, 0x1d, 0x11, 0x5a, 0x3c, 0x77,
0x83, 0x72, 0xaa, 0x86, 0x20, 0x3d, 0xd1, 0x38, 0x0f, 0x8a, 0x9e, 0x7d, 0x94, 0x12, 0x83, 0x7d, 0xee, 0x51, 0xe7, 0xd0, 0x15, 0xe1, 0x13, 0x73, 0xaa, 0xc6, 0x22, 0x3d, 0xe3, 0x38, 0x4f, 0x8c,
0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xaa, 0x63, 0xd2, 0xe2, 0xf8, 0x5f, 0x3d, 0x18, 0x7c, 0x9e, 0x86, 0x94, 0x12, 0x93, 0x7d, 0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xab, 0x23, 0xd2,
0x53, 0x83, 0xcb, 0x14, 0x3c, 0x6e, 0x8a, 0xca, 0xe3, 0xed, 0x18, 0x33, 0x72, 0xc6, 0x98, 0x08, 0xe2, 0xe4, 0x5f, 0x3d, 0xf0, 0xbf, 0xa9, 0x51, 0x66, 0x02, 0x1e, 0x37, 0x45, 0xe5, 0xf1, 0x76,
0x46, 0x8d, 0xa0, 0xf9, 0x8a, 0x55, 0xd1, 0x58, 0xb1, 0x98, 0x85, 0x4a, 0xa3, 0xfa, 0x55, 0xcf, 0xb0, 0x19, 0x3a, 0x83, 0x4d, 0x0c, 0xc3, 0x46, 0xd0, 0x7c, 0xc5, 0xaa, 0x78, 0xa4, 0x78, 0xcd,
0x2f, 0x01, 0xb1, 0xb0, 0xed, 0x3f, 0x70, 0xfa, 0xef, 0xe7, 0x66, 0xd4, 0x99, 0xdc, 0x1d, 0x0e, 0x42, 0xa5, 0x51, 0x1d, 0xac, 0x27, 0x9a, 0x90, 0x58, 0xd8, 0x76, 0x24, 0x38, 0x1d, 0xf9, 0x73,
0x76, 0x4d, 0x38, 0xff, 0xbf, 0x57, 0xfb, 0x3f, 0x1e, 0xf8, 0x6d, 0xf3, 0x9e, 0x76, 0x9b, 0xf7, 0x33, 0xfc, 0x8c, 0xef, 0x8e, 0x0b, 0xbb, 0x66, 0x9e, 0xff, 0xdf, 0x3b, 0xfe, 0x1f, 0x0f, 0x82,
0x74, 0xdb, 0xbc, 0x67, 0x27, 0xb6, 0x79, 0xcf, 0x4e, 0x10, 0x93, 0x0b, 0xdb, 0xbc, 0xe4, 0x02, 0xb6, 0x79, 0x4f, 0xbb, 0xcd, 0x7b, 0xba, 0x6d, 0xde, 0xb3, 0x13, 0xdb, 0xbc, 0x67, 0x27, 0x88,
0x93, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0xac, 0x06, 0xa4, 0xc5, 0x58, 0xf1, 0xbf, 0x59, 0xc9, 0x85, 0x6d, 0x5e, 0x72, 0x81, 0x97, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0x5b, 0x0d,
0x33, 0x61, 0x42, 0x1d, 0x10, 0x83, 0xb0, 0x3f, 0x9e, 0x29, 0x62, 0xd3, 0xc1, 0xd5, 0x20, 0xfc, 0x49, 0x8b, 0xb1, 0xe2, 0x7f, 0xb3, 0x66, 0xc2, 0xa4, 0x3a, 0x24, 0x06, 0x61, 0x7f, 0x3c, 0x53,
0x21, 0xf8, 0x04, 0x83, 0xa7, 0x22, 0xdc, 0xc9, 0x8b, 0x12, 0x13, 0xad, 0x45, 0xa7, 0xfa, 0x03, 0x54, 0xa7, 0x93, 0xab, 0x41, 0xf4, 0x43, 0x08, 0x08, 0x26, 0x4f, 0x65, 0xb8, 0x73, 0x2f, 0x4a,
0xc8, 0x34, 0x8a, 0xfd, 0x1c, 0xfa, 0x19, 0x0c, 0xe7, 0x6b, 0xbe, 0x94, 0x76, 0x60, 0xfc, 0x96, 0x4c, 0xb4, 0x16, 0x9d, 0xea, 0x4f, 0x22, 0xd3, 0x28, 0xf6, 0x03, 0xe9, 0x67, 0x30, 0x98, 0xad,
0x43, 0x8c, 0x7c, 0xc3, 0x94, 0x8e, 0x18, 0x93, 0xf8, 0x15, 0x04, 0xad, 0x70, 0x7b, 0x1c, 0xcf, 0xf9, 0x52, 0xda, 0x11, 0xf2, 0x5b, 0x0e, 0x55, 0xf2, 0x0d, 0x53, 0x3a, 0x62, 0x4c, 0x92, 0x57,
0x3d, 0x4e, 0x08, 0x83, 0xab, 0x9c, 0x4b, 0x4b, 0x11, 0xb8, 0xc6, 0xcb, 0xbe, 0xaa, 0x69, 0x2e, 0x10, 0xb6, 0xc2, 0x6d, 0x38, 0x9e, 0x1b, 0x4e, 0x04, 0xfe, 0xeb, 0x9c, 0x4b, 0x4b, 0x11, 0xb8,
0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x1c, 0x7f, 0x62, 0x8e, 0x8f, 0xee, 0xae, 0xca, 0x92, 0x09, 0x43, 0xc6, 0xc3, 0xbe, 0xaa, 0x69, 0x2e, 0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x9c, 0x7c, 0x62, 0xc2, 0x47,
0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0x5f, 0x8a, 0x3e, 0xd1, 0x20, 0xfe, 0x2d, 0x04, 0xc7, 0x77, 0xaf, 0xcb, 0x92, 0x09, 0x43, 0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0xdf, 0x8e, 0x3e,
0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0x82, 0xab, 0x46, 0x35, 0x27, 0xc0, 0xf5, 0x96, 0x5a, 0xd1, 0x20, 0xf9, 0x2d, 0x84, 0xc7, 0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0xa6, 0xab, 0x46,
0xfa, 0x77, 0xa8, 0xe5, 0x29, 0x2d, 0xe9, 0xf9, 0x99, 0xaa, 0xf3, 0x3e, 0x31, 0x28, 0xfe, 0xb3, 0x35, 0x11, 0xe0, 0x7a, 0x4b, 0x2d, 0xfd, 0x3b, 0xd4, 0xf2, 0x94, 0x96, 0xf4, 0xfc, 0x4c, 0xd5,
0x07, 0x03, 0xe4, 0x30, 0xc7, 0xf5, 0xe0, 0x6d, 0xfc, 0x77, 0x21, 0x8a, 0x6b, 0x9e, 0x32, 0x61, 0x79, 0x9f, 0x18, 0x94, 0xfc, 0xd9, 0x03, 0x1f, 0x39, 0xcc, 0x71, 0xed, 0xbf, 0x8d, 0xff, 0x2e,
0x2f, 0x67, 0xb1, 0x0a, 0x7a, 0xb2, 0x66, 0xed, 0xa0, 0x60, 0x10, 0xd6, 0x1a, 0x7e, 0x2d, 0xd9, 0x44, 0x71, 0xcd, 0x53, 0x26, 0xec, 0xe1, 0x2c, 0x56, 0x49, 0x5f, 0xac, 0x59, 0x3b, 0x3a, 0x18,
0x5e, 0x72, 0x6a, 0x0d, 0xc5, 0x44, 0x2b, 0x71, 0x18, 0x9c, 0xd7, 0x25, 0x13, 0xc7, 0xe9, 0x86, 0x84, 0xb5, 0x86, 0xdf, 0x4f, 0xb6, 0x97, 0x9c, 0x5a, 0x43, 0x31, 0xd1, 0x4a, 0x1c, 0x0f, 0x67,
0xdb, 0x29, 0xca, 0x91, 0xc4, 0x5f, 0xe8, 0xef, 0xaf, 0x7b, 0x4c, 0xe8, 0xed, 0xfe, 0x56, 0xbb, 0x75, 0xc9, 0xc4, 0x71, 0xba, 0xe1, 0x76, 0xae, 0x72, 0x24, 0xc9, 0x17, 0xfa, 0x8b, 0xec, 0x1e,
0x7b, 0xf2, 0xf8, 0x2f, 0x1e, 0x8c, 0x9e, 0x9b, 0xa9, 0xcd, 0xbd, 0x85, 0xf7, 0xc6, 0x5b, 0xf4, 0x13, 0x7a, 0xbb, 0xbf, 0xde, 0xee, 0x46, 0x9e, 0xfc, 0xc5, 0x83, 0xe1, 0x73, 0x33, 0xc7, 0xb9,
0x3a, 0xb7, 0x38, 0x82, 0x07, 0xd6, 0xa6, 0xb3, 0xbf, 0x8e, 0xc2, 0x4e, 0x9d, 0x89, 0xe8, 0xa0, 0xa7, 0xf0, 0xde, 0x78, 0x8a, 0x5e, 0xe7, 0x14, 0x47, 0xf0, 0xc0, 0xda, 0x74, 0xf6, 0xd7, 0x59,
0x4d, 0xd6, 0xbb, 0x7c, 0x7e, 0x5d, 0x76, 0x6d, 0x76, 0x25, 0xfc, 0x5e, 0x56, 0x66, 0x30, 0xb1, 0xd8, 0xa9, 0x33, 0x19, 0xf5, 0xdb, 0xcb, 0x7a, 0x97, 0x0f, 0xb2, 0xcb, 0xae, 0xcd, 0xae, 0x0b,
0x9f, 0x9d, 0x45, 0x66, 0x1f, 0x26, 0x57, 0x14, 0x1f, 0xc1, 0xf0, 0xb4, 0xc8, 0x97, 0x7c, 0x15, 0xbf, 0x77, 0x2b, 0x53, 0x18, 0xdb, 0x0f, 0xd1, 0x22, 0xb3, 0x0f, 0x93, 0x2b, 0x4a, 0x8e, 0x60,
0x1e, 0xc2, 0xe0, 0xb8, 0x96, 0x6b, 0xe5, 0x71, 0x72, 0xf4, 0xc0, 0x69, 0xfc, 0x5a, 0xae, 0xb5, 0x70, 0x5a, 0xe4, 0x4b, 0xbe, 0x8a, 0x0e, 0xc1, 0x3f, 0xae, 0xe5, 0x5a, 0x79, 0x1c, 0x1f, 0x3d,
0x0d, 0x51, 0x16, 0xf1, 0x67, 0x00, 0x5b, 0x19, 0xbe, 0x2e, 0xdb, 0x6c, 0xbc, 0x60, 0x37, 0x58, 0x70, 0x1a, 0xbf, 0x96, 0x6b, 0x6d, 0x43, 0x94, 0x45, 0xf2, 0x19, 0xc0, 0x56, 0x86, 0xaf, 0xcb,
0x32, 0x95, 0x19, 0xda, 0x77, 0x68, 0xe2, 0xcf, 0x21, 0x38, 0xa9, 0x79, 0x96, 0x9e, 0xe7, 0xcb, 0xf6, 0x36, 0x5e, 0xb0, 0x1b, 0x2c, 0x99, 0xca, 0x8c, 0xf1, 0x3b, 0x34, 0xc9, 0xe7, 0x10, 0x9e,
0x02, 0xa9, 0xe3, 0x35, 0x13, 0xd5, 0x36, 0x5f, 0x16, 0x62, 0xb8, 0x91, 0x45, 0xda, 0x1e, 0x32, 0xd4, 0x3c, 0x4b, 0xcf, 0xf3, 0x65, 0x81, 0xd4, 0x71, 0xc5, 0x44, 0xb5, 0xbd, 0x2f, 0x0b, 0x31,
0x68, 0x31, 0x54, 0x7f, 0x6a, 0x7c, 0xf2, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfb, 0xbd, 0xdd, 0xc8, 0x22, 0x6d, 0x0f, 0x19, 0x34, 0x1f, 0xa8, 0xbf, 0x39, 0x3e, 0xf9, 0x6f, 0x00, 0x00,
0x7b, 0xe6, 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x2f, 0x55, 0x31, 0xf8, 0x10, 0x00, 0x00,
} }

View File

@ -100,6 +100,7 @@ message TemplateValue {
string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
string value = 2; // Value is the specific value used to replace a template in an InfluxQL query string value = 2; // Value is the specific value used to replace a template in an InfluxQL query
bool selected = 3; // Selected states that this variable has been picked to use for replacement bool selected = 3; // Selected states that this variable has been picked to use for replacement
string key = 4; // Key is the key for a specific Value if the Template Type is map (optional)
} }
message TemplateQuery { message TemplateQuery {

View File

@ -158,9 +158,10 @@ type Range struct {
// TemplateValue is a value use to replace a template in an InfluxQL query // TemplateValue is a value use to replace a template in an InfluxQL query
type TemplateValue struct { type TemplateValue struct {
Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query
Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant, influxql
Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement
Key string `json:"key,omitempty"` // Key is the key for the Value if the Template Type is 'map'
} }
// TemplateVar is a named variable within an InfluxQL query to be replaced with Values // TemplateVar is a named variable within an InfluxQL query to be replaced with Values
@ -176,7 +177,7 @@ type TemplateID string
type Template struct { type Template struct {
TemplateVar TemplateVar
ID TemplateID `json:"id"` // ID is the unique ID associated with this template ID TemplateID `json:"id"` // ID is the unique ID associated with this template
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, measurements, databases, map, influxql
Label string `json:"label"` // Label is a user-facing description of the Template Label string `json:"label"` // Label is a user-facing description of the Template
Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
} }

View File

@ -72,7 +72,7 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
return strings.Replace(q, t.Var, `"`+t.Values[0].Value+`"`, -1), nil return strings.Replace(q, t.Var, `"`+t.Values[0].Value+`"`, -1), nil
case "tagValue", "timeStamp": case "tagValue", "timeStamp":
return strings.Replace(q, t.Var, `'`+t.Values[0].Value+`'`, -1), nil return strings.Replace(q, t.Var, `'`+t.Values[0].Value+`'`, -1), nil
case "csv", "constant": case "csv", "constant", "influxql":
return strings.Replace(q, t.Var, t.Values[0].Value, -1), nil return strings.Replace(q, t.Var, t.Values[0].Value, -1), nil
} }

View File

@ -4090,6 +4090,10 @@
"enum": ["csv", "tagKey", "tagValue", "fieldKey", "timeStamp"], "enum": ["csv", "tagKey", "tagValue", "fieldKey", "timeStamp"],
"description": "description":
"The type will change the format of the output value. tagKey/fieldKey are double quoted; tagValue are single quoted; csv and timeStamp are not quoted." "The type will change the format of the output value. tagKey/fieldKey are double quoted; tagValue are single quoted; csv and timeStamp are not quoted."
},
"key": {
"type": "string",
"description":"This will be the key for a specific value of a template variable. Used if the templateVar type is 'map'"
} }
} }
}, },

View File

@ -15,19 +15,23 @@ func ValidTemplateRequest(template *chronograf.Template) error {
switch template.Type { switch template.Type {
default: default:
return fmt.Errorf("Unknown template type %s", template.Type) return fmt.Errorf("Unknown template type %s", template.Type)
case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases": case "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases", "map", "influxql":
} }
for _, v := range template.Values { for _, v := range template.Values {
switch v.Type { switch v.Type {
default: default:
return fmt.Errorf("Unknown template variable type %s", v.Type) return fmt.Errorf("Unknown template variable type %s", v.Type)
case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant": case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant", "influxql":
}
if template.Type == "map" && v.Key == "" {
return fmt.Errorf("Templates of type 'map' require a 'key'")
} }
} }
if template.Type == "query" && template.Query == nil { if template.Type == "influxql" && template.Query == nil {
return fmt.Errorf("No query set for template of type 'query'") return fmt.Errorf("No query set for template of type 'influxql'")
} }
return nil return nil

View File

@ -57,7 +57,37 @@ func TestValidTemplateRequest(t *testing.T) {
name: "No query set", name: "No query set",
wantErr: true, wantErr: true,
template: &chronograf.Template{ template: &chronograf.Template{
Type: "query", Type: "influxql",
},
},
{
name: "Valid Map type",
template: &chronograf.Template{
Type: "map",
TemplateVar: chronograf.TemplateVar{
Values: []chronograf.TemplateValue{
{
Key: "key",
Value: "value",
Type: "constant",
},
},
},
},
},
{
name: "Map without Key",
wantErr: true,
template: &chronograf.Template{
Type: "map",
TemplateVar: chronograf.TemplateVar{
Values: []chronograf.TemplateValue{
{
Value: "value",
Type: "constant",
},
},
},
}, },
}, },
} }

View File

@ -48,7 +48,7 @@ interface State {
@ErrorHandling @ErrorHandling
export class AllUsersPage extends PureComponent<Props, State> { export class AllUsersPage extends PureComponent<Props, State> {
constructor(props) { constructor(props: Props) {
super(props) super(props)
this.state = { this.state = {

View File

@ -1,5 +1,5 @@
import {proxy} from 'src/utils/queryUrlGenerator' import {proxy} from 'src/utils/queryUrlGenerator'
import {TimeRange} from '../../types' import {TimeRange} from 'src/types'
export const getAlerts = ( export const getAlerts = (
source: string, source: string,

View File

@ -5,7 +5,9 @@ import {replace} from 'react-router-redux'
import _ from 'lodash' import _ from 'lodash'
import queryString from 'query-string' import queryString from 'query-string'
import {proxy} from 'src/utils/queryUrlGenerator'
import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized' import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
import {parseMetaQuery} from 'src/tempVars/utils/parsing'
import { import {
getDashboards as getDashboardsAJAX, getDashboards as getDashboardsAJAX,
@ -15,7 +17,6 @@ import {
updateDashboardCell as updateDashboardCellAJAX, updateDashboardCell as updateDashboardCellAJAX,
addDashboardCell as addDashboardCellAJAX, addDashboardCell as addDashboardCellAJAX,
deleteDashboardCell as deleteDashboardCellAJAX, deleteDashboardCell as deleteDashboardCellAJAX,
getTempVarValuesBySourceQuery,
createDashboard as createDashboardAJAX, createDashboard as createDashboardAJAX,
} from 'src/dashboards/apis' } from 'src/dashboards/apis'
import {getMe} from 'src/shared/apis/auth' import {getMe} from 'src/shared/apis/auth'
@ -48,7 +49,6 @@ import {
} from 'src/shared/copy/notifications' } from 'src/shared/copy/notifications'
import {makeQueryForTemplate} from 'src/dashboards/utils/tempVars' import {makeQueryForTemplate} from 'src/dashboards/utils/tempVars'
import parsers from 'src/shared/parsing'
import {getDeep} from 'src/utils/wrappers' import {getDeep} from 'src/utils/wrappers'
import idNormalizer, {TYPE_ID} from 'src/normalizers/id' import idNormalizer, {TYPE_ID} from 'src/normalizers/id'
@ -418,7 +418,7 @@ export const getDashboardsNamesAsync = (sourceID: string) => async (
} }
} }
export const getDashboardAsync = (dashboardID: string) => async ( export const getDashboardAsync = (dashboardID: number) => async (
dispatch dispatch
): Promise<Dashboard | null> => { ): Promise<Dashboard | null> => {
try { try {
@ -628,25 +628,23 @@ export const hydrateTempVarValuesAsync = (
const dashboard = getState().dashboardUI.dashboards.find( const dashboard = getState().dashboardUI.dashboards.find(
d => d.id === dashboardID d => d.id === dashboardID
) )
const templates: Template[] = dashboard.templates
const queries = templates
.filter(
template => getDeep<string>(template, 'query.influxql', '') !== ''
)
.map(async template => {
const query = makeQueryForTemplate(template.query)
const response = await proxy({source: source.links.proxy, query})
const values = parseMetaQuery(query, response.data)
const tempsWithQueries = dashboard.templates.filter( return {template, values}
({query}) => !!query.influxql
)
const asyncQueries = tempsWithQueries.map(({query}) =>
getTempVarValuesBySourceQuery(source, {
query: makeQueryForTemplate(query),
}) })
) const results = await Promise.all(queries)
const results = await Promise.all(asyncQueries) for (const {template, values} of results) {
dispatch(editTemplateVariableValues(+dashboard.id, template.id, values))
results.forEach(({data}, i) => { }
const {type, query, id} = tempsWithQueries[i]
const parsed = parsers[type](data, query.tagKey || query.measurement)
const vals = parsed[type]
dispatch(editTemplateVariableValues(+dashboard.id, id, vals))
})
} catch (error) { } catch (error) {
console.error(error) console.error(error)
dispatch(errorThrown(error)) dispatch(errorThrown(error))
@ -814,7 +812,7 @@ const syncDashboardFromURLQueryParams = (
} }
export const getDashboardWithHydratedAndSyncedTempVarsAsync = ( export const getDashboardWithHydratedAndSyncedTempVarsAsync = (
dashboardID: string, dashboardID: number,
source: Source, source: Source,
router: InjectedRouter, router: InjectedRouter,
location: Location location: Location

View File

@ -1,5 +1,4 @@
import AJAX from 'utils/ajax' import AJAX from 'utils/ajax'
import {proxy} from 'utils/queryUrlGenerator'
export function getDashboards() { export function getDashboards() {
return AJAX({ return AJAX({
@ -98,19 +97,3 @@ export const editTemplateVariables = async templateVariable => {
throw error throw error
} }
} }
export const getTempVarValuesBySourceQuery = async (source, templateQuery) => {
const {
query,
db,
// rp, TODO
tempVars,
} = templateQuery
try {
// TODO: add rp as argument to proxy
return await proxy({source: source.links.proxy, query, db, tempVars})
} catch (error) {
console.error(error)
throw error
}
}

View File

@ -495,7 +495,7 @@ DashboardPage.propTypes = {
sources: arrayOf(shape({})).isRequired, sources: arrayOf(shape({})).isRequired,
params: shape({ params: shape({
sourceID: string.isRequired, sourceID: string.isRequired,
dashboardID: string.isRequired, dashboardID: number.isRequired,
}).isRequired, }).isRequired,
location: shape({ location: shape({
pathname: string.isRequired, pathname: string.isRequired,

View File

@ -134,7 +134,7 @@ class AlertTabs extends PureComponent<Props, State> {
this.setState({services}) this.setState({services})
} catch (error) { } catch (error) {
this.setState({services: null}) this.setState({services: null})
this.props.notify(notifyCouldNotRetrieveKapacitorServices(kapacitor)) this.props.notify(notifyCouldNotRetrieveKapacitorServices(kapacitor.name))
} }
} }

View File

@ -1,24 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const CodeData = ({onClickTemplate, template}) => (
<code
className="rule-builder--message-template"
data-tip={template.text}
onClick={onClickTemplate}
>
{template.label}
</code>
)
const {func, shape, string} = PropTypes
CodeData.propTypes = {
onClickTemplate: func,
template: shape({
label: string,
text: string,
}),
}
export default CodeData

View File

@ -0,0 +1,20 @@
import React, {SFC} from 'react'
import {RuleMessage} from 'src/types/kapacitor'
interface Props {
onClickTemplate: () => void
template: RuleMessage
}
const CodeData: SFC<Props> = ({onClickTemplate, template}) => (
<code
className="rule-builder--message-template"
data-tip={template.text}
onClick={onClickTemplate}
>
{template.label}
</code>
)
export default CodeData

View File

@ -24,7 +24,7 @@ interface Props {
query: QueryConfig query: QueryConfig
isDeadman: boolean isDeadman: boolean
isKapacitorRule: boolean isKapacitorRule: boolean
onAddEvery: () => void onAddEvery: (every?: string) => void
timeRange: TimeRange timeRange: TimeRange
} }

View File

@ -1,13 +1,24 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
import {PERIODS} from 'src/kapacitor/constants' import {PERIODS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown' import Dropdown from 'src/shared/components/Dropdown'
import {AlertRule} from 'src/types'
const periods = PERIODS.map(text => { const periods = PERIODS.map(text => {
return {text} return {text}
}) })
const Deadman = ({rule, onChange}) => ( interface Item {
text: string
}
interface Props {
rule: AlertRule
onChange: (item: Item) => void
}
const Deadman: SFC<Props> = ({rule, onChange}) => (
<div className="rule-section--row rule-section--row-first rule-section--row-last"> <div className="rule-section--row rule-section--row-first rule-section--row-last">
<p>Send Alert if Data is missing for</p> <p>Send Alert if Data is missing for</p>
<Dropdown <Dropdown
@ -20,15 +31,4 @@ const Deadman = ({rule, onChange}) => (
</div> </div>
) )
const {shape, string, func} = PropTypes
Deadman.propTypes = {
rule: shape({
values: shape({
period: string,
}),
}),
onChange: func.isRequired,
}
export default Deadman export default Deadman

View File

@ -1,7 +1,7 @@
import React, {Component} from 'react' import React, {Component, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {InjectedRouter} from 'react-router'
import PageHeader from 'src/shared/components/PageHeader' import PageHeader from 'src/shared/components/PageHeader'
import NameSection from 'src/kapacitor/components/NameSection' import NameSection from 'src/kapacitor/components/NameSection'
@ -9,13 +9,13 @@ import ValuesSection from 'src/kapacitor/components/ValuesSection'
import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave' import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave'
import RuleHandlers from 'src/kapacitor/components/RuleHandlers' import RuleHandlers from 'src/kapacitor/components/RuleHandlers'
import RuleMessage from 'src/kapacitor/components/RuleMessage' import RuleMessage from 'src/kapacitor/components/RuleMessage'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {createRule, editRule} from 'src/kapacitor/apis' import {createRule, editRule} from 'src/kapacitor/apis'
import buildInfluxQLQuery from 'utils/influxql' import buildInfluxQLQuery from 'src/utils/influxql'
import {timeRanges} from 'shared/data/timeRanges' import {timeRanges} from 'src/shared/data/timeRanges'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {notify as notifyAction} from 'shared/actions/notifications' import {notify as notifyAction} from 'src/shared/actions/notifications'
import { import {
notifyAlertRuleCreated, notifyAlertRuleCreated,
@ -25,11 +25,52 @@ import {
notifyAlertRuleRequiresQuery, notifyAlertRuleRequiresQuery,
notifyAlertRuleRequiresConditionValue, notifyAlertRuleRequiresConditionValue,
notifyAlertRuleDeadmanInvalid, notifyAlertRuleDeadmanInvalid,
} from 'shared/copy/notifications' } from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {
Source,
AlertRule,
Notification,
Kapacitor,
QueryConfig,
TimeRange,
} from 'src/types'
import {Handler} from 'src/types/kapacitor'
import {
KapacitorQueryConfigActions,
KapacitorRuleActions,
} from 'src/types/actions'
interface Props {
source: Source
rule: AlertRule
query: QueryConfig
queryConfigs: QueryConfig[]
queryConfigActions: KapacitorQueryConfigActions
ruleActions: KapacitorRuleActions
notify: (message: Notification) => void
ruleID: string
handlersFromConfig: Handler[]
router: InjectedRouter
kapacitor: Kapacitor
configLink: string
}
interface Item {
text: string
}
interface TypeItem extends Item {
type: string
}
interface State {
timeRange: TimeRange
}
@ErrorHandling @ErrorHandling
class KapacitorRule extends Component { class KapacitorRule extends Component<Props, State> {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
@ -37,146 +78,7 @@ class KapacitorRule extends Component {
} }
} }
handleChooseTimeRange = ({lower}) => { public render() {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
handleCreate = pathname => {
const {notify, queryConfigs, rule, source, router, kapacitor} = this.props
const newRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
delete newRule.queryID
createRule(kapacitor, newRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleCreated(newRule.name))
})
.catch(e => {
notify(notifyAlertRuleCreateFailed(newRule.name, e.data.message))
})
}
handleEdit = pathname => {
const {notify, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleUpdated(rule.name))
})
.catch(e => {
notify(notifyAlertRuleUpdateFailed(rule.name, e.data.message))
})
}
handleSave = () => {
const {rule} = this.props
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate()
} else {
this.handleEdit()
}
}
handleSaveToConfig = configName => () => {
const {rule, configLink, router} = this.props
const pathname = `${configLink}#${configName}`
if (this.validationError()) {
router.push({
pathname,
})
return
}
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate(pathname)
} else {
this.handleEdit(pathname)
}
}
handleAddEvery = frequency => {
const {
rule: {id: ruleID},
ruleActions: {addEvery},
} = this.props
addEvery(ruleID, frequency)
}
handleRemoveEvery = () => {
const {
rule: {id: ruleID},
ruleActions: {removeEvery},
} = this.props
removeEvery(ruleID)
}
validationError = () => {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
}
if (!buildInfluxQLQuery({}, query)) {
return notifyAlertRuleRequiresQuery()
}
if (!rule.values.value) {
return notifyAlertRuleRequiresConditionValue()
}
return ''
}
deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return notifyAlertRuleDeadmanInvalid()
}
return ''
}
handleRuleTypeDropdownChange = ({type, text}) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
[type]: text,
})
}
handleRuleTypeInputChange = e => {
const {ruleActions, rule} = this.props
const {lower, upper} = e.target.form
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
value: lower.value,
rangeValue: upper ? upper.value : '',
})
}
handleDeadmanChange = ({text}) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {period: text})
}
optionsComponents = () => {
return (
<RuleHeaderSave
onSave={this.handleSave}
validationError={this.validationError()}
/>
)
}
render() {
const { const {
rule, rule,
source, source,
@ -191,7 +93,7 @@ class KapacitorRule extends Component {
return ( return (
<div className="page"> <div className="page">
<PageHeader <PageHeader
title="Alert Rule Builder" titleText="Alert Rule Builder"
optionsComponents={this.optionsComponents} optionsComponents={this.optionsComponents}
sourceIndicator={true} sourceIndicator={true}
/> />
@ -225,7 +127,7 @@ class KapacitorRule extends Component {
ruleActions={ruleActions} ruleActions={ruleActions}
handlersFromConfig={handlersFromConfig} handlersFromConfig={handlersFromConfig}
onGoToConfig={this.handleSaveToConfig} onGoToConfig={this.handleSaveToConfig}
validationError={this.validationError()} validationError={this.validationError}
/> />
<RuleMessage rule={rule} ruleActions={ruleActions} /> <RuleMessage rule={rule} ruleActions={ruleActions} />
</div> </div>
@ -236,27 +138,147 @@ class KapacitorRule extends Component {
</div> </div>
) )
} }
}
const {arrayOf, func, shape, string} = PropTypes private handleChooseTimeRange = ({lower}: TimeRange) => {
const timeRange = timeRanges.find(range => range.lower === lower)
this.setState({timeRange})
}
KapacitorRule.propTypes = { private handleCreate = (pathname?: string) => {
source: shape({}).isRequired, const {notify, queryConfigs, rule, source, router, kapacitor} = this.props
rule: shape({
values: shape({}), const newRule = Object.assign({}, rule, {
}).isRequired, query: queryConfigs[rule.queryID],
query: shape({}).isRequired, })
queryConfigs: shape({}).isRequired, delete newRule.queryID
queryConfigActions: shape({}).isRequired,
ruleActions: shape({}).isRequired, createRule(kapacitor, newRule)
notify: func.isRequired, .then(() => {
ruleID: string.isRequired, router.push(pathname || `/sources/${source.id}/alert-rules`)
handlersFromConfig: arrayOf(shape({})).isRequired, notify(notifyAlertRuleCreated(newRule.name))
router: shape({ })
push: func.isRequired, .catch(e => {
}).isRequired, notify(notifyAlertRuleCreateFailed(newRule.name, e.data.message))
kapacitor: shape({}).isRequired, })
configLink: string.isRequired, }
private handleEdit = (pathname?: string) => {
const {notify, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
notify(notifyAlertRuleUpdated(rule.name))
})
.catch(e => {
notify(notifyAlertRuleUpdateFailed(rule.name, e.data.message))
})
}
private handleSave = () => {
const {rule} = this.props
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate()
} else {
this.handleEdit()
}
}
private handleSaveToConfig = (configName: string) => () => {
const {rule, configLink, router} = this.props
const pathname = `${configLink}#${configName}`
if (this.validationError) {
router.push({
pathname,
})
return
}
if (rule.id === DEFAULT_RULE_ID) {
this.handleCreate(pathname)
} else {
this.handleEdit(pathname)
}
}
private handleAddEvery = (frequency: string) => {
const {
rule: {id: ruleID},
ruleActions: {addEvery},
} = this.props
addEvery(ruleID, frequency)
}
private handleRemoveEvery = () => {
const {
rule: {id: ruleID},
ruleActions: {removeEvery},
} = this.props
removeEvery(ruleID)
}
private get validationError(): string {
const {rule, query} = this.props
if (rule.trigger === 'deadman') {
return this.deadmanValidation()
}
if (!buildInfluxQLQuery({lower: ''}, query)) {
return notifyAlertRuleRequiresQuery()
}
if (!rule.values.value) {
return notifyAlertRuleRequiresConditionValue()
}
return ''
}
private deadmanValidation = () => {
const {query} = this.props
if (query && (!query.database || !query.measurement)) {
return notifyAlertRuleDeadmanInvalid()
}
return ''
}
private handleRuleTypeDropdownChange = ({type, text}: TypeItem) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
[type]: text,
})
}
private handleRuleTypeInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const {ruleActions, rule} = this.props
const {lower, upper} = e.target.form
ruleActions.updateRuleValues(rule.id, rule.trigger, {
...this.props.rule.values,
value: lower.value,
rangeValue: upper ? upper.value : '',
})
}
private handleDeadmanChange = ({text}: Item) => {
const {ruleActions, rule} = this.props
ruleActions.updateRuleValues(rule.id, rule.trigger, {period: text})
}
private get optionsComponents(): JSX.Element {
return (
<RuleHeaderSave
onSave={this.handleSave}
validationError={this.validationError}
/>
)
}
} }
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -1,7 +1,12 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const LogItemHTTP = ({logItem}) => ( import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemHTTP: SFC<Props> = ({logItem}) => (
<div className="logs-table--row"> <div className="logs-table--row">
<div className="logs-table--divider"> <div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} /> <div className={`logs-table--level ${logItem.lvl}`} />
@ -16,17 +21,4 @@ const LogItemHTTP = ({logItem}) => (
</div> </div>
) )
const {shape, string} = PropTypes
LogItemHTTP.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
method: string.isRequired,
username: string.isRequired,
host: string.isRequired,
duration: string.isRequired,
}),
}
export default LogItemHTTP export default LogItemHTTP

View File

@ -1,7 +1,12 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const LogItemHTTPError = ({logItem}) => ( import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemHTTPError: SFC<Props> = ({logItem}) => (
<div className="logs-table--row" key={logItem.key}> <div className="logs-table--row" key={logItem.key}>
<div className="logs-table--divider"> <div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} /> <div className={`logs-table--level ${logItem.lvl}`} />
@ -16,15 +21,4 @@ const LogItemHTTPError = ({logItem}) => (
</div> </div>
) )
const {shape, string} = PropTypes
LogItemHTTPError.propTypes = {
logItem: shape({
key: string.isRequired,
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemHTTPError export default LogItemHTTPError

View File

@ -1,7 +1,12 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const LogItemInfluxDBDebug = ({logItem}) => ( import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemInfluxDBDebug: SFC<Props> = ({logItem}) => (
<div className="logs-table--row"> <div className="logs-table--row">
<div className="logs-table--divider"> <div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} /> <div className={`logs-table--level ${logItem.lvl}`} />
@ -20,15 +25,4 @@ const LogItemInfluxDBDebug = ({logItem}) => (
</div> </div>
) )
const {shape, string} = PropTypes
LogItemInfluxDBDebug.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
cluster: string.isRequired,
}),
}
export default LogItemInfluxDBDebug export default LogItemInfluxDBDebug

View File

@ -1,7 +1,12 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const LogItemKapacitorDebug = ({logItem}) => ( import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemKapacitorDebug: SFC<Props> = ({logItem}) => (
<div className="logs-table--row"> <div className="logs-table--row">
<div className="logs-table--divider"> <div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} /> <div className={`logs-table--level ${logItem.lvl}`} />
@ -16,14 +21,4 @@ const LogItemKapacitorDebug = ({logItem}) => (
</div> </div>
) )
const {shape, string} = PropTypes
LogItemKapacitorDebug.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemKapacitorDebug export default LogItemKapacitorDebug

View File

@ -1,7 +1,12 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const LogItemKapacitorError = ({logItem}) => ( import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemKapacitorError: SFC<Props> = ({logItem}) => (
<div className="logs-table--row"> <div className="logs-table--row">
<div className="logs-table--divider"> <div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} /> <div className={`logs-table--level ${logItem.lvl}`} />
@ -16,14 +21,4 @@ const LogItemKapacitorError = ({logItem}) => (
</div> </div>
) )
const {shape, string} = PropTypes
LogItemKapacitorError.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemKapacitorError export default LogItemKapacitorError

View File

@ -1,51 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const renderKeysAndValues = (object, name) => {
if (!object) {
return <span className="logs-table--empty-cell">--</span>
}
const sortedObjKeys = Object.keys(object).sort()
return (
<div className="logs-table--column">
<h1>{`${sortedObjKeys.length} ${name}`}</h1>
<div className="logs-table--scrollbox">
{sortedObjKeys.map(objKey => (
<div key={objKey} className="logs-table--key-value">
{objKey}: <span>{object[objKey]}</span>
</div>
))}
</div>
</div>
)
}
const LogItemKapacitorPoint = ({logItem}) => (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
<div className="logs-table--timestamp">{logItem.ts}</div>
</div>
<div className="logs-table--details">
<div className="logs-table--service">Kapacitor Point</div>
<div className="logs-table--columns">
{renderKeysAndValues(logItem.tag, 'Tags')}
{renderKeysAndValues(logItem.field, 'Fields')}
</div>
</div>
</div>
)
const {shape, string} = PropTypes
LogItemKapacitorPoint.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
tag: shape.isRequired,
field: shape.isRequired,
}),
}
export default LogItemKapacitorPoint

View File

@ -0,0 +1,55 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
@ErrorHandling
class LogItemKapacitorPoint extends PureComponent<Props> {
public render() {
const {logItem} = this.props
return (
<div className="logs-table--row">
<div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} />
<div className="logs-table--timestamp">{logItem.ts}</div>
</div>
<div className="logs-table--details">
<div className="logs-table--service">Kapacitor Point</div>
<div className="logs-table--columns">
{this.renderKeysAndValues(logItem.tag, 'Tags')}
{this.renderKeysAndValues(logItem.field, 'Fields')}
</div>
</div>
</div>
)
}
private renderKeysAndValues = (object: any, name: string) => {
if (_.isEmpty(object)) {
return <span className="logs-table--empty-cell">--</span>
}
const sortedObjKeys = Object.keys(object).sort()
return (
<div className="logs-table--column">
<h1>{`${sortedObjKeys.length} ${name}`}</h1>
<div className="logs-table--scrollbox">
{sortedObjKeys.map(objKey => (
<div key={objKey} className="logs-table--key-value">
{objKey}: <span>{object[objKey]}</span>
</div>
))}
</div>
</div>
)
}
}
export default LogItemKapacitorPoint

View File

@ -1,7 +1,12 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const LogItemSession = ({logItem}) => ( import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogItemSession: SFC<Props> = ({logItem}) => (
<div className="logs-table--row"> <div className="logs-table--row">
<div className="logs-table--divider"> <div className="logs-table--divider">
<div className={`logs-table--level ${logItem.lvl}`} /> <div className={`logs-table--level ${logItem.lvl}`} />
@ -13,14 +18,4 @@ const LogItemSession = ({logItem}) => (
</div> </div>
) )
const {shape, string} = PropTypes
LogItemSession.propTypes = {
logItem: shape({
lvl: string.isRequired,
ts: string.isRequired,
msg: string.isRequired,
}),
}
export default LogItemSession export default LogItemSession

View File

@ -1,12 +1,17 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
import LogsTableRow from 'src/kapacitor/components/LogsTableRow' import LogsTableRow from 'src/kapacitor/components/LogsTableRow'
import FancyScrollbar from 'src/shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {LogItem} from 'src/types/kapacitor'
const numLogsToRender = 200 const numLogsToRender = 200
const LogsTable = ({logs}) => ( interface Props {
logs: LogItem[]
}
const LogsTable: SFC<Props> = ({logs}) => (
<div className="logs-table"> <div className="logs-table">
<div className="logs-table--header"> <div className="logs-table--header">
{`${numLogsToRender} Most Recent Logs`} {`${numLogsToRender} Most Recent Logs`}
@ -22,17 +27,4 @@ const LogsTable = ({logs}) => (
</div> </div>
) )
const {arrayOf, shape, string} = PropTypes
LogsTable.propTypes = {
logs: arrayOf(
shape({
key: string.isRequired,
ts: string.isRequired,
lvl: string.isRequired,
msg: string.isRequired,
})
).isRequired,
}
export default LogsTable export default LogsTable

View File

@ -1,5 +1,4 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
import LogItemSession from 'src/kapacitor/components/LogItemSession' import LogItemSession from 'src/kapacitor/components/LogItemSession'
import LogItemHTTP from 'src/kapacitor/components/LogItemHTTP' import LogItemHTTP from 'src/kapacitor/components/LogItemHTTP'
@ -9,7 +8,13 @@ import LogItemKapacitorError from 'src/kapacitor/components/LogItemKapacitorErro
import LogItemKapacitorDebug from 'src/kapacitor/components/LogItemKapacitorDebug' import LogItemKapacitorDebug from 'src/kapacitor/components/LogItemKapacitorDebug'
import LogItemInfluxDBDebug from 'src/kapacitor/components/LogItemInfluxDBDebug' import LogItemInfluxDBDebug from 'src/kapacitor/components/LogItemInfluxDBDebug'
const LogsTableRow = ({logItem}) => { import {LogItem} from 'src/types/kapacitor'
interface Props {
logItem: LogItem
}
const LogsTableRow: SFC<Props> = ({logItem}) => {
if (logItem.service === 'sessions') { if (logItem.service === 'sessions') {
return <LogItemSession logItem={logItem} /> return <LogItemSession logItem={logItem} />
} }
@ -51,15 +56,4 @@ const LogsTableRow = ({logItem}) => {
) )
} }
const {shape, string} = PropTypes
LogsTableRow.propTypes = {
logItem: shape({
key: string.isRequired,
ts: string.isRequired,
lvl: string.isRequired,
msg: string.isRequired,
}).isRequired,
}
export default LogsTableRow export default LogsTableRow

View File

@ -1,11 +1,25 @@
import React, {Component} from 'react' import React, {Component, ChangeEvent, KeyboardEvent} from 'react'
import PropTypes from 'prop-types'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule} from 'src/types'
interface Props {
defaultName: string
onRuleRename: (id: string, name: string) => void
rule: AlertRule
}
interface State {
reset: boolean
}
@ErrorHandling @ErrorHandling
class NameSection extends Component { class NameSection extends Component<Props, State> {
constructor(props) { private inputRef: HTMLInputElement
constructor(props: Props) {
super(props) super(props)
this.state = { this.state = {
@ -13,14 +27,22 @@ class NameSection extends Component {
} }
} }
handleInputBlur = reset => e => { public handleInputBlur = (reset: boolean) => (
e: ChangeEvent<HTMLInputElement>
): void => {
const {defaultName, onRuleRename, rule} = this.props const {defaultName, onRuleRename, rule} = this.props
onRuleRename(rule.id, reset ? defaultName : e.target.value) let ruleName: string
if (reset) {
ruleName = defaultName
} else {
ruleName = e.target.value
}
onRuleRename(rule.id, ruleName)
this.setState({reset: false}) this.setState({reset: false})
} }
handleKeyDown = e => { public handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.inputRef.blur() this.inputRef.blur()
} }
@ -30,15 +52,13 @@ class NameSection extends Component {
} }
} }
render() { public render() {
const {rule, defaultName} = this.props const {defaultName} = this.props
const {reset} = this.state const {reset} = this.state
return ( return (
<div className="rule-section"> <div className="rule-section">
<h3 className="rule-section--heading"> <h3 className="rule-section--heading">{this.header}</h3>
{rule.id === DEFAULT_RULE_ID ? 'Name this Alert Rule' : 'Name'}
</h3>
<div className="rule-section--body"> <div className="rule-section--body">
<div className="rule-section--row rule-section--row-first rule-section--row-last"> <div className="rule-section--row rule-section--row-first rule-section--row-last">
<input <input
@ -55,14 +75,18 @@ class NameSection extends Component {
</div> </div>
) )
} }
}
const {func, string, shape} = PropTypes private get header() {
const {
rule: {id},
} = this.props
NameSection.propTypes = { if (id === DEFAULT_RULE_ID) {
defaultName: string.isRequired, return 'Name this Alert Rule'
onRuleRename: func.isRequired, }
rule: shape({}).isRequired,
return 'Name'
}
} }
export default NameSection export default NameSection

View File

@ -1,14 +1,27 @@
import React from 'react' import React, {SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
const mapToItems = (arr, type) => arr.map(text => ({text, type})) import {CHANGES, RELATIVE_OPERATORS, SHIFTS} from 'src/kapacitor/constants'
import Dropdown from 'src/shared/components/Dropdown'
import {AlertRule} from 'src/types'
const mapToItems = (arr: string[], type: string) =>
arr.map(text => ({text, type}))
const changes = mapToItems(CHANGES, 'change') const changes = mapToItems(CHANGES, 'change')
const shifts = mapToItems(SHIFTS, 'shift') const shifts = mapToItems(SHIFTS, 'shift')
const operators = mapToItems(RELATIVE_OPERATORS, 'operator') const operators = mapToItems(RELATIVE_OPERATORS, 'operator')
const Relative = ({ interface TypeItem {
type: string
text: string
}
interface Props {
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onDropdownChange: (item: TypeItem) => void
rule: AlertRule
}
const Relative: SFC<Props> = ({
onRuleTypeInputChange, onRuleTypeInputChange,
onDropdownChange, onDropdownChange,
rule: { rule: {
@ -46,7 +59,7 @@ const Relative = ({
style={{width: '160px', marginLeft: '6px'}} style={{width: '160px', marginLeft: '6px'}}
type="text" type="text"
name="lower" name="lower"
spellCheck="false" spellCheck={false}
value={value} value={value}
onChange={onRuleTypeInputChange} onChange={onRuleTypeInputChange}
required={true} required={true}
@ -56,19 +69,4 @@ const Relative = ({
</div> </div>
) )
const {shape, string, func} = PropTypes
Relative.propTypes = {
onRuleTypeInputChange: func.isRequired,
onDropdownChange: func.isRequired,
rule: shape({
values: shape({
change: string,
shift: string,
operator: string,
value: string,
}),
}),
}
export default Relative export default Relative

View File

@ -26,7 +26,7 @@ interface Props {
rule: AlertRule rule: AlertRule
ruleActions: RuleActions ruleActions: RuleActions
handlersFromConfig: Handler[] handlersFromConfig: Handler[]
onGoToConfig: () => void onGoToConfig: (configName: string) => void
validationError: string validationError: string
} }

View File

@ -1,22 +1,24 @@
import React, {Component} from 'react' import React, {Component, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import RuleMessageText from 'src/kapacitor/components/RuleMessageText' import RuleMessageText from 'src/kapacitor/components/RuleMessageText'
import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates' import RuleMessageTemplates from 'src/kapacitor/components/RuleMessageTemplates'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule} from 'src/types'
import {KapacitorRuleActions} from 'src/types/actions'
interface Props {
rule: AlertRule
ruleActions: KapacitorRuleActions
}
@ErrorHandling @ErrorHandling
class RuleMessage extends Component { class RuleMessage extends Component<Props> {
constructor(props) { constructor(props) {
super(props) super(props)
} }
handleChangeMessage = e => { public render() {
const {ruleActions, rule} = this.props
ruleActions.updateMessage(rule.id, e.target.value)
}
render() {
const {rule, ruleActions} = this.props const {rule, ruleActions} = this.props
return ( return (
@ -35,15 +37,11 @@ class RuleMessage extends Component {
</div> </div>
) )
} }
}
const {func, shape} = PropTypes private handleChangeMessage = (e: ChangeEvent<HTMLTextAreaElement>) => {
const {ruleActions, rule} = this.props
RuleMessage.propTypes = { ruleActions.updateMessage(rule.id, e.target.value)
rule: shape().isRequired, }
ruleActions: shape({
updateMessage: func.isRequired,
}).isRequired,
} }
export default RuleMessage export default RuleMessage

View File

@ -1,5 +1,5 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash' import _ from 'lodash'
import ReactTooltip from 'react-tooltip' import ReactTooltip from 'react-tooltip'
@ -8,19 +8,22 @@ import CodeData from 'src/kapacitor/components/CodeData'
import {RULE_MESSAGE_TEMPLATES} from 'src/kapacitor/constants' import {RULE_MESSAGE_TEMPLATES} from 'src/kapacitor/constants'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {RuleMessage} from 'src/types/kapacitor'
import {AlertRule} from 'src/types'
interface Props {
rule: AlertRule
updateMessage: (id: string, message: string) => void
}
// needs to be React Component for CodeData click handler to work // needs to be React Component for CodeData click handler to work
@ErrorHandling @ErrorHandling
class RuleMessageTemplates extends Component { class RuleMessageTemplates extends Component<Props> {
constructor(props) { constructor(props) {
super(props) super(props)
} }
handleClickTemplate = template => () => { public render() {
const {updateMessage, rule} = this.props
updateMessage(rule.id, `${rule.message} ${template.label}`)
}
render() {
return ( return (
<div className="rule-section--row rule-section--row-last"> <div className="rule-section--row rule-section--row-last">
<p>Templates:</p> <p>Templates:</p>
@ -41,13 +44,11 @@ class RuleMessageTemplates extends Component {
</div> </div>
) )
} }
}
const {func, shape} = PropTypes private handleClickTemplate = (template: RuleMessage) => () => {
const {updateMessage, rule} = this.props
RuleMessageTemplates.propTypes = { updateMessage(rule.id, `${rule.message} ${template.label}`)
rule: shape().isRequired, }
updateMessage: func.isRequired,
} }
export default RuleMessageTemplates export default RuleMessageTemplates

View File

@ -1,7 +1,13 @@
import React from 'react' import React, {SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
const RuleMessageText = ({rule, updateMessage}) => ( import {AlertRule} from 'src/types'
interface Props {
rule: AlertRule
updateMessage: (e: ChangeEvent<HTMLTextAreaElement>) => void
}
const RuleMessageText: SFC<Props> = ({rule, updateMessage}) => (
<div className="rule-builder--message"> <div className="rule-builder--message">
<textarea <textarea
className="form-control input-sm form-malachite monotype" className="form-control input-sm form-malachite monotype"
@ -13,11 +19,4 @@ const RuleMessageText = ({rule, updateMessage}) => (
</div> </div>
) )
const {func, shape} = PropTypes
RuleMessageText.propTypes = {
rule: shape().isRequired,
updateMessage: func.isRequired,
}
export default RuleMessageText export default RuleMessageText

View File

@ -1,85 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'shared/components/Dropdown'
import _ from 'lodash'
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
const operators = mapToItems(THRESHOLD_OPERATORS, 'operator')
const noopSubmit = e => e.preventDefault()
const getField = ({fields}) => {
const alias = _.get(fields, ['0', 'alias'], false)
if (!alias) {
return _.get(fields, ['0', 'value'], 'Select a Time-Series')
}
return alias
}
const Threshold = ({
rule: {
values: {operator, value, rangeValue},
},
query,
onDropdownChange,
onRuleTypeInputChange,
}) => (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">{getField(query)}</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}} onSubmit={noopSubmit}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck="false"
value={value}
onChange={onRuleTypeInputChange}
placeholder={
operator === 'inside range' || operator === 'outside range'
? 'Lower'
: null
}
/>
{(operator === 'inside range' || operator === 'outside range') && (
<input
className="form-control input-sm form-malachite monotype"
name="upper"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck="false"
value={rangeValue}
onChange={onRuleTypeInputChange}
/>
)}
</form>
</div>
)
const {shape, string, func} = PropTypes
Threshold.propTypes = {
rule: shape({
values: shape({
operator: string,
rangeOperator: string,
value: string,
rangeValue: string,
}),
}),
onDropdownChange: func.isRequired,
onRuleTypeInputChange: func.isRequired,
query: shape({}).isRequired,
}
export default Threshold

View File

@ -0,0 +1,122 @@
import React, {Component, FormEvent, ChangeEvent} from 'react'
import {THRESHOLD_OPERATORS} from 'src/kapacitor/constants'
import Dropdown from 'src/shared/components/Dropdown'
import {getDeep} from 'src/utils/wrappers'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {AlertRule, QueryConfig} from 'src/types'
interface TypeItem {
type: string
text: string
}
interface Props {
rule: AlertRule
onDropdownChange: (item: TypeItem) => void
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
query: QueryConfig
}
@ErrorHandling
class Threshold extends Component<Props> {
public render() {
const {
rule: {
values: {operator, value},
},
query,
onDropdownChange,
onRuleTypeInputChange,
} = this.props
return (
<div className="rule-section--row rule-section--row-first rule-section--border-bottom">
<p>Send Alert where</p>
<span className="rule-builder--metric">{this.getField(query)}</span>
<p>is</p>
<Dropdown
className="dropdown-180"
menuClass="dropdown-malachite"
items={this.operators}
selected={operator}
onChoose={onDropdownChange}
/>
<form style={{display: 'flex'}} onSubmit={this.noopSubmit}>
<input
className="form-control input-sm form-malachite monotype"
style={{width: '160px', marginLeft: '6px'}}
type="text"
name="lower"
spellCheck={false}
value={value}
onChange={onRuleTypeInputChange}
placeholder={this.firstInputPlaceholder}
/>
{this.secondInput}
</form>
</div>
)
}
private get operators() {
const type = 'operator'
return THRESHOLD_OPERATORS.map(text => {
return {text, type}
})
}
private get isSecondInputRequired() {
const {rule} = this.props
const operator = getDeep<string>(rule, 'values.operator', '')
if (operator === 'inside range' || operator === 'outside range') {
return true
}
return false
}
private get firstInputPlaceholder() {
if (this.isSecondInputRequired) {
return 'lower'
}
return null
}
private get secondInput() {
const {rule, onRuleTypeInputChange} = this.props
const rangeValue = getDeep<string>(rule, 'values.rangeValue', '')
if (this.isSecondInputRequired) {
return (
<input
className="form-control input-sm form-malachite monotype"
name="upper"
style={{width: '160px'}}
placeholder="Upper"
type="text"
spellCheck={false}
value={rangeValue}
onChange={onRuleTypeInputChange}
/>
)
}
}
private noopSubmit = (e: FormEvent<HTMLElement>) => e.preventDefault()
private getField = ({fields}: QueryConfig): string => {
const alias = getDeep<string>(fields, '0.alias', '')
if (!alias) {
return getDeep<string>(fields, '0.value', 'Select a Time-Series')
}
return alias
}
}
export default Threshold

View File

@ -1,89 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader'
import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor'
import TickscriptEditorControls from 'src/kapacitor/components/TickscriptEditorControls'
import TickscriptEditorConsole from 'src/kapacitor/components/TickscriptEditorConsole'
import LogsTable from 'src/kapacitor/components/LogsTable'
const Tickscript = ({
onSave,
onExit,
task,
logs,
consoleMessage,
onSelectDbrps,
onChangeScript,
onChangeType,
onChangeID,
unsavedChanges,
isNewTickscript,
areLogsVisible,
areLogsEnabled,
onToggleLogsVisibility,
}) => (
<div className="page">
<TickscriptHeader
task={task}
onSave={onSave}
onExit={onExit}
unsavedChanges={unsavedChanges}
areLogsVisible={areLogsVisible}
areLogsEnabled={areLogsEnabled}
onToggleLogsVisibility={onToggleLogsVisibility}
isNewTickscript={isNewTickscript}
/>
<div className="page-contents--split">
<div
className="tickscript"
style={areLogsVisible ? {maxWidth: '50%'} : null}
>
<TickscriptEditorControls
isNewTickscript={isNewTickscript}
onSelectDbrps={onSelectDbrps}
onChangeType={onChangeType}
onChangeID={onChangeID}
task={task}
/>
<TickscriptEditor
script={task.tickscript}
onChangeScript={onChangeScript}
/>
<TickscriptEditorConsole
consoleMessage={consoleMessage}
unsavedChanges={unsavedChanges}
/>
</div>
{areLogsVisible ? <LogsTable logs={logs} /> : null}
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes
Tickscript.propTypes = {
logs: arrayOf(shape()).isRequired,
onSave: func.isRequired,
onExit: func.isRequired,
source: shape({
id: string,
}),
areLogsVisible: bool,
areLogsEnabled: bool,
onToggleLogsVisibility: func.isRequired,
task: shape({
id: string,
script: string,
dbsrps: arrayOf(shape()),
}).isRequired,
onChangeScript: func.isRequired,
onSelectDbrps: func.isRequired,
consoleMessage: string,
onChangeType: func.isRequired,
onChangeID: func.isRequired,
isNewTickscript: bool.isRequired,
unsavedChanges: bool,
}
export default Tickscript

View File

@ -0,0 +1,101 @@
import React, {PureComponent, MouseEvent, ChangeEvent} from 'react'
import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader'
import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor'
import TickscriptEditorControls from 'src/kapacitor/components/TickscriptEditorControls'
import TickscriptEditorConsole from 'src/kapacitor/components/TickscriptEditorConsole'
import LogsTable from 'src/kapacitor/components/LogsTable'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Task} from 'src/types'
import {LogItem, DBRP} from 'src/types/kapacitor'
interface Props {
logs: LogItem[]
onSave: () => void
onExit: () => void
areLogsVisible: boolean
areLogsEnabled: boolean
onToggleLogsVisibility: () => void
task: Task
onChangeScript: (tickscript: string) => void
onSelectDbrps: (dbrps: DBRP[]) => void
consoleMessage: string
onChangeType: (type: string) => (event: MouseEvent<HTMLLIElement>) => void
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
isNewTickscript: boolean
unsavedChanges: boolean
}
@ErrorHandling
class Tickscript extends PureComponent<Props> {
public render() {
const {
onSave,
onExit,
task,
consoleMessage,
onSelectDbrps,
onChangeScript,
onChangeType,
onChangeID,
unsavedChanges,
isNewTickscript,
areLogsVisible,
areLogsEnabled,
onToggleLogsVisibility,
} = this.props
return (
<div className="page">
<TickscriptHeader
task={task}
onSave={onSave}
onExit={onExit}
unsavedChanges={unsavedChanges}
areLogsVisible={areLogsVisible}
areLogsEnabled={areLogsEnabled}
onToggleLogsVisibility={onToggleLogsVisibility}
isNewTickscript={isNewTickscript}
/>
<div className="page-contents--split">
<div className="tickscript" style={this.style}>
<TickscriptEditorControls
isNewTickscript={isNewTickscript}
onSelectDbrps={onSelectDbrps}
onChangeType={onChangeType}
onChangeID={onChangeID}
task={task}
/>
<TickscriptEditor
script={task.tickscript}
onChangeScript={onChangeScript}
/>
<TickscriptEditorConsole
consoleMessage={consoleMessage}
unsavedChanges={unsavedChanges}
/>
</div>
{this.logsTable}
</div>
</div>
)
}
private get style() {
const {areLogsVisible} = this.props
if (areLogsVisible) {
return {maxWidth: '50%'}
}
}
private get logsTable() {
const {areLogsVisible, logs} = this.props
if (areLogsVisible) {
return <LogsTable logs={logs} />
}
}
}
export default Tickscript

View File

@ -1,20 +1,24 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Controlled as CodeMirror} from 'react-codemirror2' import {Controlled as CodeMirror} from 'react-codemirror2'
import 'src/external/codemirror' import 'src/external/codemirror'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
onChangeScript: (tickscript: string) => void
script: string
}
const NOOP = () => {}
@ErrorHandling @ErrorHandling
class TickscriptEditor extends Component { class TickscriptEditor extends Component<Props> {
constructor(props) { constructor(props) {
super(props) super(props)
} }
updateCode = (_, __, script) => { public render() {
this.props.onChangeScript(script)
}
render() {
const {script} = this.props const {script} = this.props
const options = { const options = {
@ -31,17 +35,15 @@ class TickscriptEditor extends Component {
value={script} value={script}
onBeforeChange={this.updateCode} onBeforeChange={this.updateCode}
options={options} options={options}
onTouchStart={NOOP}
/> />
</div> </div>
) )
} }
}
const {func, string} = PropTypes private updateCode = (_, __, script) => {
this.props.onChangeScript(script)
TickscriptEditor.propTypes = { }
onChangeScript: func,
script: string,
} }
export default TickscriptEditor export default TickscriptEditor

View File

@ -1,7 +1,14 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types'
const TickscriptEditorConsole = ({consoleMessage, unsavedChanges}) => { interface Props {
consoleMessage: string
unsavedChanges: boolean
}
const TickscriptEditorConsole: SFC<Props> = ({
consoleMessage,
unsavedChanges,
}) => {
let consoleOutput = 'TICKscript is valid' let consoleOutput = 'TICKscript is valid'
let consoleClass = 'tickscript-console--valid' let consoleClass = 'tickscript-console--valid'
@ -20,11 +27,4 @@ const TickscriptEditorConsole = ({consoleMessage, unsavedChanges}) => {
) )
} }
const {bool, string} = PropTypes
TickscriptEditorConsole.propTypes = {
consoleMessage: string,
unsavedChanges: bool,
}
export default TickscriptEditorConsole export default TickscriptEditorConsole

View File

@ -1,48 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import TickscriptType from 'src/kapacitor/components/TickscriptType'
import MultiSelectDBDropdown from 'shared/components/MultiSelectDBDropdown'
import TickscriptID, {
TickscriptStaticID,
} from 'src/kapacitor/components/TickscriptID'
const addName = list => list.map(l => ({...l, name: `${l.db}.${l.rp}`}))
const TickscriptEditorControls = ({
isNewTickscript,
onSelectDbrps,
onChangeType,
onChangeID,
task,
}) => (
<div className="tickscript-controls">
{isNewTickscript ? (
<TickscriptID onChangeID={onChangeID} id={task.id} />
) : (
<TickscriptStaticID id={task.name ? task.name : task.id} />
)}
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />
<MultiSelectDBDropdown
selectedItems={addName(task.dbrps)}
onApply={onSelectDbrps}
/>
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes
TickscriptEditorControls.propTypes = {
isNewTickscript: bool.isRequired,
onSelectDbrps: func.isRequired,
onChangeType: func.isRequired,
onChangeID: func.isRequired,
task: shape({
id: string,
script: string,
dbsrps: arrayOf(shape()),
}).isRequired,
}
export default TickscriptEditorControls

View File

@ -0,0 +1,67 @@
import React, {Component, MouseEvent, ChangeEvent} from 'react'
import TickscriptType from 'src/kapacitor/components/TickscriptType'
import MultiSelectDBDropdown from 'src/shared/components/MultiSelectDBDropdown'
import TickscriptID, {
TickscriptStaticID,
} from 'src/kapacitor/components/TickscriptID'
import {Task} from 'src/types'
import {DBRP} from 'src/types/kapacitor'
interface DBRPDropdownItem extends DBRP {
name: string
}
interface Props {
isNewTickscript: boolean
onSelectDbrps: (dbrps: DBRP[]) => void
onChangeType: (type: string) => (event: MouseEvent<HTMLLIElement>) => void
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
task: Task
}
class TickscriptEditorControls extends Component<Props> {
public render() {
const {onSelectDbrps, onChangeType, task} = this.props
return (
<div className="tickscript-controls">
{this.tickscriptID}
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />
<MultiSelectDBDropdown
selectedItems={this.addName(task.dbrps)}
onApply={onSelectDbrps}
/>
</div>
</div>
)
}
private get tickscriptID() {
const {isNewTickscript, onChangeID, task} = this.props
if (isNewTickscript) {
return <TickscriptID onChangeID={onChangeID} id={task.id} />
}
return <TickscriptStaticID id={this.taskID} />
}
private get taskID() {
const {
task: {name, id},
} = this.props
if (name) {
return name
}
return id
}
private addName = (list: DBRP[]): DBRPDropdownItem[] => {
const listWithName = list.map(l => ({...l, name: `${l.db}.${l.rp}`}))
return listWithName
}
}
export default TickscriptEditorControls

View File

@ -1,14 +1,18 @@
import React, {Component} from 'react' import React, {Component, SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
interface TickscriptIDProps {
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
id: string
}
@ErrorHandling @ErrorHandling
class TickscriptID extends Component { class TickscriptID extends Component<TickscriptIDProps> {
constructor(props) { constructor(props) {
super(props) super(props)
} }
render() { public render() {
const {onChangeID, id} = this.props const {onChangeID, id} = this.props
return ( return (
@ -25,19 +29,11 @@ class TickscriptID extends Component {
} }
} }
export const TickscriptStaticID = ({id}) => ( interface TickscriptStaticIDProps {
id: string
}
export const TickscriptStaticID: SFC<TickscriptStaticIDProps> = ({id}) => (
<h1 className="tickscript-controls--name">{id}</h1> <h1 className="tickscript-controls--name">{id}</h1>
) )
const {func, string} = PropTypes
TickscriptID.propTypes = {
onChangeID: func.isRequired,
id: string.isRequired,
}
TickscriptStaticID.propTypes = {
id: string.isRequired,
}
export default TickscriptID export default TickscriptID

View File

@ -1,30 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
const STREAM = 'stream'
const BATCH = 'batch'
const TickscriptType = ({type, onChangeType}) => (
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={type === STREAM ? 'active' : ''}
onClick={onChangeType(STREAM)}
>
Stream
</li>
<li
className={type === BATCH ? 'active' : ''}
onClick={onChangeType(BATCH)}
>
Batch
</li>
</ul>
)
const {string, func} = PropTypes
TickscriptType.propTypes = {
type: string,
onChangeType: func,
}
export default TickscriptType

View File

@ -0,0 +1,40 @@
import React, {PureComponent, MouseEvent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
type: string
onChangeType: (type: string) => (event: MouseEvent<HTMLLIElement>) => void
}
const STREAM = 'stream'
const BATCH = 'batch'
@ErrorHandling
class TickscriptType extends PureComponent<Props> {
public render() {
const {onChangeType} = this.props
return (
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={this.getClassName(STREAM)}
onClick={onChangeType(STREAM)}
>
Stream
</li>
<li className={this.getClassName(BATCH)} onClick={onChangeType(BATCH)}>
Batch
</li>
</ul>
)
}
private getClassName(type: string) {
if (type === this.props.type) {
return 'active'
}
return ''
}
}
export default TickscriptType

View File

@ -1,6 +1,4 @@
import React from 'react' import React, {SFC, ChangeEvent} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash' import _ from 'lodash'
import Deadman from 'src/kapacitor/components/Deadman' import Deadman from 'src/kapacitor/components/Deadman'
@ -9,7 +7,16 @@ import Relative from 'src/kapacitor/components/Relative'
import DataSection from 'src/kapacitor/components/DataSection' import DataSection from 'src/kapacitor/components/DataSection'
import RuleGraph from 'src/kapacitor/components/RuleGraph' import RuleGraph from 'src/kapacitor/components/RuleGraph'
import {Tab, TabList, TabPanels, TabPanel, Tabs} from 'shared/components/Tabs' import {
Tab,
TabList,
TabPanels,
TabPanel,
Tabs,
} from 'src/shared/components/Tabs'
import {AlertRule, QueryConfig, Source, TimeRange} from 'src/types'
import {KapacitorQueryConfigActions} from 'src/types/actions'
const TABS = ['Threshold', 'Relative', 'Deadman'] const TABS = ['Threshold', 'Relative', 'Deadman']
@ -22,13 +29,36 @@ const handleChooseTrigger = (rule, onChooseTrigger) => triggerIndex => {
const initialIndex = rule => TABS.indexOf(_.startCase(rule.trigger)) const initialIndex = rule => TABS.indexOf(_.startCase(rule.trigger))
const isDeadman = rule => rule.trigger === 'deadman' const isDeadman = rule => rule.trigger === 'deadman'
const ValuesSection = ({ interface Item {
text: string
}
interface TypeItem extends Item {
type: string
}
interface Props {
rule: AlertRule
onChooseTrigger: () => void
onUpdateValues: () => void
query: QueryConfig
onDeadmanChange: (item: Item) => void
onRuleTypeDropdownChange: (item: TypeItem) => void
onRuleTypeInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onAddEvery: (frequency: string) => void
onRemoveEvery: () => void
timeRange: TimeRange
queryConfigActions: KapacitorQueryConfigActions
source: Source
onChooseTimeRange: (timeRange: TimeRange) => void
}
const ValuesSection: SFC<Props> = ({
rule, rule,
query, query,
source, source,
timeRange, timeRange,
onAddEvery, onAddEvery,
onRemoveEvery,
onChooseTrigger, onChooseTrigger,
onDeadmanChange, onDeadmanChange,
onChooseTimeRange, onChooseTimeRange,
@ -58,7 +88,6 @@ const ValuesSection = ({
isKapacitorRule={true} isKapacitorRule={true}
actions={queryConfigActions} actions={queryConfigActions}
onAddEvery={onAddEvery} onAddEvery={onAddEvery}
onRemoveEvery={onRemoveEvery}
isDeadman={isDeadman(rule)} isDeadman={isDeadman(rule)}
/> />
</div> </div>
@ -97,24 +126,4 @@ const ValuesSection = ({
</div> </div>
) )
const {shape, string, func} = PropTypes
ValuesSection.propTypes = {
rule: shape({
id: string,
}).isRequired,
onChooseTrigger: func.isRequired,
onUpdateValues: func.isRequired,
query: shape({}).isRequired,
onDeadmanChange: func.isRequired,
onRuleTypeDropdownChange: func.isRequired,
onRuleTypeInputChange: func.isRequired,
onAddEvery: func.isRequired,
onRemoveEvery: func.isRequired,
timeRange: shape({}).isRequired,
queryConfigActions: shape({}).isRequired,
source: shape({}).isRequired,
onChooseTimeRange: func.isRequired,
}
export default ValuesSection export default ValuesSection

View File

@ -1,26 +1,59 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types' import _ from 'lodash'
import {InjectedRouter} from 'react-router'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view' import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view'
import * as kapacitorQueryConfigActionCreators from 'src/kapacitor/actions/queryConfigs' import * as kapacitorQueryConfigActionCreators from 'src/kapacitor/actions/queryConfigs'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index' import {getActiveKapacitor, getKapacitorConfig} from 'src/shared/apis/index'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants' import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import KapacitorRule from 'src/kapacitor/components/KapacitorRule' import KapacitorRule from 'src/kapacitor/components/KapacitorRule'
import parseHandlersFromConfig from 'src/shared/parsing/parseHandlersFromConfig' import parseHandlersFromConfig from 'src/shared/parsing/parseHandlersFromConfig'
import {notify as notifyAction} from 'shared/actions/notifications' import {notify as notifyAction} from 'src/shared/actions/notifications'
import { import {
notifyKapacitorCreateFailed, notifyKapacitorCreateFailed,
notifyCouldNotFindKapacitor, notifyCouldNotFindKapacitor,
} from 'shared/copy/notifications' } from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {
Source,
Notification,
AlertRule,
QueryConfig,
Kapacitor,
} from 'src/types'
import {
KapacitorQueryConfigActions,
KapacitorRuleActions,
} from 'src/types/actions'
interface Params {
ruleID: string
}
interface Props {
source: Source
notify: (notification: Notification) => void
rules: AlertRule[]
queryConfigs: QueryConfig[]
ruleActions: KapacitorRuleActions
queryConfigActions: KapacitorQueryConfigActions
params: Params
router: InjectedRouter
}
interface State {
handlersFromConfig: any[]
kapacitor: Kapacitor | {}
}
@ErrorHandling @ErrorHandling
class KapacitorRulePage extends Component { class KapacitorRulePage extends Component<Props, State> {
constructor(props) { constructor(props: Props) {
super(props) super(props)
this.state = { this.state = {
@ -29,7 +62,7 @@ class KapacitorRulePage extends Component {
} }
} }
async componentDidMount() { public async componentDidMount() {
const {params, source, ruleActions, notify} = this.props const {params, source, ruleActions, notify} = this.props
if (params.ruleID === 'new') { if (params.ruleID === 'new') {
@ -54,9 +87,8 @@ class KapacitorRulePage extends Component {
} }
} }
render() { public render() {
const { const {
rules,
params, params,
source, source,
router, router,
@ -65,8 +97,7 @@ class KapacitorRulePage extends Component {
queryConfigActions, queryConfigActions,
} = this.props } = this.props
const {handlersFromConfig, kapacitor} = this.state const {handlersFromConfig, kapacitor} = this.state
const rule = const rule = this.rule
params.ruleID === 'new' ? rules[DEFAULT_RULE_ID] : rules[params.ruleID]
const query = rule && queryConfigs[rule.queryID] const query = rule && queryConfigs[rule.queryID]
if (!query) { if (!query) {
@ -84,41 +115,26 @@ class KapacitorRulePage extends Component {
ruleID={params.ruleID} ruleID={params.ruleID}
router={router} router={router}
kapacitor={kapacitor} kapacitor={kapacitor}
configLink={`/sources/${source.id}/kapacitors/${kapacitor.id}/edit`} configLink={`/sources/${source.id}/kapacitors/${this.kapacitorID}/edit`}
/> />
) )
} }
}
const {func, shape, string} = PropTypes private get kapacitorID(): string {
const {kapacitor} = this.state
return _.get(kapacitor, 'id')
}
KapacitorRulePage.propTypes = { private get rule(): AlertRule {
source: shape({ const {params, rules} = this.props
links: shape({ const ruleID = _.get(params, 'ruleID')
proxy: string.isRequired,
self: string.isRequired, if (ruleID === 'new') {
}), return rules[DEFAULT_RULE_ID]
}), }
notify: func,
rules: shape({}).isRequired, return rules[params.ruleID]
queryConfigs: shape({}).isRequired, }
ruleActions: shape({
loadDefaultRule: func.isRequired,
fetchRule: func.isRequired,
chooseTrigger: func.isRequired,
addEvery: func.isRequired,
removeEvery: func.isRequired,
updateRuleValues: func.isRequired,
updateMessage: func.isRequired,
updateRuleName: func.isRequired,
}).isRequired,
queryConfigActions: shape({}).isRequired,
params: shape({
ruleID: string,
}).isRequired,
router: shape({
push: func.isRequired,
}).isRequired,
} }
const mapStateToProps = ({rules, kapacitorQueryConfigs: queryConfigs}) => ({ const mapStateToProps = ({rules, kapacitorQueryConfigs: queryConfigs}) => ({

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react' import React, {PureComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import uuid from 'uuid' import uuid from 'uuid'
@ -18,7 +18,7 @@ import {
Notification, Notification,
NotificationFunc, NotificationFunc,
} from 'src/types' } from 'src/types'
import {LogItem, DBRP} from 'src/types/kapacitor'
import { import {
notifyTickscriptLoggingUnavailable, notifyTickscriptLoggingUnavailable,
notifyTickscriptLoggingError, notifyTickscriptLoggingError,
@ -76,7 +76,7 @@ interface State {
task: Task task: Task
consoleMessage: string consoleMessage: string
isEditingID: boolean isEditingID: boolean
logs: object[] logs: LogItem[]
areLogsVisible: boolean areLogsVisible: boolean
areLogsEnabled: boolean areLogsEnabled: boolean
failStr: string failStr: string
@ -247,22 +247,22 @@ export class TickscriptPage extends PureComponent<Props, State> {
return router.push(`/sources/${sourceID}/alert-rules`) return router.push(`/sources/${sourceID}/alert-rules`)
} }
private handleChangeScript = tickscript => { private handleChangeScript = (tickscript: string) => {
this.setState({ this.setState({
task: {...this.state.task, tickscript}, task: {...this.state.task, tickscript},
unsavedChanges: true, unsavedChanges: true,
}) })
} }
private handleSelectDbrps = dbrps => { private handleSelectDbrps = (dbrps: DBRP[]) => {
this.setState({task: {...this.state.task, dbrps}, unsavedChanges: true}) this.setState({task: {...this.state.task, dbrps}, unsavedChanges: true})
} }
private handleChangeType = type => () => { private handleChangeType = (type: string) => () => {
this.setState({task: {...this.state.task, type}, unsavedChanges: true}) this.setState({task: {...this.state.task, type}, unsavedChanges: true})
} }
private handleChangeID = e => { private handleChangeID = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
task: {...this.state.task, id: e.target.value}, task: {...this.state.task, id: e.target.value},
unsavedChanges: true, unsavedChanges: true,

View File

@ -3,7 +3,7 @@ import {Notification} from 'src/types'
export type Action = ActionPublishNotification | ActionDismissNotification export type Action = ActionPublishNotification | ActionDismissNotification
// Publish notification // Publish notification
export type PubishNotification = (n: Notification) => ActionPublishNotification export type PublishNotification = (n: Notification) => ActionPublishNotification
export interface ActionPublishNotification { export interface ActionPublishNotification {
type: 'PUBLISH_NOTIFICATION' type: 'PUBLISH_NOTIFICATION'
payload: { payload: {

View File

@ -1,20 +1,47 @@
import {proxy} from 'src/utils/queryUrlGenerator' import {proxy} from 'src/utils/queryUrlGenerator'
import {noop} from 'src/shared/actions/app' import {noop} from 'src/shared/actions/app'
import _ from 'lodash'
import {errorThrown} from 'src/shared/actions/errors' import {errorThrown} from 'src/shared/actions/errors'
import {TimeSeriesResponse, TimeSeriesSeries} from 'src/types/series'
import {Status} from 'src/types'
import {getDeep} from 'src/utils/wrappers'
export const handleLoading = (query, editQueryStatus) => { interface Query {
host: string | string[]
text: string
id: string
database?: string
db?: string
rp?: string
}
interface Payload {
source: string
query: Query
tempVars: any[]
db?: string
rp?: string
resolution?: number
}
type EditQueryStatusFunction = (queryID: string, status: Status) => void
const handleLoading = (
query: Query,
editQueryStatus: EditQueryStatusFunction
): void =>
editQueryStatus(query.id, { editQueryStatus(query.id, {
loading: true, loading: true,
}) })
}
// {results: [{}]} const handleSuccess = (
export const handleSuccess = (data, query, editQueryStatus) => { data: TimeSeriesResponse,
query: Query,
editQueryStatus: EditQueryStatusFunction
): TimeSeriesResponse => {
const {results} = data const {results} = data
const error = _.get(results, ['0', 'error'], false) const error = getDeep<string>(results, '0.error', null)
const series = _.get(results, ['0', 'series'], false) const series = getDeep<TimeSeriesSeries>(results, '0.series', null)
// 200 from server and no results = warn // 200 from server and no results = warn
if (!series && !error) { if (!series && !error) {
editQueryStatus(query.id, { editQueryStatus(query.id, {
@ -38,12 +65,14 @@ export const handleSuccess = (data, query, editQueryStatus) => {
return data return data
} }
export const handleError = (error, query, editQueryStatus) => { const handleError = (
const message = _.get( error,
error, query: Query,
['data', 'message'], editQueryStatus: EditQueryStatusFunction
error.message || 'Could not retrieve data' ): void => {
) const message =
getDeep<string>(error, 'data.message', '') ||
getDeep<string>(error, 'message', 'Could not retrieve data')
// 400 from chrono server = fail // 400 from chrono server = fail
editQueryStatus(query.id, { editQueryStatus(query.id, {
@ -51,28 +80,10 @@ export const handleError = (error, query, editQueryStatus) => {
}) })
} }
interface Query {
host: string | string[]
text: string
id: string
database?: string
db?: string
rp?: string
}
interface Payload {
source: string
query: Query
tempVars: any[]
db?: string
rp?: string
resolution?: number
}
export const fetchTimeSeriesAsync = async ( export const fetchTimeSeriesAsync = async (
{source, db, rp, query, tempVars, resolution}: Payload, {source, db, rp, query, tempVars, resolution}: Payload,
editQueryStatus = noop editQueryStatus: EditQueryStatusFunction = noop
) => { ): Promise<TimeSeriesResponse> => {
handleLoading(query, editQueryStatus) handleLoading(query, editQueryStatus)
try { try {
const {data} = await proxy({ const {data} = await proxy({

View File

@ -178,8 +178,10 @@ class Dygraph extends Component<Props, State> {
} }
public componentWillUnmount() { public componentWillUnmount() {
this.dygraph.destroy() if (this.dygraph) {
delete this.dygraph this.dygraph.destroy()
delete this.dygraph
}
} }
public shouldComponentUpdate(nextProps: Props, nextState: State) { public shouldComponentUpdate(nextProps: Props, nextState: State) {

View File

@ -48,7 +48,7 @@ interface TabListProps {
activeIndex?: number activeIndex?: number
onActivate?: (index: number) => void onActivate?: (index: number) => void
isKapacitorTabs?: string isKapacitorTabs?: string
customClass: string customClass?: string
} }
export const TabList: SFC<TabListProps> = ({ export const TabList: SFC<TabListProps> = ({
@ -97,7 +97,7 @@ TabList.defaultProps = {
interface TabPanelsProps { interface TabPanelsProps {
children: JSX.Element[] | JSX.Element children: JSX.Element[] | JSX.Element
activeIndex?: number activeIndex?: number
customClass: string customClass?: string
} }
export const TabPanels: SFC<TabPanelsProps> = ({ export const TabPanels: SFC<TabPanelsProps> = ({
@ -120,7 +120,7 @@ export const TabPanel: SFC<TabPanelProps> = ({children}) => (
interface TabsProps { interface TabsProps {
children: JSX.Element[] | JSX.Element children: JSX.Element[] | JSX.Element
onSelect?: (activeIndex: number) => void onSelect?: (activeIndex: number) => void
tabContentsClass: string tabContentsClass?: string
tabsClass?: string tabsClass?: string
initialIndex?: number initialIndex?: number
} }

View File

@ -1,22 +1,29 @@
// All copy for notifications should be stored here for easy editing // All copy for notifications should be stored here for easy editing
// and ensuring stylistic consistency // and ensuring stylistic consistency
import {Notification} from 'src/types'
import {TemplateUpdate} from 'src/types/tempVars'
type NotificationExcludingMessage = Pick<
Notification,
Exclude<keyof Notification, 'message'>
>
import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index' import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index'
import {MAX_RESPONSE_BYTES} from 'src/flux/constants' import {MAX_RESPONSE_BYTES} from 'src/flux/constants'
const defaultErrorNotification = { const defaultErrorNotification: NotificationExcludingMessage = {
type: 'error', type: 'error',
icon: 'alert-triangle', icon: 'alert-triangle',
duration: TEN_SECONDS, duration: TEN_SECONDS,
} }
const defaultSuccessNotification = { const defaultSuccessNotification: NotificationExcludingMessage = {
type: 'success', type: 'success',
icon: 'checkmark', icon: 'checkmark',
duration: FIVE_SECONDS, duration: FIVE_SECONDS,
} }
const defaultDeletionNotification = { const defaultDeletionNotification: NotificationExcludingMessage = {
type: 'primary', type: 'primary',
icon: 'trash', icon: 'trash',
duration: FIVE_SECONDS, duration: FIVE_SECONDS,
@ -24,211 +31,240 @@ const defaultDeletionNotification = {
// Misc Notifications // Misc Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyGenericFail = () => 'Could not communicate with server.' export const notifyGenericFail = (): string =>
'Could not communicate with server.'
export const notifyNewVersion = version => ({ export const notifyNewVersion = (version: string): Notification => ({
type: 'info', type: 'info',
icon: 'cubo-uniform', icon: 'cubo-uniform',
duration: INFINITE, duration: INFINITE,
message: `Welcome to the latest Chronograf${version}. Local settings cleared.`, message: `Welcome to the latest Chronograf${version}. Local settings cleared.`,
}) })
export const notifyLoadLocalSettingsFailed = error => ({ export const notifyLoadLocalSettingsFailed = (error: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Loading local settings failed: ${error}`, message: `Loading local settings failed: ${error}`,
}) })
export const notifyErrorWithAltText = (type, message) => ({ export const notifyErrorWithAltText = (
type: string,
message: string
): Notification => ({
type, type,
icon: 'triangle', icon: 'triangle',
duration: TEN_SECONDS, duration: TEN_SECONDS,
message, message,
}) })
export const notifyPresentationMode = () => ({ export const notifyPresentationMode = (): Notification => ({
type: 'primary', type: 'primary',
icon: 'expand-b', icon: 'expand-b',
duration: 7500, duration: 7500,
message: 'Press ESC to exit Presentation Mode.', message: 'Press ESC to exit Presentation Mode.',
}) })
export const notifyDataWritten = () => ({ export const notifyDataWritten = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Data was written successfully.', message: 'Data was written successfully.',
}) })
export const notifyDataWriteFailed = errorMessage => ({ export const notifyDataWriteFailed = (errorMessage: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Data write failed: ${errorMessage}`, message: `Data write failed: ${errorMessage}`,
}) })
export const notifySessionTimedOut = () => ({ export const notifySessionTimedOut = (): Notification => ({
type: 'primary', type: 'primary',
icon: 'triangle', icon: 'triangle',
duration: INFINITE, duration: INFINITE,
message: 'Your session has timed out. Log in again to continue.', message: 'Your session has timed out. Log in again to continue.',
}) })
export const notifyServerError = { export const notifyServerError: Notification = {
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Internal Server Error. Check API Logs.', message: 'Internal Server Error. Check API Logs.',
} }
export const notifyCouldNotRetrieveKapacitors = sourceID => ({ export const notifyCouldNotRetrieveKapacitors = (
sourceID: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Internal Server Error. Could not retrieve Kapacitor Connections for source ${sourceID}.`, message: `Internal Server Error. Could not retrieve Kapacitor Connections for source ${sourceID}.`,
}) })
export const notifyCouldNotRetrieveKapacitorServices = kapacitor => ({ export const notifyCouldNotRetrieveKapacitorServices = (
kapacitor: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Interanl Server Error. Could not retrieve services for Kapacitor ${kapacitor}`, message: `Internal Server Error. Could not retrieve services for Kapacitor ${kapacitor}`,
}) })
export const notifyCouldNotDeleteKapacitor = () => ({ export const notifyCouldNotDeleteKapacitor = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Internal Server Error. Could not delete Kapacitor Connection.', message: 'Internal Server Error. Could not delete Kapacitor Connection.',
}) })
export const notifyCSVDownloadFailed = () => ({ export const notifyCSVDownloadFailed = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Unable to download .CSV file', message: 'Unable to download .CSV file',
}) })
// Hosts Page Notifications // Hosts Page Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyUnableToGetHosts = () => ({ export const notifyUnableToGetHosts = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Unable to get Hosts.', message: 'Unable to get Hosts.',
}) })
export const notifyUnableToGetApps = () => ({ export const notifyUnableToGetApps = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Unable to get Apps for Hosts.', message: 'Unable to get Apps for Hosts.',
}) })
// InfluxDB Sources Notifications // InfluxDB Sources Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifySourceCreationSucceeded = sourceName => ({ export const notifySourceCreationSucceeded = (
sourceName: string
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'server2', icon: 'server2',
message: `Connected to InfluxDB ${sourceName} successfully.`, message: `Connected to InfluxDB ${sourceName} successfully.`,
}) })
export const notifySourceCreationFailed = (sourceName, errorMessage) => ({ export const notifySourceCreationFailed = (
sourceName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'server2', icon: 'server2',
message: `Unable to connect to InfluxDB ${sourceName}: ${errorMessage}`, message: `Unable to connect to InfluxDB ${sourceName}: ${errorMessage}`,
}) })
export const notifySourceUdpated = sourceName => ({ export const notifySourceUdpated = (sourceName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'server2', icon: 'server2',
message: `Updated InfluxDB ${sourceName} Connection successfully.`, message: `Updated InfluxDB ${sourceName} Connection successfully.`,
}) })
export const notifySourceUdpateFailed = (sourceName, errorMessage) => ({ export const notifySourceUdpateFailed = (
sourceName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'server2', icon: 'server2',
message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`, message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`,
}) })
export const notifySourceDeleted = (sourceName: string) => ({ export const notifySourceDeleted = (sourceName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'server2', icon: 'server2',
message: `${sourceName} deleted successfully.`, message: `${sourceName} deleted successfully.`,
}) })
export const notifySourceDeleteFailed = sourceName => ({ export const notifySourceDeleteFailed = (sourceName: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'server2', icon: 'server2',
message: `There was a problem deleting ${sourceName}.`, message: `There was a problem deleting ${sourceName}.`,
}) })
export const notifySourceNoLongerAvailable = sourceName => export const notifySourceNoLongerAvailable = (
`Source ${sourceName} is no longer available. Please ensure InfluxDB is running.` sourceName: string
): Notification => ({
export const notifyNoSourcesAvailable = sourceName =>
`Unable to connect to source ${sourceName}. No other sources available.`
export const notifyUnableToRetrieveSources = () => 'Unable to retrieve sources.'
export const notifyUnableToConnectSource = sourceName =>
`Unable to connect to source ${sourceName}.`
export const notifyErrorConnectingToSource = errorMessage => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'server2',
message: `Source ${sourceName} is no longer available. Please ensure InfluxDB is running.`,
})
export const notifyErrorConnectingToSource = (
errorMessage: string
): Notification => ({
...defaultErrorNotification,
icon: 'server2',
message: `Unable to connect to InfluxDB source: ${errorMessage}`, message: `Unable to connect to InfluxDB source: ${errorMessage}`,
}) })
// Multitenancy User Notifications // Multitenancy User Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyUserRemovedFromAllOrgs = () => ({ export const notifyUserRemovedFromAllOrgs = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: message:
'You have been removed from all organizations. Please contact your administrator.', 'You have been removed from all organizations. Please contact your administrator.',
}) })
export const notifyUserRemovedFromCurrentOrg = () => ({ export const notifyUserRemovedFromCurrentOrg = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: 'You were removed from your current organization.', message: 'You were removed from your current organization.',
}) })
export const notifyOrgHasNoSources = () => ({ export const notifyOrgHasNoSources = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: 'Organization has no sources configured.', message: 'Organization has no sources configured.',
}) })
export const notifyUserSwitchedOrgs = (orgName, roleName) => ({ export const notifyUserSwitchedOrgs = (
orgName: string,
roleName: string
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
type: 'primary', type: 'primary',
message: `Now logged in to '${orgName}' as '${roleName}'.`, message: `Now logged in to '${orgName}' as '${roleName}'.`,
}) })
export const notifyOrgIsPrivate = () => ({ export const notifyOrgIsPrivate = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: message:
'This organization is private. To gain access, you must be explicitly added by an administrator.', 'This organization is private. To gain access, you must be explicitly added by an administrator.',
}) })
export const notifyCurrentOrgDeleted = () => ({ export const notifyCurrentOrgDeleted = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: 'Your current organization was deleted.', message: 'Your current organization was deleted.',
}) })
export const notifyJSONFeedFailed = url => ({ export const notifyJSONFeedFailed = (url: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Failed to fetch JSON Feed for News Feed from '${url}'`, message: `Failed to fetch JSON Feed for News Feed from '${url}'`,
}) })
// Chronograf Admin Notifications // Chronograf Admin Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyMappingDeleted = (id, scheme) => ({ export const notifyMappingDeleted = (
id: string,
scheme: string
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Mapping ${id}/${scheme} deleted successfully.`, message: `Mapping ${id}/${scheme} deleted successfully.`,
}) })
export const notifyChronografUserAddedToOrg = (user, organization) => export const notifyChronografUserAddedToOrg = (
`${user} has been added to ${organization} successfully.` user: string,
organization: string
): string => `${user} has been added to ${organization} successfully.`
export const notifyChronografUserRemovedFromOrg = (user, organization) => export const notifyChronografUserRemovedFromOrg = (
`${user} has been removed from ${organization} successfully.` user: string,
organization: string
): string => `${user} has been removed from ${organization} successfully.`
export const notifyChronografUserUpdated = message => ({ export const notifyChronografUserUpdated = (message: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message, message,
}) })
export const notifyChronografOrgDeleted = orgName => ({ export const notifyChronografOrgDeleted = (orgName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Organization ${orgName} deleted successfully.`, message: `Organization ${orgName} deleted successfully.`,
}) })
export const notifyChronografUserDeleted = (user, isAbsoluteDelete) => ({ export const notifyChronografUserDeleted = (
user: string,
isAbsoluteDelete: boolean
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `${user} has been removed from ${ message: `${user} has been removed from ${
isAbsoluteDelete isAbsoluteDelete
@ -237,7 +273,7 @@ export const notifyChronografUserDeleted = (user, isAbsoluteDelete) => ({
}`, }`,
}) })
export const notifyChronografUserMissingNameAndProvider = () => ({ export const notifyChronografUserMissingNameAndProvider = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
type: 'warning', type: 'warning',
message: 'User must have a Name and Provider.', message: 'User must have a Name and Provider.',
@ -245,220 +281,238 @@ export const notifyChronografUserMissingNameAndProvider = () => ({
// InfluxDB Admin Notifications // InfluxDB Admin Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyDBUserCreated = () => ({ export const notifyDBUserCreated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'User created successfully.', message: 'User created successfully.',
}) })
export const notifyDBUserCreationFailed = errorMessage => export const notifyDBUserCreationFailed = (errorMessage: string): string =>
`Failed to create User: ${errorMessage}` `Failed to create User: ${errorMessage}`
export const notifyDBUserDeleted = userName => ({ export const notifyDBUserDeleted = (userName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `User "${userName}" deleted successfully.`, message: `User "${userName}" deleted successfully.`,
}) })
export const notifyDBUserDeleteFailed = errorMessage => export const notifyDBUserDeleteFailed = (errorMessage: string): string =>
`Failed to delete User: ${errorMessage}` `Failed to delete User: ${errorMessage}`
export const notifyDBUserPermissionsUpdated = () => ({ export const notifyDBUserPermissionsUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'User Permissions updated successfully.', message: 'User Permissions updated successfully.',
}) })
export const notifyDBUserPermissionsUpdateFailed = errorMessage => export const notifyDBUserPermissionsUpdateFailed = (
`Failed to update User Permissions: ${errorMessage}` errorMessage: string
): string => `Failed to update User Permissions: ${errorMessage}`
export const notifyDBUserRolesUpdated = () => ({ export const notifyDBUserRolesUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'User Roles updated successfully.', message: 'User Roles updated successfully.',
}) })
export const notifyDBUserRolesUpdateFailed = errorMessage => export const notifyDBUserRolesUpdateFailed = (errorMessage: string): string =>
`Failed to update User Roles: ${errorMessage}` `Failed to update User Roles: ${errorMessage}`
export const notifyDBUserPasswordUpdated = () => ({ export const notifyDBUserPasswordUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'User Password updated successfully.', message: 'User Password updated successfully.',
}) })
export const notifyDBUserPasswordUpdateFailed = errorMessage => export const notifyDBUserPasswordUpdateFailed = (
`Failed to update User Password: ${errorMessage}` errorMessage: string
): string => `Failed to update User Password: ${errorMessage}`
export const notifyDatabaseCreated = () => ({ export const notifyDatabaseCreated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Database created successfully.', message: 'Database created successfully.',
}) })
export const notifyDBCreationFailed = errorMessage => export const notifyDBCreationFailed = (errorMessage: string): string =>
`Failed to create Database: ${errorMessage}` `Failed to create Database: ${errorMessage}`
export const notifyDBDeleted = databaseName => ({ export const notifyDBDeleted = (databaseName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Database "${databaseName}" deleted successfully.`, message: `Database "${databaseName}" deleted successfully.`,
}) })
export const notifyDBDeleteFailed = errorMessage => export const notifyDBDeleteFailed = (errorMessage: string): string =>
`Failed to delete Database: ${errorMessage}` `Failed to delete Database: ${errorMessage}`
export const notifyRoleCreated = () => ({ export const notifyRoleCreated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Role created successfully.', message: 'Role created successfully.',
}) })
export const notifyRoleCreationFailed = errorMessage => export const notifyRoleCreationFailed = (errorMessage: string): string =>
`Failed to create Role: ${errorMessage}` `Failed to create Role: ${errorMessage}`
export const notifyRoleDeleted = roleName => ({ export const notifyRoleDeleted = (roleName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Role "${roleName}" deleted successfully.`, message: `Role "${roleName}" deleted successfully.`,
}) })
export const notifyRoleDeleteFailed = errorMessage => export const notifyRoleDeleteFailed = (errorMessage: string): string =>
`Failed to delete Role: ${errorMessage}` `Failed to delete Role: ${errorMessage}`
export const notifyRoleUsersUpdated = () => ({ export const notifyRoleUsersUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Role Users updated successfully.', message: 'Role Users updated successfully.',
}) })
export const notifyRoleUsersUpdateFailed = errorMessage => export const notifyRoleUsersUpdateFailed = (errorMessage: string): string =>
`Failed to update Role Users: ${errorMessage}` `Failed to update Role Users: ${errorMessage}`
export const notifyRolePermissionsUpdated = () => ({ export const notifyRolePermissionsUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Role Permissions updated successfully.', message: 'Role Permissions updated successfully.',
}) })
export const notifyRolePermissionsUpdateFailed = errorMessage => export const notifyRolePermissionsUpdateFailed = (
`Failed to update Role Permissions: ${errorMessage}` errorMessage: string
): string => `Failed to update Role Permissions: ${errorMessage}`
export const notifyRetentionPolicyCreated = () => ({ export const notifyRetentionPolicyCreated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Retention Policy created successfully.', message: 'Retention Policy created successfully.',
}) })
export const notifyRetentionPolicyCreationError = () => ({ export const notifyRetentionPolicyCreationError = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Failed to create Retention Policy. Please check name and duration.', message: 'Failed to create Retention Policy. Please check name and duration.',
}) })
export const notifyRetentionPolicyCreationFailed = errorMessage => export const notifyRetentionPolicyCreationFailed = (
`Failed to create Retention Policy: ${errorMessage}` errorMessage: string
): string => `Failed to create Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyDeleted = rpName => ({ export const notifyRetentionPolicyDeleted = (rpName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Retention Policy "${rpName}" deleted successfully.`, message: `Retention Policy "${rpName}" deleted successfully.`,
}) })
export const notifyRetentionPolicyDeleteFailed = errorMessage => export const notifyRetentionPolicyDeleteFailed = (
`Failed to delete Retention Policy: ${errorMessage}` errorMessage: string
): string => `Failed to delete Retention Policy: ${errorMessage}`
export const notifyRetentionPolicyUpdated = () => ({ export const notifyRetentionPolicyUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Retention Policy updated successfully.', message: 'Retention Policy updated successfully.',
}) })
export const notifyRetentionPolicyUpdateFailed = errorMessage => export const notifyRetentionPolicyUpdateFailed = (
`Failed to update Retention Policy: ${errorMessage}` errorMessage: string
): string => `Failed to update Retention Policy: ${errorMessage}`
export const notifyQueriesError = errorMessage => ({ export const notifyQueriesError = (errorMessage: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
errorMessage, message: errorMessage,
}) })
export const notifyRetentionPolicyCantHaveEmptyFields = () => ({ export const notifyRetentionPolicyCantHaveEmptyFields = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Fields cannot be empty.', message: 'Fields cannot be empty.',
}) })
export const notifyDatabaseDeleteConfirmationRequired = databaseName => ({ export const notifyDatabaseDeleteConfirmationRequired = (
databaseName: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Type "DELETE ${databaseName}" to confirm. This action cannot be undone.`, message: `Type "DELETE ${databaseName}" to confirm. This action cannot be undone.`,
}) })
export const notifyDBUserNamePasswordInvalid = () => ({ export const notifyDBUserNamePasswordInvalid = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Username and/or Password too short.', message: 'Username and/or Password too short.',
}) })
export const notifyRoleNameInvalid = () => ({ export const notifyRoleNameInvalid = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Role name is too short.', message: 'Role name is too short.',
}) })
export const notifyDatabaseNameInvalid = () => ({ export const notifyDatabaseNameInvalid = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Database name cannot be blank.', message: 'Database name cannot be blank.',
}) })
export const notifyDatabaseNameAlreadyExists = () => ({ export const notifyDatabaseNameAlreadyExists = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'A Database by this name already exists.', message: 'A Database by this name already exists.',
}) })
// Dashboard Notifications // Dashboard Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyTempVarAlreadyExists = tempVarName => ({ export const notifyTempVarAlreadyExists = (
tempVarName: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'cube', icon: 'cube',
message: `Variable '${tempVarName}' already exists. Please enter a new value.`, message: `Variable '${tempVarName}' already exists. Please enter a new value.`,
}) })
export const notifyDashboardNotFound = dashboardID => ({ export const notifyDashboardNotFound = (dashboardID: number): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'dash-h', icon: 'dash-h',
message: `Dashboard ${dashboardID} could not be found`, message: `Dashboard ${dashboardID} could not be found`,
}) })
export const notifyDashboardDeleted = name => ({ export const notifyDashboardDeleted = (name: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'dash-h', icon: 'dash-h',
message: `Dashboard ${name} deleted successfully.`, message: `Dashboard ${name} deleted successfully.`,
}) })
export const notifyDashboardExported = name => ({ export const notifyDashboardExported = (name: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'dash-h', icon: 'dash-h',
message: `Dashboard ${name} exported successfully.`, message: `Dashboard ${name} exported successfully.`,
}) })
export const notifyDashboardExportFailed = (name, errorMessage) => ({ export const notifyDashboardExportFailed = (
name: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: `Failed to export Dashboard ${name}: ${errorMessage}.`, message: `Failed to export Dashboard ${name}: ${errorMessage}.`,
}) })
export const notifyDashboardImported = name => ({ export const notifyDashboardImported = (name: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'dash-h', icon: 'dash-h',
message: `Dashboard ${name} imported successfully.`, message: `Dashboard ${name} imported successfully.`,
}) })
export const notifyDashboardImportFailed = (fileName, errorMessage) => ({ export const notifyDashboardImportFailed = (
fileName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
duration: INFINITE, duration: INFINITE,
message: `Failed to import Dashboard from file ${fileName}: ${errorMessage}.`, message: `Failed to import Dashboard from file ${fileName}: ${errorMessage}.`,
}) })
export const notifyDashboardDeleteFailed = (name, errorMessage) => export const notifyDashboardDeleteFailed = (
`Failed to delete Dashboard ${name}: ${errorMessage}.` name: string,
errorMessage: string
): string => `Failed to delete Dashboard ${name}: ${errorMessage}.`
export const notifyCellAdded = name => ({ export const notifyCellAdded = (name: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'dash-h', icon: 'dash-h',
duration: 1900, duration: 1900,
message: `Added "${name}" to dashboard.`, message: `Added "${name}" to dashboard.`,
}) })
export const notifyCellDeleted = name => ({ export const notifyCellDeleted = (name: string): Notification => ({
...defaultDeletionNotification, ...defaultDeletionNotification,
icon: 'dash-h', icon: 'dash-h',
duration: 1900, duration: 1900,
message: `Deleted "${name}" from dashboard.`, message: `Deleted "${name}" from dashboard.`,
}) })
export const notifyBuilderDisabled = () => ({ export const notifyBuilderDisabled = (): Notification => ({
type: 'info', type: 'info',
icon: 'graphline', icon: 'graphline',
duration: 7500, duration: 7500,
@ -467,249 +521,276 @@ export const notifyBuilderDisabled = () => ({
// Template Variables & URL Queries // Template Variables & URL Queries
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyInvalidTempVarValueInURLQuery = ({key, value}) => ({ export const notifyInvalidTempVarValueInURLQuery = ({
key,
value,
}: TemplateUpdate): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'cube', icon: 'cube',
message: `Invalid URL query value of '${value}' supplied for template variable '${key}'.`, message: `Invalid URL query value of '${value}' supplied for template variable '${key}'.`,
}) })
export const notifyInvalidTimeRangeValueInURLQuery = () => ({ export const notifyInvalidTimeRangeValueInURLQuery = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'cube', icon: 'cube',
message: `Invalid URL query value supplied for lower or upper time range.`, message: `Invalid URL query value supplied for lower or upper time range.`,
}) })
export const notifyInvalidZoomedTimeRangeValueInURLQuery = () => ({ export const notifyInvalidZoomedTimeRangeValueInURLQuery = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
icon: 'cube', icon: 'cube',
message: `Invalid URL query value supplied for zoomed lower or zoomed upper time range.`, message: `Invalid URL query value supplied for zoomed lower or zoomed upper time range.`,
}) })
export const notifyViewerUnauthorizedToSetTempVars = () => ({ export const notifyViewerUnauthorizedToSetTempVars = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Viewer role unauthorized to override template variable values from URL.`, message: `Viewer role unauthorized to override template variable values from URL.`,
}) })
// Rule Builder Notifications // Rule Builder Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyAlertRuleCreated = ruleName => ({ export const notifyAlertRuleCreated = (ruleName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `${ruleName} created successfully.`, message: `${ruleName} created successfully.`,
}) })
export const notifyAlertRuleCreateFailed = (ruleName, errorMessage) => ({ export const notifyAlertRuleCreateFailed = (
ruleName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `There was a problem creating ${ruleName}: ${errorMessage}`, message: `There was a problem creating ${ruleName}: ${errorMessage}`,
}) })
export const notifyAlertRuleUpdated = ruleName => ({ export const notifyAlertRuleUpdated = (ruleName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `${ruleName} saved successfully.`, message: `${ruleName} saved successfully.`,
}) })
export const notifyAlertRuleUpdateFailed = (ruleName, errorMessage) => ({ export const notifyAlertRuleUpdateFailed = (
ruleName: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `There was a problem saving ${ruleName}: ${errorMessage}`, message: `There was a problem saving ${ruleName}: ${errorMessage}`,
}) })
export const notifyAlertRuleDeleted = ruleName => ({ export const notifyAlertRuleDeleted = (ruleName: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `${ruleName} deleted successfully.`, message: `${ruleName} deleted successfully.`,
}) })
export const notifyAlertRuleDeleteFailed = ruleName => ({ export const notifyAlertRuleDeleteFailed = (
ruleName: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `${ruleName} could not be deleted.`, message: `${ruleName} could not be deleted.`,
}) })
export const notifyAlertRuleStatusUpdated = (ruleName, updatedStatus) => ({ export const notifyAlertRuleStatusUpdated = (
ruleName: string,
updatedStatus: string
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `${ruleName} ${updatedStatus} successfully.`, message: `${ruleName} ${updatedStatus} successfully.`,
}) })
export const notifyAlertRuleStatusUpdateFailed = (ruleName, updatedStatus) => ({ export const notifyAlertRuleStatusUpdateFailed = (
ruleName: string,
updatedStatus: string
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `${ruleName} could not be ${updatedStatus}.`, message: `${ruleName} could not be ${updatedStatus}.`,
}) })
export const notifyAlertRuleRequiresQuery = () => export const notifyAlertRuleRequiresQuery = (): string =>
'Please select a Database, Measurement, and Field.' 'Please select a Database, Measurement, and Field.'
export const notifyAlertRuleRequiresConditionValue = () => export const notifyAlertRuleRequiresConditionValue = (): string =>
'Please enter a value in the Conditions section.' 'Please enter a value in the Conditions section.'
export const notifyAlertRuleDeadmanInvalid = () => export const notifyAlertRuleDeadmanInvalid = (): string =>
'Deadman rules require a Database and Measurement.' 'Deadman rules require a Database and Measurement.'
// Kapacitor Configuration Notifications // Kapacitor Configuration Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyKapacitorNameAlreadyTaken = kapacitorName => ({ export const notifyKapacitorNameAlreadyTaken = (
kapacitorName: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `There is already a Kapacitor Connection named "${kapacitorName}".`, message: `There is already a Kapacitor Connection named "${kapacitorName}".`,
}) })
export const notifyCouldNotFindKapacitor = () => ({ export const notifyCouldNotFindKapacitor = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'We could not find a Kapacitor configuration for this source.', message: 'We could not find a Kapacitor configuration for this source.',
}) })
export const notifyRefreshKapacitorFailed = () => ({ export const notifyRefreshKapacitorFailed = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'There was an error getting the Kapacitor configuration.', message: 'There was an error getting the Kapacitor configuration.',
}) })
export const notifyAlertEndpointSaved = endpoint => ({ export const notifyAlertEndpointSaved = (endpoint: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Alert configuration for ${endpoint} saved successfully.`, message: `Alert configuration for ${endpoint} saved successfully.`,
}) })
export const notifyAlertEndpointSaveFailed = (endpoint, errorMessage) => ({ export const notifyAlertEndpointSaveFailed = (
endpoint: string,
errorMessage: string
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `There was an error saving the alert configuration for ${endpoint}: ${errorMessage}`, message: `There was an error saving the alert configuration for ${endpoint}: ${errorMessage}`,
}) })
export const notifyAlertEndpointDeleteFailed = ( export const notifyAlertEndpointDeleteFailed = (
endpoint, endpoint: string,
config, config: string,
errorMessage errorMessage: string
) => ({ ): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `There was an error deleting the alert configuration for ${endpoint}/${config}: ${errorMessage}`, message: `There was an error deleting the alert configuration for ${endpoint}/${config}: ${errorMessage}`,
}) })
export const notifyAlertEndpointDeleted = (endpoint, config) => ({ export const notifyAlertEndpointDeleted = (
endpoint: string,
config: string
): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: `Alert configuration for ${endpoint}/${config} deleted successfully.`, message: `Alert configuration for ${endpoint}/${config} deleted successfully.`,
}) })
export const notifyTestAlertSent = endpoint => ({ export const notifyTestAlertSent = (endpoint: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
duration: TEN_SECONDS, duration: TEN_SECONDS,
message: `Test Alert sent to ${endpoint}. If the Alert does not reach its destination, please check your endpoint configuration settings.`, message: `Test Alert sent to ${endpoint}. If the Alert does not reach its destination, please check your endpoint configuration settings.`,
}) })
export const notifyTestAlertFailed = (endpoint, errorMessage?) => ({ export const notifyTestAlertFailed = (
endpoint: string,
errorMessage: string = ''
): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `There was an error sending a Test Alert to ${endpoint}${ message: `There was an error sending a Test Alert to ${endpoint} ${errorMessage}.`,
errorMessage ? `: ${errorMessage}` : '.'
}`,
}) })
export const notifyInvalidBatchSizeValue = () => ({ export const notifyInvalidBatchSizeValue = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Batch Size cannot be empty.', message: 'Batch Size cannot be empty.',
}) })
export const notifyKapacitorConnectionFailed = () => ({ export const notifyKapacitorConnectionFailed = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: message:
'Could not connect to Kapacitor. Check your connection settings in the Configuration page.', 'Could not connect to Kapacitor. Check your connection settings in the Configuration page.',
}) })
export const notifyKapacitorCreated = () => ({ export const notifyKapacitorCreated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: message:
'Connected to Kapacitor successfully! Configuring endpoints is optional.', 'Connected to Kapacitor successfully! Configuring endpoints is optional.',
}) })
export const notifyKapacitorCreateFailed = () => ({ export const notifyKapacitorCreateFailed = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'There was a problem connecting to Kapacitor.', message: 'There was a problem connecting to Kapacitor.',
}) })
export const notifyKapacitorUpdated = () => ({ export const notifyKapacitorUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Kapacitor Connection updated successfully.', message: 'Kapacitor Connection updated successfully.',
}) })
export const notifyKapacitorUpdateFailed = () => ({ export const notifyKapacitorUpdateFailed = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'There was a problem updating the Kapacitor Connection.', message: 'There was a problem updating the Kapacitor Connection.',
}) })
// TICKscript Notifications // TICKscript Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
export const notifyTickScriptCreated = () => ({ export const notifyTickScriptCreated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'TICKscript successfully created.', message: 'TICKscript successfully created.',
}) })
export const notifyTickscriptCreationFailed = () => export const notifyTickscriptCreationFailed = (): string =>
'Failed to create TICKscript.' 'Failed to create TICKscript.'
export const notifyTickscriptUpdated = () => ({ export const notifyTickscriptUpdated = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'TICKscript successfully updated.', message: 'TICKscript successfully updated.',
}) })
export const notifyTickscriptUpdateFailed = () => 'Failed to update TICKscript.' export const notifyTickscriptUpdateFailed = (): string =>
'Failed to update TICKscript.'
export const notifyTickscriptLoggingUnavailable = () => ({ export const notifyTickscriptLoggingUnavailable = (): Notification => ({
type: 'warning', type: 'warning',
icon: 'alert-triangle', icon: 'alert-triangle',
duration: INFINITE, duration: INFINITE,
message: 'Kapacitor version 1.4 required to view TICKscript logs', message: 'Kapacitor version 1.4 required to view TICKscript logs',
}) })
export const notifyTickscriptLoggingError = () => ({ export const notifyTickscriptLoggingError = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'Could not collect kapacitor logs', message: 'Could not collect kapacitor logs',
}) })
export const notifyKapacitorNotFound = () => ({ export const notifyKapacitorNotFound = (): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: 'We could not find a Kapacitor configuration for this source.', message: 'We could not find a Kapacitor configuration for this source.',
}) })
// Flux notifications // Flux notifications
export const validateSuccess = () => ({ export const validateSuccess = (): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'No errors found. Happy Happy Joy Joy!', message: 'No errors found. Happy Happy Joy Joy!',
}) })
export const notifyCopyToClipboardSuccess = text => ({ export const notifyCopyToClipboardSuccess = (text: string): Notification => ({
...defaultSuccessNotification, ...defaultSuccessNotification,
icon: 'dash-h', icon: 'dash-h',
message: `'${text}' has been copied to clipboard.`, message: `'${text}' has been copied to clipboard.`,
}) })
export const notifyCopyToClipboardFailed = text => ({ export const notifyCopyToClipboardFailed = (text: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `'${text}' was not copied to clipboard.`, message: `'${text}' was not copied to clipboard.`,
}) })
// Service notifications // Service notifications
export const couldNotGetServices = { export const couldNotGetServices: Notification = {
...defaultErrorNotification, ...defaultErrorNotification,
message: 'We could not get services', message: 'We could not get services',
} }
export const fluxCreated = { export const fluxCreated: Notification = {
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Flux Connection Created. Script your heart out!', message: 'Flux Connection Created. Script your heart out!',
} }
export const fluxNotCreated = (message: string) => ({ export const fluxNotCreated = (message: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message, message,
}) })
export const fluxNotUpdated = (message: string) => ({ export const fluxNotUpdated = (message: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message, message,
}) })
export const fluxUpdated = { export const fluxUpdated: Notification = {
...defaultSuccessNotification, ...defaultSuccessNotification,
message: 'Connection Updated. Rejoice!', message: 'Connection Updated. Rejoice!',
} }
export const fluxTimeSeriesError = (message: string) => ({ export const fluxTimeSeriesError = (message: string): Notification => ({
...defaultErrorNotification, ...defaultErrorNotification,
message: `Could not get data: ${message}`, message: `Could not get data: ${message}`,
}) })
export const fluxResponseTruncatedError = () => { export const fluxResponseTruncatedError = (): Notification => {
const BYTES_TO_MB = 1 / 1e6 const BYTES_TO_MB = 1 / 1e6
const APPROX_MAX_RESPONSE_MB = +(MAX_RESPONSE_BYTES * BYTES_TO_MB).toFixed(2) const APPROX_MAX_RESPONSE_MB = +(MAX_RESPONSE_BYTES * BYTES_TO_MB).toFixed(2)

View File

@ -6,6 +6,8 @@ interface TimeRangeOption extends TimeRange {
menuOption: string menuOption: string
} }
const nowMinus30d = 'now() - 30d'
export const timeRanges: TimeRangeOption[] = [ export const timeRanges: TimeRangeOption[] = [
{ {
defaultGroupBy: '10s', defaultGroupBy: '10s',
@ -75,7 +77,7 @@ export const timeRanges: TimeRangeOption[] = [
defaultGroupBy: '6h', defaultGroupBy: '6h',
seconds: 2592000, seconds: 2592000,
inputValue: 'Past 30d', inputValue: 'Past 30d',
lower: 'now() - 30d', lower: nowMinus30d,
upper: null, upper: null,
menuOption: 'Past 30d', menuOption: 'Past 30d',
}, },
@ -89,3 +91,7 @@ export const defaultTimeRange = {
seconds: 900, seconds: 900,
format: FORMAT_INFLUXQL, format: FORMAT_INFLUXQL,
} }
export const STATUS_PAGE_TIME_RANGE = timeRanges.find(
tr => tr.lower === nowMinus30d
)

View File

@ -0,0 +1,26 @@
import {getDeep} from 'src/utils/wrappers'
interface ParseShowSeriesResponse {
errors: string[]
series: string[]
}
const parseShowSeries = (response): ParseShowSeriesResponse => {
const results = response.results[0]
if (results.error) {
return {errors: [results.error], series: []}
}
const seriesValues = getDeep<string[]>(results, 'series.0.values', [])
if (!seriesValues.length) {
return {errors: [], series: []}
}
const series = seriesValues.map(s => s[0])
return {series, errors: []}
}
export default parseShowSeries

View File

@ -14,12 +14,12 @@ import {
notifySourceDeleteFailed, notifySourceDeleteFailed,
} from 'src/shared/copy/notifications' } from 'src/shared/copy/notifications'
import {Source, NotificationFunc} from 'src/types' import {Source, Notification} from 'src/types'
interface Props { interface Props {
source: Source source: Source
sources: Source[] sources: Source[]
notify: NotificationFunc notify: (n: Notification) => void
deleteKapacitor: actions.DeleteKapacitorAsync deleteKapacitor: actions.DeleteKapacitorAsync
fetchKapacitors: actions.FetchKapacitorsAsync fetchKapacitors: actions.FetchKapacitorsAsync
removeAndLoadSources: actions.RemoveAndLoadSources removeAndLoadSources: actions.RemoveAndLoadSources

View File

@ -11,7 +11,7 @@ import {
} from 'src/shared/actions/sources' } from 'src/shared/actions/sources'
import { import {
notify as notifyAction, notify as notifyAction,
PubishNotification, PublishNotification,
} from 'src/shared/actions/notifications' } from 'src/shared/actions/notifications'
import {connect} from 'react-redux' import {connect} from 'react-redux'
@ -34,7 +34,7 @@ import {
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props extends WithRouterProps { interface Props extends WithRouterProps {
notify: PubishNotification notify: PublishNotification
addSource: AddSource addSource: AddSource
updateSource: UpdateSource updateSource: UpdateSource
} }

View File

@ -1,49 +0,0 @@
// he is a library for safely encoding and decoding HTML Entities
import he from 'he'
import {fetchJSONFeed as fetchJSONFeedAJAX} from 'src/status/apis'
import {notify} from 'src/shared/actions/notifications'
import {notifyJSONFeedFailed} from 'src/shared/copy/notifications'
import * as actionTypes from 'src/status/constants/actionTypes'
const fetchJSONFeedRequested = () => ({
type: actionTypes.FETCH_JSON_FEED_REQUESTED,
})
const fetchJSONFeedCompleted = data => ({
type: actionTypes.FETCH_JSON_FEED_COMPLETED,
payload: {data},
})
const fetchJSONFeedFailed = () => ({
type: actionTypes.FETCH_JSON_FEED_FAILED,
})
export const fetchJSONFeedAsync = url => async dispatch => {
dispatch(fetchJSONFeedRequested())
try {
const {data} = await fetchJSONFeedAJAX(url)
// data could be from a webpage, and thus would be HTML
if (typeof data === 'string' || !data) {
dispatch(fetchJSONFeedFailed())
} else {
// decode HTML entities from response text
const decodedData = {
...data,
items: data.items.map(item => {
item.title = he.decode(item.title)
item.content_text = he.decode(item.content_text)
return item
}),
}
dispatch(fetchJSONFeedCompleted(decodedData))
}
} catch (error) {
console.error(error)
dispatch(fetchJSONFeedFailed())
dispatch(notify(notifyJSONFeedFailed(url)))
}
}

View File

@ -0,0 +1,78 @@
// he is a library for safely encoding and decoding HTML Entities
import he from 'he'
import {Dispatch} from 'redux'
import {fetchJSONFeed as fetchJSONFeedAJAX} from 'src/status/apis'
import {notify} from 'src/shared/actions/notifications'
import {notifyJSONFeedFailed} from 'src/shared/copy/notifications'
import {JSONFeedData} from 'src/types'
import {AxiosResponse} from 'axios'
export enum ActionTypes {
FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED',
FETCH_JSON_FEED_COMPLETED = 'FETCH_JSON_FEED_COMPLETED',
FETCH_JSON_FEED_FAILED = 'FETCH_JSON_FEED_FAILED',
}
interface FetchJSONFeedRequestedAction {
type: ActionTypes.FETCH_JSON_FEED_REQUESTED
}
interface FetchJSONFeedCompletedAction {
type: ActionTypes.FETCH_JSON_FEED_COMPLETED
payload: {data: JSONFeedData}
}
interface FetchJSONFeedFailedAction {
type: ActionTypes.FETCH_JSON_FEED_FAILED
}
export type Action =
| FetchJSONFeedRequestedAction
| FetchJSONFeedCompletedAction
| FetchJSONFeedFailedAction
const fetchJSONFeedRequested = (): FetchJSONFeedRequestedAction => ({
type: ActionTypes.FETCH_JSON_FEED_REQUESTED,
})
const fetchJSONFeedCompleted = (
data: JSONFeedData
): FetchJSONFeedCompletedAction => ({
type: ActionTypes.FETCH_JSON_FEED_COMPLETED,
payload: {data},
})
const fetchJSONFeedFailed = (): FetchJSONFeedFailedAction => ({
type: ActionTypes.FETCH_JSON_FEED_FAILED,
})
export const fetchJSONFeedAsync = (url: string) => async (
dispatch: Dispatch<Action>
): Promise<void> => {
dispatch(fetchJSONFeedRequested())
try {
const {data} = (await fetchJSONFeedAJAX(url)) as AxiosResponse<JSONFeedData>
// data could be from a webpage, and thus would be HTML
if (typeof data === 'string' || !data) {
dispatch(fetchJSONFeedFailed())
} else {
// decode HTML entities from response text
const decodedData = {
...data,
items: data.items.map(item => {
item.title = he.decode(item.title)
item.content_text = he.decode(item.content_text)
return item
}),
}
dispatch(fetchJSONFeedCompleted(decodedData))
}
} catch (error) {
console.error(error)
dispatch(fetchJSONFeedFailed())
dispatch(notify(notifyJSONFeedFailed(url)))
}
}

View File

@ -1,9 +1,10 @@
import AJAX from 'src/utils/ajax' import AJAX from 'src/utils/ajax'
import {JSONFeedData} from 'src/types'
const excludeBasepath = true // don't prefix route of external link with basepath/ const excludeBasepath = true // don't prefix route of external link with basepath/
export const fetchJSONFeed = url => export const fetchJSONFeed = (url: string) =>
AJAX( AJAX<JSONFeedData>(
{ {
method: 'GET', method: 'GET',
url, url,

View File

@ -1,15 +1,11 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling @ErrorHandling
class GettingStarted extends Component { class GettingStarted extends Component {
constructor(props) { public render() {
super(props)
}
render() {
return ( return (
<FancyScrollbar className="getting-started--container"> <FancyScrollbar className="getting-started--container">
<div className="getting-started"> <div className="getting-started">

View File

@ -1,9 +1,13 @@
import React from 'react' import React, {SFC} from 'react'
import PropTypes from 'prop-types' import {JSONFeedData} from 'src/types'
import moment from 'moment' import moment from 'moment'
const JSONFeedReader = ({data}) => interface Props {
data: JSONFeedData
}
const JSONFeedReader: SFC<Props> = ({data}) =>
data && data.items ? ( data && data.items ? (
<div className="newsfeed"> <div className="newsfeed">
{data.items {data.items
@ -38,24 +42,4 @@ const JSONFeedReader = ({data}) =>
</div> </div>
) : null ) : null
const {arrayOf, shape, string} = PropTypes
JSONFeedReader.propTypes = {
data: shape({
items: arrayOf(
shape({
author: shape({
name: string.isRequired,
}).isRequired,
content_text: string.isRequired,
date_published: string.isRequired,
id: string.isRequired,
image: string,
title: string.isRequired,
url: string.isRequired,
})
),
}).isRequired,
}
export default JSONFeedReader export default JSONFeedReader

View File

@ -1,23 +1,26 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {fetchJSONFeedAsync} from 'src/status/actions' import {fetchJSONFeedAsync} from 'src/status/actions'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import JSONFeedReader from 'src/status/components/JSONFeedReader' import JSONFeedReader from 'src/status/components/JSONFeedReader'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {JSONFeedData} from 'src/types'
interface Props {
hasCompletedFetchOnce: boolean
isFetching: boolean
isFailed: boolean
data: JSONFeedData
fetchJSONFeed: (statusFeedURL: string) => void
statusFeedURL: string
}
@ErrorHandling @ErrorHandling
class NewsFeed extends Component { class NewsFeed extends Component<Props> {
constructor(props) { public render() {
super(props)
}
// TODO: implement shouldComponentUpdate based on fetching conditions
render() {
const {hasCompletedFetchOnce, isFetching, isFailed, data} = this.props const {hasCompletedFetchOnce, isFetching, isFailed, data} = this.props
if (!hasCompletedFetchOnce) { if (!hasCompletedFetchOnce) {
@ -54,25 +57,13 @@ class NewsFeed extends Component {
} }
// TODO: implement interval polling a la AutoRefresh // TODO: implement interval polling a la AutoRefresh
componentDidMount() { public componentDidMount() {
const {statusFeedURL, fetchJSONFeed} = this.props const {statusFeedURL, fetchJSONFeed} = this.props
fetchJSONFeed(statusFeedURL) fetchJSONFeed(statusFeedURL)
} }
} }
const mstp = ({
const {bool, func, shape, string} = PropTypes
NewsFeed.propTypes = {
hasCompletedFetchOnce: bool.isRequired,
isFetching: bool.isRequired,
isFailed: bool.isRequired,
data: shape(),
fetchJSONFeed: func.isRequired,
statusFeedURL: string,
}
const mapStateToProps = ({
links: { links: {
external: {statusFeed: statusFeedURL}, external: {statusFeed: statusFeedURL},
}, },
@ -85,8 +76,8 @@ const mapStateToProps = ({
statusFeedURL, statusFeedURL,
}) })
const mapDispatchToProps = dispatch => ({ const mdtp = {
fetchJSONFeed: bindActionCreators(fetchJSONFeedAsync, dispatch), fetchJSONFeed: fetchJSONFeedAsync,
}) }
export default connect(mapStateToProps, mapDispatchToProps)(NewsFeed) export default connect(mstp, mdtp)(NewsFeed)

View File

@ -1,6 +0,0 @@
export const SET_STATUS_PAGE_AUTOREFRESH = 'SET_STATUS_PAGE_AUTOREFRESH'
export const SET_STATUS_PAGE_TIME_RANGE = 'SET_STATUS_PAGE_TIME_RANGE'
export const FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED'
export const FETCH_JSON_FEED_COMPLETED = 'FETCH_JSON_FEED_COMPLETED'
export const FETCH_JSON_FEED_FAILED = 'FETCH_JSON_FEED_FAILED'

View File

@ -1,11 +1,10 @@
import React, {Component} from 'react' import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import SourceIndicator from 'shared/components/SourceIndicator' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import FancyScrollbar from 'shared/components/FancyScrollbar' import LayoutRenderer from 'src/shared/components/LayoutRenderer'
import LayoutRenderer from 'shared/components/LayoutRenderer' import {STATUS_PAGE_TIME_RANGE} from 'src/shared/data/timeRanges'
import PageHeader from 'shared/components/PageHeader' import {AUTOREFRESH_DEFAULT} from 'src/shared/constants'
import PageHeader from 'src/shared/components/PageHeader'
import {fixtureStatusPageCells} from 'src/status/fixtures' import {fixtureStatusPageCells} from 'src/status/fixtures'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
@ -13,10 +12,22 @@ import {
TEMP_VAR_DASHBOARD_TIME, TEMP_VAR_DASHBOARD_TIME,
TEMP_VAR_UPPER_DASHBOARD_TIME, TEMP_VAR_UPPER_DASHBOARD_TIME,
} from 'src/shared/constants' } from 'src/shared/constants'
import {Source, Cell} from 'src/types'
interface State {
cells: Cell[]
}
interface Props {
source: Source
}
const autoRefresh = AUTOREFRESH_DEFAULT
const timeRange = STATUS_PAGE_TIME_RANGE
@ErrorHandling @ErrorHandling
class StatusPage extends Component { class StatusPage extends Component<Props, State> {
constructor(props) { constructor(props: Props) {
super(props) super(props)
this.state = { this.state = {
@ -24,8 +35,8 @@ class StatusPage extends Component {
} }
} }
render() { public render() {
const {source, autoRefresh, timeRange} = this.props const {source} = this.props
const {cells} = this.state const {cells} = this.state
const dashboardTime = { const dashboardTime = {
@ -59,9 +70,9 @@ class StatusPage extends Component {
return ( return (
<div className="page"> <div className="page">
<PageHeader <PageHeader
title="Status" titleText="Status"
fullWidth={true} fullWidth={true}
optionsComponents={this.optionsComponents()} sourceIndicator={true}
/> />
<FancyScrollbar className="page-contents"> <FancyScrollbar className="page-contents">
<div className="dashboard container-fluid full-width"> <div className="dashboard container-fluid full-width">
@ -84,30 +95,6 @@ class StatusPage extends Component {
</div> </div>
) )
} }
optionsComponents = () => {
return <SourceIndicator />
}
} }
const {number, shape, string} = PropTypes export default StatusPage
StatusPage.propTypes = {
source: shape({
name: string.isRequired,
links: shape({
proxy: string.isRequired,
}).isRequired,
}).isRequired,
autoRefresh: number.isRequired,
timeRange: shape({
lower: string.isRequired,
}).isRequired,
}
const mapStateToProps = ({statusUI: {autoRefresh, timeRange}}) => ({
autoRefresh,
timeRange,
})
export default connect(mapStateToProps, null)(StatusPage)

View File

@ -1,9 +1,38 @@
import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants/index'
import {DEFAULT_AXIS} from 'src/dashboards/constants/cellEditor'
import {Cell, CellQuery, Axes} from 'src/types'
import {CellType} from 'src/types/dashboard'
export const fixtureStatusPageCells = [ const emptyQuery: CellQuery = {
query: '',
source: '',
queryConfig: {
database: '',
measurement: '',
retentionPolicy: '',
fields: [],
tags: {},
groupBy: {},
areTagsAccepted: false,
rawText: null,
range: null,
},
}
const emptyAxes: Axes = {
x: DEFAULT_AXIS,
y: DEFAULT_AXIS,
y2: DEFAULT_AXIS,
}
export const fixtureStatusPageCells: Cell[] = [
{ {
...NEW_DEFAULT_DASHBOARD_CELL,
axes: emptyAxes,
i: 'alerts-bar-graph', i: 'alerts-bar-graph',
type: CellType.Bar,
isWidget: false, isWidget: false,
x: 0, x: 0,
y: 0, y: 0,
@ -15,7 +44,7 @@ export const fixtureStatusPageCells = [
queries: [ queries: [
{ {
query: `SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > ${TEMP_VAR_DASHBOARD_TIME} GROUP BY time(1d)`, query: `SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > ${TEMP_VAR_DASHBOARD_TIME} GROUP BY time(1d)`,
label: 'Events', source: '',
queryConfig: { queryConfig: {
database: 'chronograf', database: 'chronograf',
measurement: 'alerts', measurement: 'alerts',
@ -44,90 +73,56 @@ export const fixtureStatusPageCells = [
}, },
}, },
], ],
type: 'bar',
links: { links: {
self: '/chronograf/v1/status/23/cells/c-bar-graphs-fly', self: '/chronograf/v1/status/23/cells/c-bar-graphs-fly',
}, },
}, },
{ {
...NEW_DEFAULT_DASHBOARD_CELL,
axes: emptyAxes,
i: 'recent-alerts', i: 'recent-alerts',
type: CellType.Alerts,
isWidget: true, isWidget: true,
name: 'Alerts Last 30 Days', name: 'Alerts Last 30 Days',
type: 'alerts',
x: 0, x: 0,
y: 5, y: 5,
w: 6.5, w: 6.5,
h: 6, h: 6,
legend: {}, legend: {},
queries: [ queries: [emptyQuery],
{ colors: DEFAULT_LINE_COLORS,
query: '', links: {self: ''},
queryConfig: {
database: '',
measurement: '',
retentionPolicy: '',
fields: [],
tags: {},
groupBy: {},
areTagsAccepted: false,
rawText: null,
range: null,
},
},
],
}, },
{ {
...NEW_DEFAULT_DASHBOARD_CELL,
axes: emptyAxes,
i: 'news-feed', i: 'news-feed',
type: CellType.News,
isWidget: true, isWidget: true,
name: 'News Feed', name: 'News Feed',
type: 'news',
x: 6.5, x: 6.5,
y: 5, y: 5,
w: 3, w: 3,
h: 6, h: 6,
legend: {}, legend: {},
queries: [ queries: [emptyQuery],
{ colors: DEFAULT_LINE_COLORS,
query: '', links: {self: ''},
queryConfig: {
database: '',
measurement: '',
retentionPolicy: '',
fields: [],
tags: {},
groupBy: {},
areTagsAccepted: false,
rawText: null,
range: null,
},
},
],
}, },
{ {
...NEW_DEFAULT_DASHBOARD_CELL,
axes: emptyAxes,
i: 'getting-started', i: 'getting-started',
type: CellType.Guide,
isWidget: true, isWidget: true,
name: 'Getting Started', name: 'Getting Started',
type: 'guide',
x: 9.5, x: 9.5,
y: 5, y: 5,
w: 2.5, w: 2.5,
h: 6, h: 6,
legend: {}, legend: {},
queries: [ queries: [emptyQuery],
{ colors: DEFAULT_LINE_COLORS,
query: '', links: {self: ''},
queryConfig: {
database: '',
measurement: '',
retentionPolicy: '',
fields: [],
tags: {},
groupBy: {},
areTagsAccepted: false,
rawText: null,
range: null,
},
},
],
}, },
] ]

View File

@ -1,19 +1,30 @@
import * as actionTypes from 'src/status/constants/actionTypes' import {JSONFeedData} from 'src/types'
import {Action, ActionTypes} from 'src/status/actions'
const initialState = { export interface JSONFeedReducerState {
hasCompletedFetchOnce: boolean
isFetching: boolean
isFailed: boolean
data: JSONFeedData
}
const initialState: JSONFeedReducerState = {
hasCompletedFetchOnce: false, hasCompletedFetchOnce: false,
isFetching: false, isFetching: false,
isFailed: false, isFailed: false,
data: null, data: null,
} }
const JSONFeedReducer = (state = initialState, action) => { const JSONFeedReducer = (
state: JSONFeedReducerState = initialState,
action: Action
): JSONFeedReducerState => {
switch (action.type) { switch (action.type) {
case actionTypes.FETCH_JSON_FEED_REQUESTED: { case ActionTypes.FETCH_JSON_FEED_REQUESTED: {
return {...state, isFetching: true, isFailed: false} return {...state, isFetching: true, isFailed: false}
} }
case actionTypes.FETCH_JSON_FEED_COMPLETED: { case ActionTypes.FETCH_JSON_FEED_COMPLETED: {
const {data} = action.payload const {data} = action.payload
return { return {
@ -25,7 +36,7 @@ const JSONFeedReducer = (state = initialState, action) => {
} }
} }
case actionTypes.FETCH_JSON_FEED_FAILED: { case ActionTypes.FETCH_JSON_FEED_FAILED: {
return { return {
...state, ...state,
isFetching: false, isFetching: false,

View File

@ -1,7 +0,0 @@
import statusUI from './ui'
import JSONFeed from './JSONFeed'
export default {
statusUI,
JSONFeed,
}

View File

@ -0,0 +1,4 @@
import JSONFeed from 'src/status/reducers/JSONFeed'
export default {
JSONFeed,
}

View File

@ -1,36 +0,0 @@
import {AUTOREFRESH_DEFAULT} from 'shared/constants'
import {timeRanges} from 'shared/data/timeRanges'
import * as actionTypes from 'src/status/constants/actionTypes'
const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 30d')
const initialState = {
autoRefresh: AUTOREFRESH_DEFAULT,
timeRange: {lower, upper},
}
const statusUI = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SET_STATUS_PAGE_AUTOREFRESH: {
const {milliseconds} = action.payload
return {
...state,
autoRefresh: milliseconds,
}
}
case actionTypes.SET_STATUS_PAGE_TIME_RANGE: {
const {timeRange} = action.payload
return {...state, timeRange}
}
default: {
return state
}
}
}
export default statusUI

View File

@ -86,7 +86,7 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 6px; right: 6px;
transform: translateY(-50%); transform: translateY(-50%) rotate(180deg);
font-size: 13px; font-size: 13px;
opacity: 0; opacity: 0;
transition: opacity 0.25s ease, color 0.25s ease, transform 0.25s ease; transition: opacity 0.25s ease, color 0.25s ease, transform 0.25s ease;
@ -107,10 +107,10 @@
} }
} }
&__sort-asc:before { &__sort-asc:before {
transform: translateY(-50%) rotate(0deg); transform: translateY(-50%) rotate(180deg);
} }
&__sort-desc:before { &__sort-desc:before {
transform: translateY(-50%) rotate(180deg); transform: translateY(-50%) rotate(0deg);
} }
} }

View File

@ -0,0 +1,169 @@
import React, {PureComponent, ChangeEvent} from 'react'
import _ from 'lodash'
import {proxy} from 'src/utils/queryUrlGenerator'
import {ErrorHandling} from 'src/shared/decorators/errors'
import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview'
import {parseMetaQuery, isInvalidMetaQuery} from 'src/tempVars/utils/parsing'
import {getDeep} from 'src/utils/wrappers'
import {
TemplateBuilderProps,
RemoteDataState,
TemplateValueType,
} from 'src/types'
const DEBOUNCE_DELAY = 750
interface State {
metaQueryInput: string // bound to input
metaQuery: string // debounced view of metaQueryInput
metaQueryResults: string[]
metaQueryResultsStatus: RemoteDataState
}
@ErrorHandling
class CustomMetaQueryTemplateBuilder extends PureComponent<
TemplateBuilderProps,
State
> {
private handleMetaQueryChange: () => void = _.debounce(() => {
const {metaQuery, metaQueryInput} = this.state
if (metaQuery === metaQueryInput) {
return
}
this.setState({metaQuery: metaQueryInput}, this.executeQuery)
}, DEBOUNCE_DELAY)
constructor(props: TemplateBuilderProps) {
super(props)
const metaQuery = getDeep<string>(props.template, 'query.influxql', '')
this.state = {
metaQuery,
metaQueryInput: metaQuery,
metaQueryResults: [],
metaQueryResultsStatus: RemoteDataState.NotStarted,
}
}
public componentDidMount() {
this.executeQuery()
}
public render() {
const {metaQueryInput} = this.state
return (
<div className="temp-builder csv-temp-builder">
<div className="form-group">
<label>Meta Query</label>
<div className="temp-builder--mq-controls">
<textarea
className="form-control"
value={metaQueryInput}
onChange={this.handleMetaQueryInputChange}
onBlur={this.handleMetaQueryChange}
/>
</div>
</div>
{this.renderResults()}
</div>
)
}
private renderResults() {
const {metaQueryResults, metaQueryResultsStatus} = this.state
if (this.showInvalidMetaQueryMessage) {
return (
<div className="temp-builder-results">
<p className="error">Meta Query is not valid.</p>
</div>
)
}
return (
<TemplateMetaQueryPreview
items={metaQueryResults}
loadingStatus={metaQueryResultsStatus}
/>
)
}
private get showInvalidMetaQueryMessage(): boolean {
const {metaQuery} = this.state
return this.isInvalidMetaQuery && metaQuery !== ''
}
private get isInvalidMetaQuery(): boolean {
const {metaQuery} = this.state
return isInvalidMetaQuery(metaQuery)
}
private handleMetaQueryInputChange = (
e: ChangeEvent<HTMLTextAreaElement>
) => {
this.setState({metaQueryInput: e.target.value})
this.handleMetaQueryChange()
}
private executeQuery = async (): Promise<void> => {
const {template, source, onUpdateTemplate} = this.props
const {metaQuery} = this.state
if (this.isInvalidMetaQuery) {
return
}
this.setState({metaQueryResultsStatus: RemoteDataState.Loading})
try {
const {data} = await proxy({
source: source.links.proxy,
query: metaQuery,
})
const metaQueryResults = parseMetaQuery(metaQuery, data)
this.setState({
metaQueryResults,
metaQueryResultsStatus: RemoteDataState.Done,
})
const nextValues = metaQueryResults.map(result => {
return {
type: TemplateValueType.MetaQuery,
value: result,
selected: false,
}
})
if (nextValues[0]) {
nextValues[0].selected = true
}
const nextTemplate = {
...template,
values: nextValues,
query: {
influxql: metaQuery,
},
}
onUpdateTemplate(nextTemplate)
} catch {
this.setState({
metaQueryResults: [],
metaQueryResultsStatus: RemoteDataState.Error,
})
}
}
}
export default CustomMetaQueryTemplateBuilder

View File

@ -18,6 +18,7 @@ import MeasurementsTemplateBuilder from 'src/tempVars/components/MeasurementsTem
import FieldKeysTemplateBuilder from 'src/tempVars/components/FieldKeysTemplateBuilder' import FieldKeysTemplateBuilder from 'src/tempVars/components/FieldKeysTemplateBuilder'
import TagKeysTemplateBuilder from 'src/tempVars/components/TagKeysTemplateBuilder' import TagKeysTemplateBuilder from 'src/tempVars/components/TagKeysTemplateBuilder'
import TagValuesTemplateBuilder from 'src/tempVars/components/TagValuesTemplateBuilder' import TagValuesTemplateBuilder from 'src/tempVars/components/TagValuesTemplateBuilder'
import MetaQueryTemplateBuilder from 'src/tempVars/components/MetaQueryTemplateBuilder'
import { import {
Template, Template,
@ -59,6 +60,7 @@ const TEMPLATE_BUILDERS = {
[TemplateType.FieldKeys]: FieldKeysTemplateBuilder, [TemplateType.FieldKeys]: FieldKeysTemplateBuilder,
[TemplateType.TagKeys]: TagKeysTemplateBuilder, [TemplateType.TagKeys]: TagKeysTemplateBuilder,
[TemplateType.TagValues]: TagValuesTemplateBuilder, [TemplateType.TagValues]: TagValuesTemplateBuilder,
[TemplateType.MetaQuery]: MetaQueryTemplateBuilder,
} }
const formatName = name => `:${name.replace(/:/g, '')}:` const formatName = name => `:${name.replace(/:/g, '')}:`
@ -100,7 +102,9 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
return ( return (
<div className="edit-temp-var"> <div className="edit-temp-var">
<div className="edit-temp-var--header"> <div className="edit-temp-var--header">
<h1 className="page-header__title">Edit Template Variable</h1> <h1 className="page-header__title">
{isNew ? 'Create' : 'Edit'} Template Variable
</h1>
<div className="edit-temp-var--header-controls"> <div className="edit-temp-var--header-controls">
<button <button
className="btn btn-default" className="btn btn-default"
@ -115,7 +119,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
onClick={this.handleSave} onClick={this.handleSave}
disabled={!this.canSave} disabled={!this.canSave}
> >
{this.isSaving ? 'Saving...' : 'Save'} {this.saveButtonText}
</button> </button>
</div> </div>
</div> </div>
@ -182,10 +186,10 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
private handleChooseType = ({type}) => { private handleChooseType = ({type}) => {
const { const {
nextTemplate: {id}, nextTemplate: {id, tempVar},
} = this.state } = this.state
const nextNextTemplate = {...DEFAULT_TEMPLATES[type](), id} const nextNextTemplate = {...DEFAULT_TEMPLATES[type](), id, tempVar}
this.setState({nextTemplate: nextNextTemplate}) this.setState({nextTemplate: nextNextTemplate})
} }
@ -204,7 +208,12 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
private formatName = (): void => { private formatName = (): void => {
const {nextTemplate} = this.state const {nextTemplate} = this.state
const tempVar = formatName(nextTemplate.tempVar)
let tempVar = formatName(nextTemplate.tempVar)
if (tempVar === '::') {
tempVar = ''
}
this.setState({nextTemplate: {...nextTemplate, tempVar}}) this.setState({nextTemplate: {...nextTemplate, tempVar}})
} }
@ -272,6 +281,24 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
) )
} }
private get saveButtonText(): string {
const {isNew} = this.state
if (this.isSaving && isNew) {
return 'Creating...'
}
if (this.isSaving && !isNew) {
return 'Saving...'
}
if (!this.isSaving && isNew) {
return 'Create'
}
return 'Save'
}
private handleDelete = (): void => { private handleDelete = (): void => {
const {onDelete} = this.props const {onDelete} = this.props

View File

@ -34,6 +34,10 @@ export const TEMPLATE_TYPES_LIST: TemplateTypesListItem[] = [
text: 'CSV', text: 'CSV',
type: TemplateType.CSV, type: TemplateType.CSV,
}, },
{
text: 'Custom Meta Query',
type: TemplateType.MetaQuery,
},
] ]
export const TEMPLATE_VARIABLE_TYPES = { export const TEMPLATE_VARIABLE_TYPES = {
@ -43,6 +47,7 @@ export const TEMPLATE_VARIABLE_TYPES = {
[TemplateType.FieldKeys]: TemplateValueType.FieldKey, [TemplateType.FieldKeys]: TemplateValueType.FieldKey,
[TemplateType.TagKeys]: TemplateValueType.TagKey, [TemplateType.TagKeys]: TemplateValueType.TagKey,
[TemplateType.TagValues]: TemplateValueType.TagValue, [TemplateType.TagValues]: TemplateValueType.TagValue,
[TemplateType.MetaQuery]: TemplateValueType.MetaQuery,
} }
export const TEMPLATE_VARIABLE_QUERIES = { export const TEMPLATE_VARIABLE_QUERIES = {
@ -62,7 +67,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
[TemplateType.Databases]: () => { [TemplateType.Databases]: () => {
return { return {
id: uuid.v4(), id: uuid.v4(),
tempVar: ':my-databases:', tempVar: '',
values: [ values: [
{ {
value: '_internal', value: '_internal',
@ -80,7 +85,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
[TemplateType.Measurements]: () => { [TemplateType.Measurements]: () => {
return { return {
id: uuid.v4(), id: uuid.v4(),
tempVar: ':my-measurements:', tempVar: '',
values: [], values: [],
type: TemplateType.Measurements, type: TemplateType.Measurements,
label: '', label: '',
@ -93,7 +98,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
[TemplateType.CSV]: () => { [TemplateType.CSV]: () => {
return { return {
id: uuid.v4(), id: uuid.v4(),
tempVar: ':my-values:', tempVar: '',
values: [], values: [],
type: TemplateType.CSV, type: TemplateType.CSV,
label: '', label: '',
@ -103,7 +108,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
[TemplateType.TagKeys]: () => { [TemplateType.TagKeys]: () => {
return { return {
id: uuid.v4(), id: uuid.v4(),
tempVar: ':my-tag-keys:', tempVar: '',
values: [], values: [],
type: TemplateType.TagKeys, type: TemplateType.TagKeys,
label: '', label: '',
@ -115,7 +120,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
[TemplateType.FieldKeys]: () => { [TemplateType.FieldKeys]: () => {
return { return {
id: uuid.v4(), id: uuid.v4(),
tempVar: ':my-field-keys:', tempVar: '',
values: [], values: [],
type: TemplateType.FieldKeys, type: TemplateType.FieldKeys,
label: '', label: '',
@ -127,7 +132,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
[TemplateType.TagValues]: () => { [TemplateType.TagValues]: () => {
return { return {
id: uuid.v4(), id: uuid.v4(),
tempVar: ':my-tag-values:', tempVar: '',
values: [], values: [],
type: TemplateType.TagValues, type: TemplateType.TagValues,
label: '', label: '',
@ -136,6 +141,18 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
}, },
} }
}, },
[TemplateType.MetaQuery]: () => {
return {
id: uuid.v4(),
tempVar: ':my-meta-query:',
values: [],
type: TemplateType.MetaQuery,
label: '',
query: {
influxql: '',
},
}
},
} }
export const RESERVED_TEMPLATE_NAMES = [ export const RESERVED_TEMPLATE_NAMES = [

View File

@ -0,0 +1,84 @@
import parseShowDatabases from 'src/shared/parsing/showDatabases'
import parseShowFieldKeys from 'src/shared/parsing/showFieldKeys'
import parseShowTagKeys from 'src/shared/parsing/showTagKeys'
import parseShowTagValues from 'src/shared/parsing/showTagValues'
import parseShowMeasurements from 'src/shared/parsing/showMeasurements'
import parseShowSeries from 'src/shared/parsing/showSeries'
export const parseMetaQuery = (metaQuery: string, response): string[] => {
const metaQueryStart = getMetaQueryPrefix(metaQuery)
if (!metaQueryStart) {
throw new Error('Could not find parser for meta query')
}
const parser = PARSERS[metaQueryStart]
const extractor = EXTRACTORS[metaQueryStart]
const parsed = parser(response)
if (parsed.errors.length) {
throw new Error(parsed.errors)
}
return extractor(parsed)
}
export const isInvalidMetaQuery = (metaQuery: string): boolean =>
!getMetaQueryPrefix(metaQuery)
const getMetaQueryPrefix = (metaQuery: string): string | null => {
const words = metaQuery
.trim()
.toUpperCase()
.split(' ')
const firstTwoWords = words.slice(0, 2).join(' ')
const firstThreeWords = words.slice(0, 3).join(' ')
return VALID_META_QUERY_PREFIXES.find(
q => q === firstTwoWords || q === firstThreeWords
)
}
const VALID_META_QUERY_PREFIXES = [
'SHOW DATABASES',
'SHOW MEASUREMENTS',
'SHOW SERIES',
'SHOW TAG VALUES',
'SHOW FIELD KEYS',
'SHOW TAG KEYS',
]
const PARSERS = {
'SHOW DATABASES': parseShowDatabases,
'SHOW FIELD KEYS': parseShowFieldKeys,
'SHOW MEASUREMENTS': parseShowMeasurements,
'SHOW SERIES': parseShowSeries,
'SHOW TAG VALUES': parseShowTagValues,
'SHOW TAG KEYS': parseShowTagKeys,
}
const EXTRACTORS = {
'SHOW DATABASES': parsed => parsed.databases,
'SHOW FIELD KEYS': parsed => {
const {fieldSets} = parsed
const fieldSetsValues = Object.values(fieldSets) as string[]
return fieldSetsValues.reduce((acc, current) => [...acc, ...current], [])
},
'SHOW MEASUREMENTS': parsed => {
const {measurementSets} = parsed
return measurementSets.reduce(
(acc, current) => [...acc, ...current.measurements],
[]
)
},
'SHOW TAG KEYS': parsed => parsed.tagKeys,
'SHOW TAG VALUES': parsed => {
const {tags} = parsed
const tagsValues = Object.values(tags) as string[]
return tagsValues.reduce((acc, current) => [...acc, ...current], [])
},
'SHOW SERIES': parsed => parsed.series,
}

View File

@ -1,4 +1,8 @@
import * as kapacitorQueryConfigActions from 'src/kapacitor/actions/queryConfigs' import * as kapacitorQueryConfigActions from 'src/kapacitor/actions/queryConfigs'
import * as kapacitorRuleActionCreators from 'src/kapacitor/actions/view'
type KapacitorQueryConfigActions = typeof kapacitorQueryConfigActions type KapacitorQueryConfigActions = typeof kapacitorQueryConfigActions
type KapacitorRuleActions = typeof kapacitorRuleActionCreators
export {KapacitorQueryConfigActions} export {KapacitorQueryConfigActions}
export {KapacitorRuleActions}

View File

@ -3,12 +3,12 @@ import {ColorString} from 'src/types/colors'
import {Template} from 'src/types' import {Template} from 'src/types'
export interface Axis { export interface Axis {
bounds: [string, string]
label: string label: string
prefix: string prefix: string
suffix: string suffix: string
base: string base: string
scale: string scale: string
bounds?: [string, string]
} }
export type TimeSeriesValue = string | number | null | undefined export type TimeSeriesValue = string | number | null | undefined
@ -76,6 +76,7 @@ export interface Cell {
decimalPlaces: DecimalPlaces decimalPlaces: DecimalPlaces
links: CellLinks links: CellLinks
legend: Legend legend: Legend
isWidget?: boolean
} }
export enum CellType { export enum CellType {

View File

@ -44,6 +44,7 @@ import {
DygraphClass, DygraphClass,
DygraphData, DygraphData,
} from './dygraphs' } from './dygraphs'
import {JSONFeedData} from './status'
import {AnnotationInterface} from './annotations' import {AnnotationInterface} from './annotations'
export { export {
@ -102,6 +103,7 @@ export {
SchemaFilter, SchemaFilter,
RemoteDataState, RemoteDataState,
URLQueryParams, URLQueryParams,
JSONFeedData,
AnnotationInterface, AnnotationInterface,
TemplateType, TemplateType,
TemplateValueType, TemplateValueType,

View File

@ -32,6 +32,7 @@ export interface AlertRule {
error: string error: string
created: string created: string
modified: string modified: string
queryID?: string
'last-enabled'?: string 'last-enabled'?: string
} }
@ -186,7 +187,7 @@ interface OpsGenie {
} }
// Talk sends alerts to Jane Talk (https://jianliao.com/site) // Talk sends alerts to Jane Talk (https://jianliao.com/site)
interface Talk {} // tslint:disable-line interface Talk { } // tslint:disable-line
// TriggerValues specifies the alerting logic for a specific trigger type // TriggerValues specifies the alerting logic for a specific trigger type
interface TriggerValues { interface TriggerValues {
@ -224,7 +225,7 @@ export interface RuleMessageTemplate {
time: RuleMessage time: RuleMessage
} }
interface RuleMessage { export interface RuleMessage {
label: string label: string
text: string text: string
} }
@ -411,3 +412,20 @@ export interface RuleValues {
rangeValue?: string | null rangeValue?: string | null
operator?: string operator?: string
} }
export interface LogItem {
key: string
service: string
lvl: string
ts: string
msg: string
id: string
tags: string
method?: string
username?: string
host?: string
duration?: string
tag?: object
field?: object
cluster?: string
}

View File

@ -6,6 +6,6 @@ export interface Notification {
message: string message: string
} }
export type NotificationFunc = (message: any) => Notification export type NotificationFunc = (message: string) => Notification
export type NotificationAction = (message: Notification) => void export type NotificationAction = (message: Notification) => void

View File

@ -77,7 +77,7 @@ export interface Namespace {
} }
export interface Status { export interface Status {
loading?: string loading?: boolean
error?: string error?: string
warn?: string warn?: string
success?: string success?: string

22
ui/src/types/status.ts Normal file
View File

@ -0,0 +1,22 @@
interface JSONFeedDataItem {
id: string
url: string
title: string
content_text: string
date_published: string
date_modified: string
image: string
author: {
name: string
}
}
export interface JSONFeedData {
version: string
user_comment: string
home_page_url: string
feed_url: string
title: string
description: string
items: JSONFeedDataItem[]
}

View File

@ -9,6 +9,7 @@ export enum TemplateValueType {
CSV = 'csv', CSV = 'csv',
Points = 'points', Points = 'points',
Constant = 'constant', Constant = 'constant',
MetaQuery = 'influxql',
} }
export interface TemplateValue { export interface TemplateValue {
@ -34,8 +35,8 @@ export enum TemplateType {
TagKeys = 'tagKeys', TagKeys = 'tagKeys',
TagValues = 'tagValues', TagValues = 'tagValues',
CSV = 'csv', CSV = 'csv',
Query = 'query',
Databases = 'databases', Databases = 'databases',
MetaQuery = 'influxql',
} }
export interface Template { export interface Template {