commit
b778fd9756
|
@ -57,6 +57,9 @@
|
||||||
1. [#3412](https://github.com/influxdata/chronograf/pull/3412): Limit max-width of TICKScript editor
|
1. [#3412](https://github.com/influxdata/chronograf/pull/3412): Limit max-width of TICKScript editor
|
||||||
1. [#3166](https://github.com/influxdata/chronograf/pull/3166): Fix naming of new TICKScripts
|
1. [#3166](https://github.com/influxdata/chronograf/pull/3166): Fix naming of new TICKScripts
|
||||||
1. [#3449](https://github.com/influxdata/chronograf/pull/3449): Fix data explorer query error reporting regression
|
1. [#3449](https://github.com/influxdata/chronograf/pull/3449): Fix data explorer query error reporting regression
|
||||||
|
1. [#3412](https://github.com/influxdata/chronograf/pull/3412): Limit max-width of TICKScript editor.
|
||||||
|
1. [#3166](https://github.com/influxdata/chronograf/pull/3166): Fixes naming of new TICKScripts
|
||||||
|
1. [#3449](https://github.com/influxdata/chronograf/pull/3449): Fixes data explorer query error reporting regression
|
||||||
1. [#3453](https://github.com/influxdata/chronograf/pull/3453): Fix Kapacitor Logs fetch regression
|
1. [#3453](https://github.com/influxdata/chronograf/pull/3453): Fix Kapacitor Logs fetch regression
|
||||||
|
|
||||||
## v1.4.4.1 [2018-04-16]
|
## v1.4.4.1 [2018-04-16]
|
||||||
|
|
|
@ -76,6 +76,14 @@ func UnmarshalSource(data []byte, s *chronograf.Source) error {
|
||||||
|
|
||||||
// MarshalServer encodes a server to binary protobuf format.
|
// MarshalServer encodes a server to binary protobuf format.
|
||||||
func MarshalServer(s chronograf.Server) ([]byte, error) {
|
func MarshalServer(s chronograf.Server) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
metadata []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
metadata, err = json.Marshal(s.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return proto.Marshal(&Server{
|
return proto.Marshal(&Server{
|
||||||
ID: int64(s.ID),
|
ID: int64(s.ID),
|
||||||
SrcID: int64(s.SrcID),
|
SrcID: int64(s.SrcID),
|
||||||
|
@ -86,6 +94,8 @@ func MarshalServer(s chronograf.Server) ([]byte, error) {
|
||||||
Active: s.Active,
|
Active: s.Active,
|
||||||
Organization: s.Organization,
|
Organization: s.Organization,
|
||||||
InsecureSkipVerify: s.InsecureSkipVerify,
|
InsecureSkipVerify: s.InsecureSkipVerify,
|
||||||
|
Type: s.Type,
|
||||||
|
MetadataJSON: string(metadata),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +106,13 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Metadata = make(map[string]interface{})
|
||||||
|
if len(pb.MetadataJSON) > 0 {
|
||||||
|
if err := json.Unmarshal([]byte(pb.MetadataJSON), &s.Metadata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.ID = int(pb.ID)
|
s.ID = int(pb.ID)
|
||||||
s.SrcID = int(pb.SrcID)
|
s.SrcID = int(pb.SrcID)
|
||||||
s.Name = pb.Name
|
s.Name = pb.Name
|
||||||
|
@ -105,6 +122,7 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error {
|
||||||
s.Active = pb.Active
|
s.Active = pb.Active
|
||||||
s.Organization = pb.Organization
|
s.Organization = pb.Organization
|
||||||
s.InsecureSkipVerify = pb.InsecureSkipVerify
|
s.InsecureSkipVerify = pb.InsecureSkipVerify
|
||||||
|
s.Type = pb.Type
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -734,6 +734,8 @@ type Server struct {
|
||||||
Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"`
|
Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"`
|
||||||
Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"`
|
Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"`
|
||||||
InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"`
|
InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"`
|
||||||
|
Type string `protobuf:"bytes,10,opt,name=Type,proto3" json:"Type,omitempty"`
|
||||||
|
MetadataJSON string `protobuf:"bytes,11,opt,name=MetadataJSON,proto3" json:"MetadataJSON,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Server) Reset() { *m = Server{} }
|
func (m *Server) Reset() { *m = Server{} }
|
||||||
|
@ -804,6 +806,20 @@ func (m *Server) GetInsecureSkipVerify() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Server) GetType() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Type
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) GetMetadataJSON() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.MetadataJSON
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type Layout struct {
|
type Layout struct {
|
||||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"`
|
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"`
|
||||||
|
@ -1372,110 +1388,111 @@ func init() {
|
||||||
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
||||||
|
|
||||||
var fileDescriptorInternal = []byte{
|
var fileDescriptorInternal = []byte{
|
||||||
// 1667 bytes of a gzipped FileDescriptorProto
|
// 1685 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x6f, 0xe4, 0x48,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x8f, 0xe3, 0x48,
|
||||||
0x15, 0x96, 0xbb, 0xed, 0x4e, 0xfb, 0x74, 0x92, 0x8d, 0x8a, 0xd1, 0xae, 0x59, 0x10, 0x6a, 0x2c,
|
0x15, 0x96, 0x93, 0x38, 0x89, 0x4f, 0xd2, 0xbd, 0x2d, 0x33, 0xda, 0x35, 0x0b, 0x42, 0xc1, 0xe2,
|
||||||
0x2e, 0xe1, 0xb2, 0xc3, 0x2a, 0x2b, 0x24, 0xb4, 0xda, 0x5d, 0x29, 0x97, 0x9d, 0x21, 0x73, 0xcd,
|
0xd2, 0x5c, 0x76, 0x58, 0xf5, 0x0a, 0x09, 0xad, 0x76, 0x57, 0xea, 0xcb, 0xce, 0xd0, 0x73, 0xed,
|
||||||
0x54, 0x27, 0xc3, 0x13, 0x5a, 0x55, 0xdb, 0xd5, 0xdd, 0xa5, 0x75, 0xdb, 0xa6, 0x6c, 0x27, 0x31,
|
0xa9, 0x74, 0x0f, 0x4f, 0x68, 0x55, 0xb1, 0x2b, 0x49, 0x69, 0x1d, 0xdb, 0x94, 0xcb, 0xdd, 0x6d,
|
||||||
0xcf, 0xfc, 0x0e, 0x24, 0x24, 0xf8, 0x03, 0x88, 0x47, 0x24, 0xde, 0xf9, 0x01, 0xfc, 0x15, 0x78,
|
0x9e, 0xf9, 0x1d, 0x48, 0x48, 0xf0, 0x8e, 0x10, 0x8f, 0x48, 0xbc, 0xf3, 0x03, 0xf8, 0x2b, 0xbc,
|
||||||
0x44, 0xa7, 0x2e, 0xee, 0x72, 0xd2, 0x33, 0x1a, 0x24, 0xb4, 0x6f, 0xf5, 0x9d, 0x73, 0xfa, 0x54,
|
0xa2, 0x53, 0x17, 0xa7, 0xdc, 0x9d, 0x19, 0x0d, 0x12, 0xda, 0xb7, 0xfa, 0xce, 0x39, 0x39, 0x55,
|
||||||
0xd5, 0xb9, 0x7c, 0x75, 0xdc, 0xb0, 0x2f, 0xf2, 0x9a, 0xcb, 0x9c, 0x65, 0x0f, 0x4b, 0x59, 0xd4,
|
0x75, 0x2e, 0x5f, 0x1d, 0x07, 0xf6, 0x79, 0x2e, 0x99, 0xc8, 0x69, 0xf6, 0xb0, 0x14, 0x85, 0x2c,
|
||||||
0x05, 0x19, 0x5b, 0x1c, 0xff, 0x61, 0x08, 0xa3, 0x59, 0xd1, 0xc8, 0x84, 0x93, 0x7d, 0x18, 0x9c,
|
0xc2, 0xb1, 0xc5, 0xf1, 0x1f, 0xfa, 0x30, 0x9c, 0x17, 0xb5, 0x48, 0x58, 0xb8, 0x0f, 0xbd, 0xf3,
|
||||||
0x9f, 0x45, 0xde, 0xd4, 0x3b, 0x1c, 0xd2, 0xc1, 0xf9, 0x19, 0x21, 0xe0, 0xbf, 0x60, 0x6b, 0x1e,
|
0xb3, 0xc8, 0x9b, 0x79, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x0b, 0x43, 0x18, 0xbc, 0xa0, 0x1b, 0x16,
|
||||||
0x0d, 0xa6, 0xde, 0x61, 0x48, 0xd5, 0x1a, 0x65, 0x97, 0x6d, 0xc9, 0xa3, 0xa1, 0x96, 0xe1, 0x9a,
|
0xf5, 0x66, 0xde, 0x61, 0x40, 0xd4, 0x1a, 0x65, 0x97, 0x4d, 0xc9, 0xa2, 0xbe, 0x96, 0xe1, 0x3a,
|
||||||
0x7c, 0x08, 0xe3, 0xab, 0x0a, 0xbd, 0xad, 0x79, 0xe4, 0x2b, 0x79, 0x87, 0x51, 0x77, 0xc1, 0xaa,
|
0xfc, 0x10, 0xc6, 0x57, 0x15, 0x7a, 0xdb, 0xb0, 0x68, 0xa0, 0xe4, 0x2d, 0x46, 0xdd, 0x05, 0xad,
|
||||||
0xea, 0xa6, 0x90, 0x69, 0x14, 0x68, 0x9d, 0xc5, 0xe4, 0x00, 0x86, 0x57, 0xf4, 0x59, 0x34, 0x52,
|
0xaa, 0x9b, 0x42, 0xa4, 0x91, 0xaf, 0x75, 0x16, 0x87, 0x07, 0xd0, 0xbf, 0x22, 0xcf, 0xa2, 0xa1,
|
||||||
0x62, 0x5c, 0x92, 0x08, 0x76, 0xce, 0xf8, 0x82, 0x35, 0x59, 0x1d, 0xed, 0x4c, 0xbd, 0xc3, 0x31,
|
0x12, 0xe3, 0x32, 0x8c, 0x60, 0x74, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x34, 0x9a, 0x79, 0x87, 0x63,
|
||||||
0xb5, 0x10, 0xfd, 0x5c, 0xf2, 0x8c, 0x2f, 0x25, 0x5b, 0x44, 0x63, 0xed, 0xc7, 0x62, 0xf2, 0x10,
|
0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0xc6, 0xda, 0x8f, 0xc5, 0xe1, 0x43,
|
||||||
0xc8, 0x79, 0x5e, 0xf1, 0xa4, 0x91, 0x7c, 0xf6, 0xb5, 0x28, 0x5f, 0x73, 0x29, 0x16, 0x6d, 0x14,
|
0x08, 0xcf, 0xf3, 0x8a, 0x25, 0xb5, 0x60, 0xf3, 0xaf, 0x79, 0xf9, 0x9a, 0x09, 0xbe, 0x6c, 0xa2,
|
||||||
0x2a, 0x07, 0x5b, 0x34, 0xb8, 0xcb, 0x73, 0x5e, 0x33, 0xdc, 0x1b, 0x94, 0x2b, 0x0b, 0x49, 0x0c,
|
0x40, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x0c, 0x63,
|
||||||
0xbb, 0xb3, 0x15, 0x93, 0x3c, 0x9d, 0xf1, 0x44, 0xf2, 0x3a, 0x9a, 0x28, 0x75, 0x4f, 0x86, 0x36,
|
0x98, 0xce, 0xd7, 0x54, 0xb0, 0x74, 0xce, 0x12, 0xc1, 0x64, 0x34, 0x51, 0xea, 0x8e, 0x0c, 0x6d,
|
||||||
0x2f, 0xe5, 0x92, 0xe5, 0xe2, 0xf7, 0xac, 0x16, 0x45, 0x1e, 0xed, 0x6a, 0x1b, 0x57, 0x86, 0x51,
|
0x5e, 0x8a, 0x15, 0xcd, 0xf9, 0xef, 0xa9, 0xe4, 0x45, 0x1e, 0x4d, 0xb5, 0x8d, 0x2b, 0xc3, 0x28,
|
||||||
0xa2, 0x45, 0xc6, 0xa3, 0x3d, 0x1d, 0x25, 0x5c, 0x93, 0xef, 0x42, 0x68, 0x2e, 0x43, 0x2f, 0xa2,
|
0x91, 0x22, 0x63, 0xd1, 0x9e, 0x8e, 0x12, 0xae, 0xc3, 0xef, 0x42, 0x60, 0x2e, 0x43, 0x2e, 0xa2,
|
||||||
0x7d, 0xa5, 0xd8, 0x08, 0xe2, 0xbf, 0x79, 0x10, 0x9e, 0xb1, 0x6a, 0x35, 0x2f, 0x98, 0x4c, 0xdf,
|
0x7d, 0xa5, 0xd8, 0x0a, 0xe2, 0xbf, 0x7b, 0x10, 0x9c, 0xd1, 0x6a, 0xbd, 0x28, 0xa8, 0x48, 0xdf,
|
||||||
0x29, 0x13, 0x1f, 0x41, 0x90, 0xf0, 0x2c, 0xab, 0xa2, 0xe1, 0x74, 0x78, 0x38, 0x39, 0xfa, 0xe0,
|
0x29, 0x13, 0x1f, 0x81, 0x9f, 0xb0, 0x2c, 0xab, 0xa2, 0xfe, 0xac, 0x7f, 0x38, 0x39, 0xfa, 0xe0,
|
||||||
0x61, 0x97, 0xe2, 0xce, 0xcf, 0x29, 0xcf, 0x32, 0xaa, 0xad, 0xc8, 0xc7, 0x10, 0xd6, 0x7c, 0x5d,
|
0x61, 0x9b, 0xe2, 0xd6, 0xcf, 0x29, 0xcb, 0x32, 0xa2, 0xad, 0xc2, 0x8f, 0x21, 0x90, 0x6c, 0x53,
|
||||||
0x66, 0xac, 0xe6, 0x55, 0xe4, 0xab, 0x9f, 0x90, 0xcd, 0x4f, 0x2e, 0x8d, 0x8a, 0x6e, 0x8c, 0xee,
|
0x66, 0x54, 0xb2, 0x2a, 0x1a, 0xa8, 0x9f, 0x84, 0xdb, 0x9f, 0x5c, 0x1a, 0x15, 0xd9, 0x1a, 0xdd,
|
||||||
0x5d, 0x34, 0xb8, 0x7f, 0xd1, 0xf8, 0x5f, 0x3e, 0xec, 0xf5, 0xb6, 0x23, 0xbb, 0xe0, 0xdd, 0xaa,
|
0xbb, 0xa8, 0x7f, 0xff, 0xa2, 0xf1, 0xbf, 0x07, 0xb0, 0xd7, 0xd9, 0x2e, 0x9c, 0x82, 0x77, 0xab,
|
||||||
0x93, 0x07, 0xd4, 0xbb, 0x45, 0xd4, 0xaa, 0x53, 0x07, 0xd4, 0x6b, 0x11, 0xdd, 0xa8, 0xca, 0x09,
|
0x4e, 0xee, 0x13, 0xef, 0x16, 0x51, 0xa3, 0x4e, 0xed, 0x13, 0xaf, 0x41, 0x74, 0xa3, 0x2a, 0xc7,
|
||||||
0xa8, 0x77, 0x83, 0x68, 0xa5, 0xea, 0x25, 0xa0, 0xde, 0x8a, 0xfc, 0x04, 0x76, 0x7e, 0xd7, 0x70,
|
0x27, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x17, 0x9f, 0x78, 0xeb, 0xf0, 0x27, 0x30, 0xfa, 0x5d, 0xcd,
|
||||||
0x29, 0x78, 0x15, 0x05, 0xea, 0x74, 0xef, 0x6d, 0x4e, 0xf7, 0xaa, 0xe1, 0xb2, 0xa5, 0x56, 0x8f,
|
0x04, 0x67, 0x55, 0xe4, 0xab, 0xd3, 0xbd, 0xb7, 0x3d, 0xdd, 0xab, 0x9a, 0x89, 0x86, 0x58, 0x3d,
|
||||||
0xd1, 0x50, 0xb5, 0xa6, 0x0b, 0x47, 0xad, 0x51, 0x56, 0x63, 0x5d, 0xee, 0x68, 0x19, 0xae, 0x4d,
|
0x46, 0x43, 0xd5, 0x9a, 0x2e, 0x1c, 0xb5, 0x46, 0x99, 0xc4, 0xba, 0x1c, 0x69, 0x19, 0xae, 0x4d,
|
||||||
0x14, 0x75, 0xb5, 0x60, 0x14, 0x7f, 0x09, 0x3e, 0xbb, 0xe5, 0x55, 0x14, 0x2a, 0xff, 0xdf, 0x7f,
|
0x14, 0x75, 0xb5, 0x60, 0x14, 0x7f, 0x09, 0x03, 0x7a, 0xcb, 0xaa, 0x28, 0x50, 0xfe, 0xbf, 0xff,
|
||||||
0x43, 0xc0, 0x1e, 0x1e, 0xdf, 0xf2, 0xea, 0xcb, 0xbc, 0x96, 0x2d, 0x55, 0xe6, 0xe4, 0xc7, 0x30,
|
0x86, 0x80, 0x3d, 0x3c, 0xbe, 0x65, 0xd5, 0x97, 0xb9, 0x14, 0x0d, 0x51, 0xe6, 0xe1, 0x8f, 0x61,
|
||||||
0x4a, 0x8a, 0xac, 0x90, 0x55, 0x04, 0x77, 0x0f, 0x76, 0x8a, 0x72, 0x6a, 0xd4, 0xe4, 0x10, 0x46,
|
0x98, 0x14, 0x59, 0x21, 0xaa, 0x08, 0xee, 0x1e, 0xec, 0x14, 0xe5, 0xc4, 0xa8, 0xc3, 0x43, 0x18,
|
||||||
0x19, 0x5f, 0xf2, 0x3c, 0x55, 0x75, 0x33, 0x39, 0x3a, 0xd8, 0x18, 0x3e, 0x53, 0x72, 0x6a, 0xf4,
|
0x66, 0x6c, 0xc5, 0xf2, 0x54, 0xd5, 0xcd, 0xe4, 0xe8, 0x60, 0x6b, 0xf8, 0x4c, 0xc9, 0x89, 0xd1,
|
||||||
0xe4, 0x53, 0xd8, 0xad, 0xd9, 0x3c, 0xe3, 0x2f, 0x4b, 0x8c, 0x62, 0xa5, 0x6a, 0x68, 0x72, 0xf4,
|
0x87, 0x9f, 0xc2, 0x54, 0xd2, 0x45, 0xc6, 0x5e, 0x96, 0x18, 0xc5, 0x4a, 0xd5, 0xd0, 0xe4, 0xe8,
|
||||||
0xbe, 0x93, 0x0f, 0x47, 0x4b, 0x7b, 0xb6, 0xe4, 0x33, 0xd8, 0x5d, 0x08, 0x9e, 0xa5, 0xf6, 0xb7,
|
0x7d, 0x27, 0x1f, 0x8e, 0x96, 0x74, 0x6c, 0xc3, 0xcf, 0x60, 0xba, 0xe4, 0x2c, 0x4b, 0xed, 0x6f,
|
||||||
0x7b, 0xea, 0x50, 0xd1, 0xe6, 0xb7, 0x94, 0xe7, 0x6c, 0x8d, 0xbf, 0x78, 0x84, 0x66, 0xb4, 0x67,
|
0xf7, 0xd4, 0xa1, 0xa2, 0xed, 0x6f, 0x09, 0xcb, 0xe9, 0x06, 0x7f, 0xf1, 0x08, 0xcd, 0x48, 0xc7,
|
||||||
0x4d, 0xbe, 0x07, 0x50, 0x8b, 0x35, 0x7f, 0x54, 0xc8, 0x35, 0xab, 0x4d, 0x19, 0x3a, 0x12, 0xf2,
|
0x3a, 0xfc, 0x1e, 0x80, 0xe4, 0x1b, 0xf6, 0xa8, 0x10, 0x1b, 0x2a, 0x4d, 0x19, 0x3a, 0x92, 0xf0,
|
||||||
0x39, 0xec, 0xa5, 0x3c, 0x11, 0x6b, 0x96, 0x5d, 0x64, 0x2c, 0xe1, 0x55, 0xf4, 0x9e, 0x3a, 0x9a,
|
0x73, 0xd8, 0x4b, 0x59, 0xc2, 0x37, 0x34, 0xbb, 0xc8, 0x68, 0xc2, 0xaa, 0xe8, 0x3d, 0x75, 0x34,
|
||||||
0x5b, 0x5d, 0xae, 0x9a, 0xf6, 0xad, 0x3f, 0x7c, 0x0c, 0x61, 0x17, 0x3e, 0xec, 0xef, 0xaf, 0x79,
|
0xb7, 0xba, 0x5c, 0x35, 0xe9, 0x5a, 0x7f, 0xf8, 0x18, 0x82, 0x36, 0x7c, 0xd8, 0xdf, 0x5f, 0xb3,
|
||||||
0xab, 0x8a, 0x21, 0xa4, 0xb8, 0x24, 0x3f, 0x80, 0xe0, 0x9a, 0x65, 0x8d, 0x2e, 0xe4, 0xc9, 0xd1,
|
0x46, 0x15, 0x43, 0x40, 0x70, 0x19, 0xfe, 0x00, 0xfc, 0x6b, 0x9a, 0xd5, 0xba, 0x90, 0x27, 0x47,
|
||||||
0xfe, 0xc6, 0xeb, 0xf1, 0xad, 0xa8, 0xa8, 0x56, 0x7e, 0x3a, 0xf8, 0x95, 0x17, 0x3f, 0x86, 0xbd,
|
0xfb, 0x5b, 0xaf, 0xc7, 0xb7, 0xbc, 0x22, 0x5a, 0xf9, 0x69, 0xef, 0x57, 0x5e, 0xfc, 0x18, 0xf6,
|
||||||
0xde, 0x46, 0x78, 0x70, 0x51, 0x7d, 0x99, 0x2f, 0x0a, 0x99, 0xf0, 0x54, 0xf9, 0x1c, 0x53, 0x47,
|
0x3a, 0x1b, 0xe1, 0xc1, 0x79, 0xf5, 0x65, 0xbe, 0x2c, 0x44, 0xc2, 0x52, 0xe5, 0x73, 0x4c, 0x1c,
|
||||||
0x42, 0xde, 0x87, 0x51, 0x2a, 0x96, 0xa2, 0xae, 0x4c, 0xb9, 0x19, 0x14, 0xff, 0xdd, 0x83, 0x5d,
|
0x49, 0xf8, 0x3e, 0x0c, 0x53, 0xbe, 0xe2, 0xb2, 0x32, 0xe5, 0x66, 0x50, 0xfc, 0x0f, 0x0f, 0xa6,
|
||||||
0x37, 0x9a, 0xe4, 0xa7, 0x70, 0x70, 0xcd, 0x65, 0x2d, 0x12, 0x96, 0x5d, 0x8a, 0x35, 0xc7, 0x8d,
|
0x6e, 0x34, 0xc3, 0x9f, 0xc2, 0xc1, 0x35, 0x13, 0x92, 0x27, 0x34, 0xbb, 0xe4, 0x1b, 0x86, 0x1b,
|
||||||
0xd5, 0x4f, 0xc6, 0xf4, 0x9e, 0x9c, 0x7c, 0x0c, 0xa3, 0xaa, 0x90, 0xf5, 0x49, 0xab, 0xaa, 0xf6,
|
0xab, 0x9f, 0x8c, 0xc9, 0x3d, 0x79, 0xf8, 0x31, 0x0c, 0xab, 0x42, 0xc8, 0x93, 0x46, 0x55, 0xed,
|
||||||
0x6d, 0x51, 0x36, 0x76, 0xc8, 0x53, 0x37, 0x92, 0x95, 0xa5, 0xc8, 0x97, 0x96, 0x0b, 0x2d, 0x26,
|
0xdb, 0xa2, 0x6c, 0xec, 0x90, 0xa7, 0x6e, 0x04, 0x2d, 0x4b, 0x9e, 0xaf, 0x2c, 0x17, 0x5a, 0x1c,
|
||||||
0x3f, 0x82, 0xfd, 0x85, 0xb8, 0x7d, 0x24, 0x64, 0x55, 0x9f, 0x16, 0x59, 0xb3, 0xce, 0x55, 0x05,
|
0xfe, 0x08, 0xf6, 0x97, 0xfc, 0xf6, 0x11, 0x17, 0x95, 0x3c, 0x2d, 0xb2, 0x7a, 0x93, 0xab, 0x0a,
|
||||||
0x8f, 0xe9, 0x1d, 0xe9, 0x13, 0x7f, 0xec, 0x1d, 0x0c, 0x9e, 0xf8, 0xe3, 0xe0, 0x60, 0x14, 0x97,
|
0x1e, 0x93, 0x3b, 0xd2, 0x27, 0x83, 0xb1, 0x77, 0xd0, 0x7b, 0x32, 0x18, 0xfb, 0x07, 0xc3, 0xb8,
|
||||||
0xb0, 0xdf, 0xdf, 0x09, 0xdb, 0xd2, 0x1e, 0x42, 0x71, 0x82, 0x0e, 0x6f, 0x4f, 0x46, 0xa6, 0x30,
|
0x84, 0xfd, 0xee, 0x4e, 0xd8, 0x96, 0xf6, 0x10, 0x8a, 0x13, 0x74, 0x78, 0x3b, 0xb2, 0x70, 0x06,
|
||||||
0x49, 0x45, 0x55, 0x66, 0xac, 0x75, 0x68, 0xc3, 0x15, 0x21, 0x07, 0x5e, 0x8b, 0x4a, 0xcc, 0x33,
|
0x93, 0x94, 0x57, 0x65, 0x46, 0x1b, 0x87, 0x36, 0x5c, 0x11, 0x72, 0xe0, 0x35, 0xaf, 0xf8, 0x22,
|
||||||
0x4d, 0xe5, 0x63, 0x6a, 0x61, 0xbc, 0x84, 0x40, 0x95, 0xb5, 0x43, 0x42, 0xa1, 0x25, 0x21, 0x45,
|
0xd3, 0x54, 0x3e, 0x26, 0x16, 0xc6, 0x2b, 0xf0, 0x55, 0x59, 0x3b, 0x24, 0x14, 0x58, 0x12, 0x52,
|
||||||
0xfd, 0x03, 0x87, 0xfa, 0x0f, 0x60, 0xf8, 0x6b, 0x7e, 0x6b, 0x5e, 0x03, 0x5c, 0x76, 0x54, 0xe5,
|
0xd4, 0xdf, 0x73, 0xa8, 0xff, 0x00, 0xfa, 0xbf, 0x66, 0xb7, 0xe6, 0x35, 0xc0, 0x65, 0x4b, 0x55,
|
||||||
0x3b, 0x54, 0xf5, 0x00, 0x82, 0xd7, 0x2a, 0xed, 0x9a, 0x42, 0x34, 0x88, 0xbf, 0x80, 0x91, 0x6e,
|
0x03, 0x87, 0xaa, 0x1e, 0x80, 0xff, 0x5a, 0xa5, 0x5d, 0x53, 0x88, 0x06, 0xf1, 0x17, 0x30, 0xd4,
|
||||||
0x8b, 0xce, 0xb3, 0xe7, 0x78, 0x9e, 0xc2, 0xe4, 0xa5, 0x14, 0x3c, 0xaf, 0x35, 0xf9, 0x98, 0x2b,
|
0x6d, 0xd1, 0x7a, 0xf6, 0x1c, 0xcf, 0x33, 0x98, 0xbc, 0x14, 0x9c, 0xe5, 0x52, 0x93, 0x8f, 0xb9,
|
||||||
0x38, 0xa2, 0xf8, 0xaf, 0x1e, 0xf8, 0x2a, 0x4b, 0x31, 0xec, 0x66, 0x7c, 0xc9, 0x92, 0xf6, 0xa4,
|
0x82, 0x23, 0x8a, 0xff, 0xe6, 0xc1, 0x40, 0x65, 0x29, 0x86, 0x69, 0xc6, 0x56, 0x34, 0x69, 0x4e,
|
||||||
0x68, 0xf2, 0xb4, 0x8a, 0xbc, 0xe9, 0xf0, 0x70, 0x48, 0x7b, 0x32, 0x2c, 0x8f, 0xb9, 0xd6, 0x0e,
|
0x8a, 0x3a, 0x4f, 0xab, 0xc8, 0x9b, 0xf5, 0x0f, 0xfb, 0xa4, 0x23, 0xc3, 0xf2, 0x58, 0x68, 0x6d,
|
||||||
0xa6, 0xc3, 0xc3, 0x90, 0x1a, 0x84, 0x47, 0xcb, 0xd8, 0x9c, 0x67, 0xe6, 0x0a, 0x1a, 0xa0, 0x75,
|
0x6f, 0xd6, 0x3f, 0x0c, 0x88, 0x41, 0x78, 0xb4, 0x8c, 0x2e, 0x58, 0x66, 0xae, 0xa0, 0x01, 0x5a,
|
||||||
0x29, 0xf9, 0x42, 0xdc, 0x9a, 0x6b, 0x18, 0x84, 0xf2, 0xaa, 0x59, 0xa0, 0x5c, 0xdf, 0xc4, 0x20,
|
0x97, 0x82, 0x2d, 0xf9, 0xad, 0xb9, 0x86, 0x41, 0x28, 0xaf, 0xea, 0x25, 0xca, 0xf5, 0x4d, 0x0c,
|
||||||
0xbc, 0xc0, 0x9c, 0x55, 0x1d, 0x23, 0xe1, 0x1a, 0x3d, 0x57, 0x09, 0xcb, 0x2c, 0x25, 0x69, 0x10,
|
0xc2, 0x0b, 0x2c, 0x68, 0xd5, 0x32, 0x12, 0xae, 0xd1, 0x73, 0x95, 0xd0, 0xcc, 0x52, 0x92, 0x06,
|
||||||
0xff, 0xc3, 0xc3, 0x87, 0x4c, 0x53, 0xec, 0xbd, 0x08, 0x7f, 0x1b, 0xc6, 0x48, 0xbf, 0x5f, 0x5d,
|
0xf1, 0x3f, 0x3d, 0x7c, 0xc8, 0x34, 0xc5, 0xde, 0x8b, 0xf0, 0xb7, 0x61, 0x8c, 0xf4, 0xfb, 0xd5,
|
||||||
0x33, 0x69, 0x2e, 0xbc, 0x83, 0xf8, 0x35, 0x93, 0xe4, 0x17, 0x30, 0x52, 0xcd, 0xb1, 0x85, 0xee,
|
0x35, 0x15, 0xe6, 0xc2, 0x23, 0xc4, 0xaf, 0xa9, 0x08, 0x7f, 0x01, 0x43, 0xd5, 0x1c, 0x3b, 0xe8,
|
||||||
0xad, 0x3b, 0x15, 0x55, 0x6a, 0xcc, 0x3a, 0x42, 0xf4, 0x1d, 0x42, 0xec, 0x2e, 0x1b, 0xb8, 0x97,
|
0xde, 0xba, 0x53, 0x51, 0x25, 0xc6, 0xac, 0x25, 0xc4, 0x81, 0x43, 0x88, 0xed, 0x65, 0x7d, 0xf7,
|
||||||
0xfd, 0x08, 0x02, 0x64, 0xd6, 0x56, 0x9d, 0x7e, 0xab, 0x67, 0xcd, 0xbf, 0xda, 0x2a, 0xbe, 0x82,
|
0xb2, 0x1f, 0x81, 0x8f, 0xcc, 0xda, 0xa8, 0xd3, 0xef, 0xf4, 0xac, 0xf9, 0x57, 0x5b, 0xc5, 0x57,
|
||||||
0xbd, 0xde, 0x8e, 0xdd, 0x4e, 0x5e, 0x7f, 0xa7, 0x4d, 0xa3, 0x87, 0xa6, 0xb1, 0xb1, 0x39, 0x2a,
|
0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xc0, 0x34, 0x36, 0x36, 0x47,
|
||||||
0x9e, 0xf1, 0xa4, 0xe6, 0xa9, 0xa9, 0xba, 0x0e, 0xc7, 0x7f, 0xf2, 0x36, 0x7e, 0xd5, 0x7e, 0x58,
|
0xc5, 0x32, 0x96, 0x48, 0x96, 0x9a, 0xaa, 0x6b, 0x71, 0xfc, 0x27, 0x6f, 0xeb, 0x57, 0xed, 0x87,
|
||||||
0xa2, 0x49, 0xb1, 0x5e, 0xb3, 0x3c, 0x35, 0xae, 0x2d, 0xc4, 0xb8, 0xa5, 0x73, 0xe3, 0x7a, 0x90,
|
0x25, 0x9a, 0x14, 0x9b, 0x0d, 0xcd, 0x53, 0xe3, 0xda, 0x42, 0x8c, 0x5b, 0xba, 0x30, 0xae, 0x7b,
|
||||||
0xce, 0x11, 0xcb, 0xd2, 0x64, 0x70, 0x20, 0x4b, 0xac, 0x9d, 0x35, 0x67, 0x55, 0x23, 0xf9, 0x9a,
|
0xe9, 0x02, 0xb1, 0x28, 0x4d, 0x06, 0x7b, 0xa2, 0xc4, 0xda, 0xd9, 0x30, 0x5a, 0xd5, 0x82, 0x6d,
|
||||||
0xe7, 0xb5, 0x09, 0x81, 0x2b, 0x22, 0x1f, 0xc0, 0x4e, 0xcd, 0x96, 0x5f, 0x21, 0x3d, 0x99, 0x4c,
|
0x58, 0x2e, 0x4d, 0x08, 0x5c, 0x51, 0xf8, 0x01, 0x8c, 0x24, 0x5d, 0x7d, 0x85, 0xf4, 0x64, 0x32,
|
||||||
0xd6, 0x6c, 0xf9, 0x94, 0xb7, 0xe4, 0x3b, 0x10, 0x2a, 0xbe, 0x54, 0x2a, 0x9d, 0xce, 0xb1, 0x12,
|
0x29, 0xe9, 0xea, 0x29, 0x6b, 0xc2, 0xef, 0x40, 0xa0, 0xf8, 0x52, 0xa9, 0x74, 0x3a, 0xc7, 0x4a,
|
||||||
0x3c, 0xe5, 0x6d, 0xfc, 0x1f, 0x0f, 0x46, 0x33, 0x2e, 0xaf, 0xb9, 0x7c, 0xa7, 0x17, 0xda, 0x9d,
|
0xf0, 0x94, 0x35, 0xf1, 0x5f, 0x7b, 0x30, 0x9c, 0x33, 0x71, 0xcd, 0xc4, 0x3b, 0xbd, 0xd0, 0xee,
|
||||||
0x8b, 0x86, 0x6f, 0x99, 0x8b, 0xfc, 0xed, 0x73, 0x51, 0xb0, 0x99, 0x8b, 0x1e, 0x40, 0x30, 0x93,
|
0x5c, 0xd4, 0x7f, 0xcb, 0x5c, 0x34, 0xd8, 0x3d, 0x17, 0xf9, 0xdb, 0xb9, 0xe8, 0x01, 0xf8, 0x73,
|
||||||
0xc9, 0xf9, 0x99, 0x3a, 0xd1, 0x90, 0x6a, 0x80, 0xd5, 0x78, 0x9c, 0xd4, 0xe2, 0x9a, 0x9b, 0x61,
|
0x91, 0x9c, 0x9f, 0xa9, 0x13, 0xf5, 0x89, 0x06, 0x58, 0x8d, 0xc7, 0x89, 0xe4, 0xd7, 0xcc, 0x0c,
|
||||||
0xc9, 0xa0, 0x7b, 0x0f, 0xf7, 0x78, 0xcb, 0x84, 0xf2, 0x3f, 0xce, 0x4c, 0xf1, 0x1f, 0x3d, 0x18,
|
0x4b, 0x06, 0xdd, 0x7b, 0xb8, 0xc7, 0x3b, 0x26, 0x94, 0xff, 0x75, 0x66, 0xb2, 0x2d, 0x0a, 0x4e,
|
||||||
0x3d, 0x63, 0x6d, 0xd1, 0xd4, 0xf7, 0xaa, 0x76, 0x0a, 0x93, 0xe3, 0xb2, 0xcc, 0x44, 0xd2, 0xeb,
|
0x8b, 0xc6, 0x30, 0xc5, 0xc1, 0x29, 0xa5, 0x92, 0x3e, 0x99, 0xbf, 0x7c, 0x61, 0xa7, 0x25, 0x57,
|
||||||
0x54, 0x47, 0x84, 0x16, 0xcf, 0x9d, 0x7c, 0xe8, 0x58, 0xb8, 0x22, 0x7c, 0x18, 0x4e, 0xd5, 0x30,
|
0x16, 0xff, 0xd1, 0x83, 0xe1, 0x33, 0xda, 0x14, 0xb5, 0xbc, 0x57, 0xed, 0x33, 0x98, 0x1c, 0x97,
|
||||||
0xa3, 0x27, 0x13, 0xe7, 0x61, 0xd0, 0x33, 0x8c, 0x52, 0x62, 0xd0, 0x8e, 0x9b, 0xba, 0x58, 0x64,
|
0x65, 0xc6, 0x93, 0x4e, 0x87, 0x3b, 0x22, 0xb4, 0x78, 0xee, 0xe4, 0x51, 0xc7, 0xd0, 0x15, 0xe1,
|
||||||
0xc5, 0x8d, 0x8a, 0xce, 0x98, 0x76, 0x38, 0xfe, 0xe7, 0x00, 0xfc, 0x6f, 0x6a, 0x00, 0xd9, 0x05,
|
0x83, 0x72, 0xaa, 0x86, 0x20, 0x3d, 0xd1, 0x38, 0x0f, 0x8a, 0x9e, 0x7d, 0x94, 0x12, 0x83, 0x7d,
|
||||||
0x4f, 0x98, 0xe2, 0xf0, 0x44, 0x37, 0x8e, 0xec, 0x38, 0xe3, 0x48, 0x04, 0x3b, 0xad, 0x64, 0xf9,
|
0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xaa, 0x63, 0xd2, 0xe2, 0xf8, 0x5f, 0x3d, 0x18, 0x7c,
|
||||||
0x92, 0x57, 0xd1, 0x58, 0xb1, 0x91, 0x85, 0x4a, 0xa3, 0xfa, 0x4e, 0xcf, 0x21, 0x21, 0xb5, 0xb0,
|
0x53, 0x83, 0xcb, 0x14, 0x3c, 0x6e, 0x8a, 0xca, 0xe3, 0xed, 0x18, 0x33, 0x72, 0xc6, 0x98, 0x08,
|
||||||
0xeb, 0x23, 0x70, 0xfa, 0xe8, 0xe7, 0x66, 0x64, 0x99, 0xdc, 0x7d, 0xe4, 0xb7, 0x4d, 0x2a, 0xff,
|
0x46, 0x8d, 0xa0, 0xf9, 0x8a, 0x55, 0xd1, 0x58, 0xb1, 0x98, 0x85, 0x4a, 0xa3, 0xfa, 0x55, 0xcf,
|
||||||
0xbf, 0xd7, 0xf7, 0xdf, 0x1e, 0x04, 0x5d, 0x13, 0x9e, 0xf6, 0x9b, 0xf0, 0x74, 0xd3, 0x84, 0x67,
|
0x2f, 0x01, 0xb1, 0xb0, 0xed, 0x3f, 0x70, 0xfa, 0xef, 0xe7, 0x66, 0xd4, 0x99, 0xdc, 0x1d, 0x0e,
|
||||||
0x27, 0xb6, 0x09, 0xcf, 0x4e, 0x10, 0xd3, 0x0b, 0xdb, 0x84, 0xf4, 0x02, 0x93, 0xf5, 0x58, 0x16,
|
0x76, 0x4d, 0x38, 0xff, 0xbf, 0x57, 0xfb, 0x3f, 0x1e, 0xf8, 0x6d, 0xf3, 0x9e, 0x76, 0x9b, 0xf7,
|
||||||
0x4d, 0x79, 0xd2, 0xea, 0xac, 0x86, 0xb4, 0xc3, 0x58, 0xb9, 0xbf, 0x59, 0x71, 0x69, 0x42, 0x1d,
|
0x74, 0xdb, 0xbc, 0x67, 0x27, 0xb6, 0x79, 0xcf, 0x4e, 0x10, 0x93, 0x0b, 0xdb, 0xbc, 0xe4, 0x02,
|
||||||
0x52, 0x83, 0xb0, 0xce, 0x9f, 0x29, 0x82, 0xd2, 0xc1, 0xd5, 0x80, 0xfc, 0x10, 0x02, 0x8a, 0xc1,
|
0x93, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0xac, 0x06, 0xa4, 0xc5, 0x58, 0xf1, 0xbf, 0x59,
|
||||||
0x53, 0x11, 0xee, 0xe5, 0x45, 0x89, 0xa9, 0xd6, 0xa2, 0x53, 0xfd, 0x21, 0x63, 0x0a, 0xde, 0x7e,
|
0x33, 0x61, 0x42, 0x1d, 0x10, 0x83, 0xb0, 0x3f, 0x9e, 0x29, 0x62, 0xd3, 0xc1, 0xd5, 0x20, 0xfc,
|
||||||
0xd6, 0xfc, 0x0c, 0x46, 0xb3, 0x95, 0x58, 0xd4, 0x76, 0xf0, 0xfb, 0x96, 0x43, 0x70, 0x62, 0xcd,
|
0x21, 0xf8, 0x04, 0x83, 0xa7, 0x22, 0xdc, 0xc9, 0x8b, 0x12, 0x13, 0xad, 0x45, 0xa7, 0xfa, 0x03,
|
||||||
0x95, 0x8e, 0x1a, 0x93, 0xf8, 0x15, 0x84, 0x9d, 0x70, 0x73, 0x1c, 0xcf, 0x3d, 0x0e, 0x01, 0xff,
|
0xc8, 0x34, 0x8a, 0xfd, 0x1c, 0xfa, 0x19, 0x0c, 0xe7, 0x6b, 0xbe, 0x94, 0x76, 0x60, 0xfc, 0x96,
|
||||||
0x2a, 0x17, 0xb5, 0x6d, 0x75, 0x5c, 0xe3, 0x65, 0x5f, 0x35, 0x2c, 0xaf, 0x45, 0xdd, 0xda, 0x56,
|
0x43, 0x8c, 0x7c, 0xc3, 0x94, 0x8e, 0x18, 0x93, 0xf8, 0x15, 0x04, 0xad, 0x70, 0x7b, 0x1c, 0xcf,
|
||||||
0xb7, 0x38, 0xfe, 0xc4, 0x1c, 0x1f, 0xdd, 0x5d, 0x95, 0x25, 0x97, 0x86, 0x36, 0x34, 0x50, 0x9b,
|
0x3d, 0x4e, 0x08, 0x83, 0xab, 0x9c, 0x4b, 0x4b, 0x11, 0xb8, 0xc6, 0xcb, 0xbe, 0xaa, 0x69, 0x2e,
|
||||||
0x14, 0x37, 0x5c, 0x33, 0xfe, 0x90, 0x6a, 0x10, 0xff, 0x16, 0xc2, 0xe3, 0x8c, 0xcb, 0x9a, 0x36,
|
0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x1c, 0x7f, 0x62, 0x8e, 0x8f, 0xee, 0xae, 0xca, 0x92, 0x09, 0x43,
|
||||||
0x19, 0xdf, 0xf6, 0x12, 0x3f, 0x99, 0xbd, 0x7c, 0x61, 0x4f, 0x80, 0xeb, 0x0d, 0x45, 0x0c, 0xef,
|
0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0x5f, 0x8a, 0x3e, 0xd1, 0x20, 0xfe, 0x2d, 0x04, 0xc7,
|
||||||
0x50, 0xc4, 0x53, 0x56, 0xb2, 0xf3, 0x33, 0x55, 0xe7, 0x43, 0x6a, 0x50, 0xfc, 0x67, 0x0f, 0x7c,
|
0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0x82, 0xab, 0x46, 0x35, 0x27, 0xc0, 0xf5, 0x96, 0x5a,
|
||||||
0xe4, 0x22, 0xc7, 0xb5, 0xff, 0x36, 0x1e, 0xbb, 0x90, 0xc5, 0xb5, 0x48, 0xb9, 0xb4, 0x97, 0xb3,
|
0xfa, 0x77, 0xa8, 0xe5, 0x29, 0x2d, 0xe9, 0xf9, 0x99, 0xaa, 0xf3, 0x3e, 0x31, 0x28, 0xfe, 0xb3,
|
||||||
0x58, 0x05, 0x3d, 0x59, 0xf1, 0xee, 0xc1, 0x37, 0x08, 0x6b, 0x0d, 0xbf, 0x7a, 0x6c, 0x2f, 0x39,
|
0x07, 0x03, 0xe4, 0x30, 0xc7, 0xf5, 0xe0, 0x6d, 0xfc, 0x77, 0x21, 0x8a, 0x6b, 0x9e, 0x32, 0x61,
|
||||||
0xb5, 0x86, 0x62, 0xaa, 0x95, 0x38, 0xd4, 0xcd, 0x9a, 0x92, 0xcb, 0xe3, 0x74, 0x2d, 0xec, 0x34,
|
0x2f, 0x67, 0xb1, 0x0a, 0x7a, 0xb2, 0x66, 0xed, 0xa0, 0x60, 0x10, 0xd6, 0x1a, 0x7e, 0x2d, 0xd9,
|
||||||
0xe4, 0x48, 0xe2, 0x2f, 0xf4, 0x77, 0xd4, 0x3d, 0x46, 0xf3, 0xb6, 0x7f, 0x73, 0xdd, 0x3d, 0x79,
|
0x5e, 0x72, 0x6a, 0x0d, 0xc5, 0x44, 0x2b, 0x71, 0x18, 0x9c, 0xd7, 0x25, 0x13, 0xc7, 0xe9, 0x86,
|
||||||
0xfc, 0x17, 0x0f, 0x76, 0x9e, 0x9b, 0xe9, 0xcb, 0xbd, 0x85, 0xf7, 0xc6, 0x5b, 0x0c, 0x7a, 0xb7,
|
0xdb, 0x29, 0xca, 0x91, 0xc4, 0x5f, 0xe8, 0xef, 0xaf, 0x7b, 0x4c, 0xe8, 0xed, 0xfe, 0x56, 0xbb,
|
||||||
0x38, 0x82, 0x07, 0xd6, 0xa6, 0xb7, 0xbf, 0x8e, 0xc2, 0x56, 0x9d, 0x89, 0xa8, 0xdf, 0x25, 0xeb,
|
0x7b, 0xf2, 0xf8, 0x2f, 0x1e, 0x8c, 0x9e, 0x9b, 0xa9, 0xcd, 0xbd, 0x85, 0xf7, 0xc6, 0x5b, 0xf4,
|
||||||
0x5d, 0x3e, 0xa3, 0x2e, 0xfb, 0x36, 0xdb, 0x12, 0x7e, 0x2f, 0x2b, 0x53, 0x98, 0xd8, 0xcf, 0xc7,
|
0x3a, 0xb7, 0x38, 0x82, 0x07, 0xd6, 0xa6, 0xb3, 0xbf, 0x8e, 0xc2, 0x4e, 0x9d, 0x89, 0xe8, 0xa0,
|
||||||
0x22, 0xb3, 0x0f, 0x8c, 0x2b, 0x8a, 0x8f, 0x60, 0x74, 0x5a, 0xe4, 0x0b, 0xb1, 0x24, 0x87, 0xe0,
|
0x4d, 0xd6, 0xbb, 0x7c, 0x7e, 0x5d, 0x76, 0x6d, 0x76, 0x25, 0xfc, 0x5e, 0x56, 0x66, 0x30, 0xb1,
|
||||||
0x1f, 0x37, 0xf5, 0x4a, 0x79, 0x9c, 0x1c, 0x3d, 0x70, 0x1a, 0xbf, 0xa9, 0x57, 0xda, 0x86, 0x2a,
|
0x9f, 0x9d, 0x45, 0x66, 0x1f, 0x26, 0x57, 0x14, 0x1f, 0xc1, 0xf0, 0xb4, 0xc8, 0x97, 0x7c, 0x15,
|
||||||
0x8b, 0xf8, 0x33, 0x80, 0x8d, 0x0c, 0x5f, 0x89, 0x4d, 0x36, 0x5e, 0xf0, 0x1b, 0x2c, 0x99, 0xca,
|
0x1e, 0xc2, 0xe0, 0xb8, 0x96, 0x6b, 0xe5, 0x71, 0x72, 0xf4, 0xc0, 0x69, 0xfc, 0x5a, 0xae, 0xb5,
|
||||||
0x0c, 0xdf, 0x5b, 0x34, 0xf1, 0xe7, 0x10, 0x9e, 0x34, 0x22, 0x4b, 0xcf, 0xf3, 0x45, 0x81, 0xd4,
|
0x0d, 0x51, 0x16, 0xf1, 0x67, 0x00, 0x5b, 0x19, 0xbe, 0x2e, 0xdb, 0x6c, 0xbc, 0x60, 0x37, 0x58,
|
||||||
0xf1, 0x9a, 0xcb, 0x6a, 0x93, 0x2f, 0x0b, 0x31, 0xdc, 0xc8, 0x22, 0x5d, 0x0f, 0x19, 0x34, 0x1f,
|
0x32, 0x95, 0x19, 0xda, 0x77, 0x68, 0xe2, 0xcf, 0x21, 0x38, 0xa9, 0x79, 0x96, 0x9e, 0xe7, 0xcb,
|
||||||
0xa9, 0x3f, 0x27, 0x3e, 0xf9, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0xc4, 0xaa, 0x16, 0xae,
|
0x02, 0xa9, 0xe3, 0x35, 0x13, 0xd5, 0x36, 0x5f, 0x16, 0x62, 0xb8, 0x91, 0x45, 0xda, 0x1e, 0x32,
|
||||||
0x10, 0x00, 0x00,
|
0x68, 0x31, 0x54, 0x7f, 0x6a, 0x7c, 0xf2, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfb, 0xbd,
|
||||||
|
0x7b, 0xe6, 0x10, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,8 @@ message Server {
|
||||||
bool Active = 7; // is this the currently active server for the source
|
bool Active = 7; // is this the currently active server for the source
|
||||||
string Organization = 8; // Organization is the organization ID that resource belongs to
|
string Organization = 8; // Organization is the organization ID that resource belongs to
|
||||||
bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client
|
bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client
|
||||||
|
string Type = 10; // Type is the kind of the server (e.g. ifql)
|
||||||
|
string MetadataJSON = 11; // JSON byte representation of the metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
message Layout {
|
message Layout {
|
||||||
|
|
|
@ -368,6 +368,8 @@ type Server struct {
|
||||||
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the server is accepted.
|
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the server is accepted.
|
||||||
Active bool `json:"active"` // Is this the active server for the source?
|
Active bool `json:"active"` // Is this the active server for the source?
|
||||||
Organization string `json:"organization"` // Organization is the organization ID that resource belongs to
|
Organization string `json:"organization"` // Organization is the organization ID that resource belongs to
|
||||||
|
Type string `json:"type"` // Type is the kind of service (e.g. kapacitor or ifql)
|
||||||
|
Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServersStore stores connection information for a `Server`
|
// ServersStore stores connection information for a `Server`
|
||||||
|
|
|
@ -108,6 +108,7 @@ func TestServer(t *testing.T) {
|
||||||
"links": {
|
"links": {
|
||||||
"self": "/chronograf/v1/sources/5000",
|
"self": "/chronograf/v1/sources/5000",
|
||||||
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
|
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
|
||||||
|
"services": "/chronograf/v1/sources/5000/services",
|
||||||
"proxy": "/chronograf/v1/sources/5000/proxy",
|
"proxy": "/chronograf/v1/sources/5000/proxy",
|
||||||
"queries": "/chronograf/v1/sources/5000/queries",
|
"queries": "/chronograf/v1/sources/5000/queries",
|
||||||
"write": "/chronograf/v1/sources/5000/write",
|
"write": "/chronograf/v1/sources/5000/write",
|
||||||
|
@ -296,6 +297,7 @@ func TestServer(t *testing.T) {
|
||||||
"links": {
|
"links": {
|
||||||
"self": "/chronograf/v1/sources/5000",
|
"self": "/chronograf/v1/sources/5000",
|
||||||
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
|
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
|
||||||
|
"services": "/chronograf/v1/sources/5000/services",
|
||||||
"proxy": "/chronograf/v1/sources/5000/proxy",
|
"proxy": "/chronograf/v1/sources/5000/proxy",
|
||||||
"queries": "/chronograf/v1/sources/5000/queries",
|
"queries": "/chronograf/v1/sources/5000/queries",
|
||||||
"write": "/chronograf/v1/sources/5000/write",
|
"write": "/chronograf/v1/sources/5000/write",
|
||||||
|
@ -2943,7 +2945,7 @@ func TestServer(t *testing.T) {
|
||||||
serverURL := fmt.Sprintf("http://%v:%v%v", host, port, tt.args.path)
|
serverURL := fmt.Sprintf("http://%v:%v%v", host, port, tt.args.path)
|
||||||
|
|
||||||
// Wait for the server to come online
|
// Wait for the server to come online
|
||||||
timeout := time.Now().Add(5 * time.Second)
|
timeout := time.Now().Add(30 * time.Second)
|
||||||
for {
|
for {
|
||||||
_, err := http.Get(serverURL + "/swagger.json")
|
_, err := http.Get(serverURL + "/swagger.json")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -154,7 +154,7 @@ func (s *Service) Kapacitors(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
srvs := []kapacitor{}
|
srvs := []kapacitor{}
|
||||||
for _, srv := range mrSrvs {
|
for _, srv := range mrSrvs {
|
||||||
if srv.SrcID == srcID {
|
if srv.SrcID == srcID && srv.Type == "" {
|
||||||
srvs = append(srvs, newKapacitor(srv))
|
srvs = append(srvs, newKapacitor(srv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ func (s *Service) KapacitorsID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
||||||
if err != nil || srv.SrcID != srcID {
|
if err != nil || srv.SrcID != srcID || srv.Type != "" {
|
||||||
notFound(w, id, s.Logger)
|
notFound(w, id, s.Logger)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ func (s *Service) RemoveKapacitor(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
||||||
if err != nil || srv.SrcID != srcID {
|
if err != nil || srv.SrcID != srcID || srv.Type != "" {
|
||||||
notFound(w, id, s.Logger)
|
notFound(w, id, s.Logger)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ func (s *Service) UpdateKapacitor(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
||||||
if err != nil || srv.SrcID != srcID {
|
if err != nil || srv.SrcID != srcID || srv.Type != "" {
|
||||||
notFound(w, id, s.Logger)
|
notFound(w, id, s.Logger)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,6 +203,19 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
||||||
router.DELETE("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.RemoveSourceRole))
|
router.DELETE("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.RemoveSourceRole))
|
||||||
router.PATCH("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.UpdateSourceRole))
|
router.PATCH("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.UpdateSourceRole))
|
||||||
|
|
||||||
|
// Services are resources that chronograf proxies to
|
||||||
|
router.GET("/chronograf/v1/sources/:id/services", EnsureViewer(service.Services))
|
||||||
|
router.POST("/chronograf/v1/sources/:id/services", EnsureEditor(service.NewService))
|
||||||
|
router.GET("/chronograf/v1/sources/:id/services/:kid", EnsureViewer(service.ServiceID))
|
||||||
|
router.PATCH("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.UpdateService))
|
||||||
|
router.DELETE("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.RemoveService))
|
||||||
|
|
||||||
|
// Service Proxy
|
||||||
|
router.GET("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureViewer(service.ProxyGet))
|
||||||
|
router.POST("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPost))
|
||||||
|
router.PATCH("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPatch))
|
||||||
|
router.DELETE("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyDelete))
|
||||||
|
|
||||||
// Kapacitor
|
// Kapacitor
|
||||||
router.GET("/chronograf/v1/sources/:id/kapacitors", EnsureViewer(service.Kapacitors))
|
router.GET("/chronograf/v1/sources/:id/kapacitors", EnsureViewer(service.Kapacitors))
|
||||||
router.POST("/chronograf/v1/sources/:id/kapacitors", EnsureEditor(service.NewKapacitor))
|
router.POST("/chronograf/v1/sources/:id/kapacitors", EnsureEditor(service.NewKapacitor))
|
||||||
|
@ -221,10 +234,10 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
||||||
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesDelete))
|
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesDelete))
|
||||||
|
|
||||||
// Kapacitor Proxy
|
// Kapacitor Proxy
|
||||||
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.KapacitorProxyGet))
|
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.ProxyGet))
|
||||||
router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyPost))
|
router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPost))
|
||||||
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyPatch))
|
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPatch))
|
||||||
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyDelete))
|
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyDelete))
|
||||||
|
|
||||||
// Layouts
|
// Layouts
|
||||||
router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts))
|
router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts))
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KapacitorProxy proxies requests to kapacitor using the path query parameter.
|
// Proxy proxies requests to services using the path query parameter.
|
||||||
func (s *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) {
|
func (s *Service) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||||
srcID, err := paramID("id", r)
|
srcID, err := paramID("id", r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
@ -88,24 +88,24 @@ func (s *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
proxy.ServeHTTP(w, r)
|
proxy.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KapacitorProxyPost proxies POST to kapacitor
|
// ProxyPost proxies POST to service
|
||||||
func (s *Service) KapacitorProxyPost(w http.ResponseWriter, r *http.Request) {
|
func (s *Service) ProxyPost(w http.ResponseWriter, r *http.Request) {
|
||||||
s.KapacitorProxy(w, r)
|
s.Proxy(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KapacitorProxyPatch proxies PATCH to kapacitor
|
// ProxyPatch proxies PATCH to Service
|
||||||
func (s *Service) KapacitorProxyPatch(w http.ResponseWriter, r *http.Request) {
|
func (s *Service) ProxyPatch(w http.ResponseWriter, r *http.Request) {
|
||||||
s.KapacitorProxy(w, r)
|
s.Proxy(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KapacitorProxyGet proxies GET to kapacitor
|
// ProxyGet proxies GET to service
|
||||||
func (s *Service) KapacitorProxyGet(w http.ResponseWriter, r *http.Request) {
|
func (s *Service) ProxyGet(w http.ResponseWriter, r *http.Request) {
|
||||||
s.KapacitorProxy(w, r)
|
s.Proxy(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KapacitorProxyDelete proxies DELETE to kapacitor
|
// ProxyDelete proxies DELETE to service
|
||||||
func (s *Service) KapacitorProxyDelete(w http.ResponseWriter, r *http.Request) {
|
func (s *Service) ProxyDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
s.KapacitorProxy(w, r)
|
s.Proxy(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func singleJoiningSlash(a, b string) string {
|
func singleJoiningSlash(a, b string) string {
|
||||||
|
|
|
@ -0,0 +1,320 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/influxdata/chronograf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type postServiceRequest struct {
|
||||||
|
Name *string `json:"name"` // User facing name of service instance.; Required: true
|
||||||
|
URL *string `json:"url"` // URL for the service backend (e.g. http://localhost:9092);/ Required: true
|
||||||
|
Type *string `json:"type"` // Type is the kind of service (e.g. ifql); Required
|
||||||
|
Username string `json:"username,omitempty"` // Username for authentication to service
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted.
|
||||||
|
Organization string `json:"organization"` // Organization is the organization ID that resource belongs to
|
||||||
|
Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *postServiceRequest) Valid(defaultOrgID string) error {
|
||||||
|
if p.Name == nil || p.URL == nil {
|
||||||
|
return fmt.Errorf("name and url required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Type == nil {
|
||||||
|
return fmt.Errorf("type required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Organization == "" {
|
||||||
|
p.Organization = defaultOrgID
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := url.ParseRequestURI(*p.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid source URI: %v", err)
|
||||||
|
}
|
||||||
|
if len(url.Scheme) == 0 {
|
||||||
|
return fmt.Errorf("Invalid URL; no URL scheme defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceLinks struct {
|
||||||
|
Proxy string `json:"proxy"` // URL location of proxy endpoint for this source
|
||||||
|
Self string `json:"self"` // Self link mapping to this resource
|
||||||
|
Source string `json:"source"` // URL location of the parent source
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
ID int `json:"id,string"` // Unique identifier representing a service instance.
|
||||||
|
SrcID int `json:"sourceID,string"` // SrcID of the data source
|
||||||
|
Name string `json:"name"` // User facing name of service instance.
|
||||||
|
URL string `json:"url"` // URL for the service backend (e.g. http://localhost:9092)
|
||||||
|
Username string `json:"username,omitempty"` // Username for authentication to service
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted.
|
||||||
|
Type string `json:"type"` // Type is the kind of service (e.g. ifql)
|
||||||
|
Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service
|
||||||
|
Links serviceLinks `json:"links"` // Links are URI locations related to service
|
||||||
|
}
|
||||||
|
|
||||||
|
func newService(srv chronograf.Server) service {
|
||||||
|
if srv.Metadata == nil {
|
||||||
|
srv.Metadata = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
httpAPISrcs := "/chronograf/v1/sources"
|
||||||
|
return service{
|
||||||
|
ID: srv.ID,
|
||||||
|
SrcID: srv.SrcID,
|
||||||
|
Name: srv.Name,
|
||||||
|
Username: srv.Username,
|
||||||
|
URL: srv.URL,
|
||||||
|
InsecureSkipVerify: srv.InsecureSkipVerify,
|
||||||
|
Type: srv.Type,
|
||||||
|
Metadata: srv.Metadata,
|
||||||
|
Links: serviceLinks{
|
||||||
|
Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
|
Source: fmt.Sprintf("%s/%d", httpAPISrcs, srv.SrcID),
|
||||||
|
Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type services struct {
|
||||||
|
Services []service `json:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService adds valid service store store.
|
||||||
|
func (s *Service) NewService(w http.ResponseWriter, r *http.Request) {
|
||||||
|
srcID, err := paramID("id", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
_, err = s.Store.Sources(ctx).Get(ctx, srcID)
|
||||||
|
if err != nil {
|
||||||
|
notFound(w, srcID, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req postServiceRequest
|
||||||
|
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
invalidJSON(w, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx)
|
||||||
|
if err != nil {
|
||||||
|
unknownErrorWithMessage(w, err, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.Valid(defaultOrg.ID); err != nil {
|
||||||
|
invalidData(w, err, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := chronograf.Server{
|
||||||
|
SrcID: srcID,
|
||||||
|
Name: *req.Name,
|
||||||
|
Username: req.Username,
|
||||||
|
Password: req.Password,
|
||||||
|
InsecureSkipVerify: req.InsecureSkipVerify,
|
||||||
|
URL: *req.URL,
|
||||||
|
Organization: req.Organization,
|
||||||
|
Type: *req.Type,
|
||||||
|
Metadata: req.Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv, err = s.Store.Servers(ctx).Add(ctx, srv); err != nil {
|
||||||
|
msg := fmt.Errorf("Error storing service %v: %v", req, err)
|
||||||
|
unknownErrorWithMessage(w, msg, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := newService(srv)
|
||||||
|
location(w, res.Links.Self)
|
||||||
|
encodeJSON(w, http.StatusCreated, res, s.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Services retrieves all services from store.
|
||||||
|
func (s *Service) Services(w http.ResponseWriter, r *http.Request) {
|
||||||
|
srcID, err := paramID("id", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
mrSrvs, err := s.Store.Servers(ctx).All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusInternalServerError, "Error loading services", s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srvs := []service{}
|
||||||
|
for _, srv := range mrSrvs {
|
||||||
|
if srv.SrcID == srcID && srv.Type != "" {
|
||||||
|
srvs = append(srvs, newService(srv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := services{
|
||||||
|
Services: srvs,
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceID retrieves a service with ID from store.
|
||||||
|
func (s *Service) ServiceID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := paramID("kid", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srcID, err := paramID("id", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
||||||
|
if err != nil || srv.SrcID != srcID || srv.Type == "" {
|
||||||
|
notFound(w, id, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := newService(srv)
|
||||||
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveService deletes service from store.
|
||||||
|
func (s *Service) RemoveService(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := paramID("kid", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srcID, err := paramID("id", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
||||||
|
if err != nil || srv.SrcID != srcID || srv.Type == "" {
|
||||||
|
notFound(w, id, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.Store.Servers(ctx).Delete(ctx, srv); err != nil {
|
||||||
|
unknownErrorWithMessage(w, err, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
type patchServiceRequest struct {
|
||||||
|
Name *string `json:"name,omitempty"` // User facing name of service instance.
|
||||||
|
Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. ifql)
|
||||||
|
URL *string `json:"url,omitempty"` // URL for the service
|
||||||
|
Username *string `json:"username,omitempty"` // Username for service auth
|
||||||
|
Password *string `json:"password,omitempty"`
|
||||||
|
InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted.
|
||||||
|
Metadata *map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *patchServiceRequest) Valid() error {
|
||||||
|
if p.URL != nil {
|
||||||
|
url, err := url.ParseRequestURI(*p.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid source URI: %v", err)
|
||||||
|
}
|
||||||
|
if len(url.Scheme) == 0 {
|
||||||
|
return fmt.Errorf("Invalid URL; no URL scheme defined")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Type != nil && *p.Type == "" {
|
||||||
|
return fmt.Errorf("Invalid type; type must not be an empty string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateService incrementally updates a service definition in the store
|
||||||
|
func (s *Service) UpdateService(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := paramID("kid", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srcID, err := paramID("id", r)
|
||||||
|
if err != nil {
|
||||||
|
Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
srv, err := s.Store.Servers(ctx).Get(ctx, id)
|
||||||
|
if err != nil || srv.SrcID != srcID || srv.Type == "" {
|
||||||
|
notFound(w, id, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req patchServiceRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
invalidJSON(w, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.Valid(); err != nil {
|
||||||
|
invalidData(w, err, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name != nil {
|
||||||
|
srv.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Type != nil {
|
||||||
|
srv.Type = *req.Type
|
||||||
|
}
|
||||||
|
if req.URL != nil {
|
||||||
|
srv.URL = *req.URL
|
||||||
|
}
|
||||||
|
if req.Password != nil {
|
||||||
|
srv.Password = *req.Password
|
||||||
|
}
|
||||||
|
if req.Username != nil {
|
||||||
|
srv.Username = *req.Username
|
||||||
|
}
|
||||||
|
if req.InsecureSkipVerify != nil {
|
||||||
|
srv.InsecureSkipVerify = *req.InsecureSkipVerify
|
||||||
|
}
|
||||||
|
if req.Metadata != nil {
|
||||||
|
srv.Metadata = *req.Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Store.Servers(ctx).Update(ctx, srv); err != nil {
|
||||||
|
msg := fmt.Sprintf("Error updating service ID %d", id)
|
||||||
|
Error(w, http.StatusInternalServerError, msg, s.Logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := newService(srv)
|
||||||
|
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import (
|
||||||
type sourceLinks struct {
|
type sourceLinks struct {
|
||||||
Self string `json:"self"` // Self link mapping to this resource
|
Self string `json:"self"` // Self link mapping to this resource
|
||||||
Kapacitors string `json:"kapacitors"` // URL for kapacitors endpoint
|
Kapacitors string `json:"kapacitors"` // URL for kapacitors endpoint
|
||||||
|
Services string `json:"services"` // URL for services endpoint
|
||||||
Proxy string `json:"proxy"` // URL for proxy endpoint
|
Proxy string `json:"proxy"` // URL for proxy endpoint
|
||||||
Queries string `json:"queries"` // URL for the queries analysis endpoint
|
Queries string `json:"queries"` // URL for the queries analysis endpoint
|
||||||
Write string `json:"write"` // URL for the write line-protocol endpoint
|
Write string `json:"write"` // URL for the write line-protocol endpoint
|
||||||
|
@ -49,6 +50,7 @@ func newSourceResponse(src chronograf.Source) sourceResponse {
|
||||||
Links: sourceLinks{
|
Links: sourceLinks{
|
||||||
Self: fmt.Sprintf("%s/%d", httpAPISrcs, src.ID),
|
Self: fmt.Sprintf("%s/%d", httpAPISrcs, src.ID),
|
||||||
Kapacitors: fmt.Sprintf("%s/%d/kapacitors", httpAPISrcs, src.ID),
|
Kapacitors: fmt.Sprintf("%s/%d/kapacitors", httpAPISrcs, src.ID),
|
||||||
|
Services: fmt.Sprintf("%s/%d/services", httpAPISrcs, src.ID),
|
||||||
Proxy: fmt.Sprintf("%s/%d/proxy", httpAPISrcs, src.ID),
|
Proxy: fmt.Sprintf("%s/%d/proxy", httpAPISrcs, src.ID),
|
||||||
Queries: fmt.Sprintf("%s/%d/queries", httpAPISrcs, src.ID),
|
Queries: fmt.Sprintf("%s/%d/queries", httpAPISrcs, src.ID),
|
||||||
Write: fmt.Sprintf("%s/%d/write", httpAPISrcs, src.ID),
|
Write: fmt.Sprintf("%s/%d/write", httpAPISrcs, src.ID),
|
||||||
|
|
|
@ -175,6 +175,7 @@ func Test_newSourceResponse(t *testing.T) {
|
||||||
},
|
},
|
||||||
Links: sourceLinks{
|
Links: sourceLinks{
|
||||||
Self: "/chronograf/v1/sources/1",
|
Self: "/chronograf/v1/sources/1",
|
||||||
|
Services: "/chronograf/v1/sources/1/services",
|
||||||
Proxy: "/chronograf/v1/sources/1/proxy",
|
Proxy: "/chronograf/v1/sources/1/proxy",
|
||||||
Queries: "/chronograf/v1/sources/1/queries",
|
Queries: "/chronograf/v1/sources/1/queries",
|
||||||
Write: "/chronograf/v1/sources/1/write",
|
Write: "/chronograf/v1/sources/1/write",
|
||||||
|
@ -201,6 +202,7 @@ func Test_newSourceResponse(t *testing.T) {
|
||||||
Links: sourceLinks{
|
Links: sourceLinks{
|
||||||
Self: "/chronograf/v1/sources/1",
|
Self: "/chronograf/v1/sources/1",
|
||||||
Proxy: "/chronograf/v1/sources/1/proxy",
|
Proxy: "/chronograf/v1/sources/1/proxy",
|
||||||
|
Services: "/chronograf/v1/sources/1/services",
|
||||||
Queries: "/chronograf/v1/sources/1/queries",
|
Queries: "/chronograf/v1/sources/1/queries",
|
||||||
Write: "/chronograf/v1/sources/1/write",
|
Write: "/chronograf/v1/sources/1/write",
|
||||||
Kapacitors: "/chronograf/v1/sources/1/kapacitors",
|
Kapacitors: "/chronograf/v1/sources/1/kapacitors",
|
||||||
|
@ -440,7 +442,7 @@ func TestService_SourcesID(t *testing.T) {
|
||||||
ID: "1",
|
ID: "1",
|
||||||
wantStatusCode: 200,
|
wantStatusCode: 200,
|
||||||
wantContentType: "application/json",
|
wantContentType: "application/json",
|
||||||
wantBody: `{"id":"1","name":"","url":"","default":false,"telegraf":"telegraf","organization":"","defaultRP":"","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}}
|
wantBody: `{"id":"1","name":"","url":"","default":false,"telegraf":"telegraf","organization":"","defaultRP":"","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","services":"/chronograf/v1/sources/1/services","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -530,7 +532,7 @@ func TestService_UpdateSource(t *testing.T) {
|
||||||
wantStatusCode: 200,
|
wantStatusCode: 200,
|
||||||
wantContentType: "application/json",
|
wantContentType: "application/json",
|
||||||
wantBody: func(url string) string {
|
wantBody: func(url string) string {
|
||||||
return fmt.Sprintf(`{"id":"1","name":"marty","type":"influx","username":"bob","url":"%s","metaUrl":"http://murl","default":false,"telegraf":"murlin","organization":"1337","defaultRP":"pineapple","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}}
|
return fmt.Sprintf(`{"id":"1","name":"marty","type":"influx","username":"bob","url":"%s","metaUrl":"http://murl","default":false,"telegraf":"murlin","organization":"1337","defaultRP":"pineapple","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","services":"/chronograf/v1/sources/1/services","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}}
|
||||||
`, url)
|
`, url)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,3 +5,4 @@ export const getAST = jest.fn(() => Promise.resolve({}))
|
||||||
export const getDatabases = jest.fn(() =>
|
export const getDatabases = jest.fn(() =>
|
||||||
Promise.resolve(['db1', 'db2', 'db3'])
|
Promise.resolve(['db1', 'db2', 'db3'])
|
||||||
)
|
)
|
||||||
|
export const getTimeSeries = jest.fn(() => Promise.resolve({data: ''}))
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"url": "github:influxdata/chronograf"
|
"url": "github:influxdata/chronograf"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn run clean && webpack --config ./webpack/prod.config.js",
|
"build": "yarn run clean && webpack --config ./webpack/prod.config.js --display-error-details",
|
||||||
"build:dev": "webpack --config ./webpack/dev.config.js",
|
"build:dev": "webpack --config ./webpack/dev.config.js",
|
||||||
"build:vendor": "webpack --config webpack/vendor.config.js",
|
"build:vendor": "webpack --config webpack/vendor.config.js",
|
||||||
"start": "yarn run clean && yarn run build:vendor && webpack --watch --config ./webpack/dev.config.js --progress",
|
"start": "yarn run clean && yarn run build:vendor && webpack --watch --config ./webpack/dev.config.js --progress",
|
||||||
|
|
|
@ -34,6 +34,20 @@ export const getAST = async (request: ASTRequest) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTimeSeries = async (script: string) => {
|
||||||
|
try {
|
||||||
|
const data = await AJAX({
|
||||||
|
method: 'POST',
|
||||||
|
url: `http://localhost:8093/query?q=${script}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Problem fetching data', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: replace with actual requests to IFQL daemon
|
// TODO: replace with actual requests to IFQL daemon
|
||||||
export const getDatabases = async () => {
|
export const getDatabases = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -218,10 +218,9 @@ export default class Walker {
|
||||||
return [...this.walk(currentNode.argument), {name, args, source}]
|
return [...this.walk(currentNode.argument), {name, args, source}]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNode.type === 'ArrowFunctionExpression') {
|
if (currentNode.type === 'Identifier') {
|
||||||
const params = currentNode.params
|
name = currentNode.name
|
||||||
const body = currentNode.body
|
return [{name, source}]
|
||||||
return [{name, params, body}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name = currentNode.callee.name
|
name = currentNode.callee.name
|
||||||
|
|
|
@ -4,12 +4,15 @@ import _ from 'lodash'
|
||||||
import ExpressionNode from 'src/ifql/components/ExpressionNode'
|
import ExpressionNode from 'src/ifql/components/ExpressionNode'
|
||||||
import VariableName from 'src/ifql/components/VariableName'
|
import VariableName from 'src/ifql/components/VariableName'
|
||||||
import FuncSelector from 'src/ifql/components/FuncSelector'
|
import FuncSelector from 'src/ifql/components/FuncSelector'
|
||||||
|
import {funcNames} from 'src/ifql/constants'
|
||||||
|
|
||||||
import {FlatBody, Suggestion} from 'src/types/ifql'
|
import {FlatBody, Suggestion} from 'src/types/ifql'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
body: Body[]
|
body: Body[]
|
||||||
suggestions: Suggestion[]
|
suggestions: Suggestion[]
|
||||||
|
onAppendFrom: () => void
|
||||||
|
onAppendJoin: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Body extends FlatBody {
|
interface Body extends FlatBody {
|
||||||
|
@ -18,15 +21,14 @@ interface Body extends FlatBody {
|
||||||
|
|
||||||
class BodyBuilder extends PureComponent<Props> {
|
class BodyBuilder extends PureComponent<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const bodybuilder = this.props.body.map(b => {
|
const bodybuilder = this.props.body.map((b, i) => {
|
||||||
if (b.declarations.length) {
|
if (b.declarations.length) {
|
||||||
return b.declarations.map(d => {
|
return b.declarations.map(d => {
|
||||||
if (d.funcs) {
|
if (d.funcs) {
|
||||||
return (
|
return (
|
||||||
<div className="declaration" key={b.id}>
|
<div className="declaration" key={i}>
|
||||||
<VariableName name={d.name} />
|
<VariableName name={d.name} />
|
||||||
<ExpressionNode
|
<ExpressionNode
|
||||||
key={b.id}
|
|
||||||
bodyID={b.id}
|
bodyID={b.id}
|
||||||
declarationID={d.id}
|
declarationID={d.id}
|
||||||
funcNames={this.funcNames}
|
funcNames={this.funcNames}
|
||||||
|
@ -37,7 +39,7 @@ class BodyBuilder extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="declaration" key={b.id}>
|
<div className="declaration" key={i}>
|
||||||
<VariableName name={b.source} />
|
<VariableName name={b.source} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -45,8 +47,7 @@ class BodyBuilder extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="declaration" key={b.id}>
|
<div className="declaration" key={i}>
|
||||||
<VariableName />
|
|
||||||
<ExpressionNode
|
<ExpressionNode
|
||||||
bodyID={b.id}
|
bodyID={b.id}
|
||||||
funcs={b.funcs}
|
funcs={b.funcs}
|
||||||
|
@ -63,7 +64,7 @@ class BodyBuilder extends PureComponent<Props> {
|
||||||
<FuncSelector
|
<FuncSelector
|
||||||
bodyID="fake-body-id"
|
bodyID="fake-body-id"
|
||||||
declarationID="fake-declaration-id"
|
declarationID="fake-declaration-id"
|
||||||
onAddNode={this.createNewDeclaration}
|
onAddNode={this.createNewBody}
|
||||||
funcs={this.newDeclarationFuncs}
|
funcs={this.newDeclarationFuncs}
|
||||||
connectorVisible={false}
|
connectorVisible={false}
|
||||||
/>
|
/>
|
||||||
|
@ -73,15 +74,21 @@ class BodyBuilder extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get newDeclarationFuncs(): string[] {
|
private get newDeclarationFuncs(): string[] {
|
||||||
// 'JOIN' only available if there are at least 2 named declarations
|
const {body} = this.props
|
||||||
return ['from', 'join', 'variable']
|
const declarationFunctions = [funcNames.FROM]
|
||||||
|
if (body.length > 1) {
|
||||||
|
declarationFunctions.push(funcNames.JOIN)
|
||||||
|
}
|
||||||
|
return declarationFunctions
|
||||||
}
|
}
|
||||||
|
|
||||||
private createNewDeclaration = (bodyID, name, declarationID) => {
|
private createNewBody = name => {
|
||||||
// Returning a string here so linter stops yelling
|
if (name === funcNames.FROM) {
|
||||||
// TODO: write a real function
|
this.props.onAppendFrom()
|
||||||
|
}
|
||||||
return `${bodyID} / ${name} / ${declarationID}`
|
if (name === funcNames.JOIN) {
|
||||||
|
this.props.onAppendJoin()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get funcNames() {
|
private get funcNames() {
|
||||||
|
|
|
@ -22,9 +22,9 @@ class ExpressionNode extends PureComponent<Props> {
|
||||||
{({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => {
|
{({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{funcs.map(func => (
|
{funcs.map((func, i) => (
|
||||||
<FuncNode
|
<FuncNode
|
||||||
key={func.id}
|
key={i}
|
||||||
func={func}
|
func={func}
|
||||||
bodyID={bodyID}
|
bodyID={bodyID}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React, {PureComponent, ChangeEvent, FormEvent} from 'react'
|
||||||
|
|
||||||
|
import IFQLForm from 'src/ifql/components/IFQLForm'
|
||||||
|
|
||||||
|
import {Service, Notification} from 'src/types'
|
||||||
|
import {ifqlUpdated, ifqlNotUpdated} from 'src/shared/copy/notifications'
|
||||||
|
import {UpdateServiceAsync} from 'src/shared/actions/services'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
service: Service
|
||||||
|
onDismiss: () => void
|
||||||
|
updateService: UpdateServiceAsync
|
||||||
|
notify: (message: Notification) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
service: Service
|
||||||
|
}
|
||||||
|
|
||||||
|
class IFQLEdit extends PureComponent<Props, State> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
service: this.props.service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<IFQLForm
|
||||||
|
service={this.state.service}
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
onInputChange={this.handleInputChange}
|
||||||
|
mode="edit"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
const {value, name} = e.target
|
||||||
|
const update = {[name]: value}
|
||||||
|
|
||||||
|
this.setState({service: {...this.state.service, ...update}})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSubmit = async (
|
||||||
|
e: FormEvent<HTMLFormElement>
|
||||||
|
): Promise<void> => {
|
||||||
|
e.preventDefault()
|
||||||
|
const {notify, onDismiss, updateService} = this.props
|
||||||
|
const {service} = this.state
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateService(service)
|
||||||
|
} catch (error) {
|
||||||
|
notify(ifqlNotUpdated(error.message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(ifqlUpdated)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IFQLEdit
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React, {ChangeEvent, PureComponent} from 'react'
|
||||||
|
|
||||||
|
import Input from 'src/kapacitor/components/KapacitorFormInput'
|
||||||
|
|
||||||
|
import {NewService} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
service: NewService
|
||||||
|
mode: string
|
||||||
|
onSubmit: (e: ChangeEvent<HTMLFormElement>) => void
|
||||||
|
onInputChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class IFQLForm extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {service, onSubmit, onInputChange} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="template-variable-manager--body">
|
||||||
|
<form onSubmit={onSubmit} style={{display: 'inline-block'}}>
|
||||||
|
<Input
|
||||||
|
name="url"
|
||||||
|
label="IFQL URL"
|
||||||
|
value={this.url}
|
||||||
|
placeholder={this.url}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
value={service.name}
|
||||||
|
placeholder={service.name}
|
||||||
|
onChange={onInputChange}
|
||||||
|
maxLength={33}
|
||||||
|
/>
|
||||||
|
<div className="form-group form-group-submit col-xs-12 text-center">
|
||||||
|
<button
|
||||||
|
className="btn btn-success"
|
||||||
|
type="submit"
|
||||||
|
data-test="submit-button"
|
||||||
|
>
|
||||||
|
{this.buttonText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get buttonText(): string {
|
||||||
|
const {mode} = this.props
|
||||||
|
|
||||||
|
if (mode === 'edit') {
|
||||||
|
return 'Update'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Connect'
|
||||||
|
}
|
||||||
|
|
||||||
|
private get url(): string {
|
||||||
|
const {
|
||||||
|
service: {url},
|
||||||
|
} = this.props
|
||||||
|
if (url) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IFQLForm
|
|
@ -0,0 +1,67 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import IFQLOverlay from 'src/ifql/components/IFQLOverlay'
|
||||||
|
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||||
|
import {
|
||||||
|
showOverlay as showOverlayAction,
|
||||||
|
ShowOverlay,
|
||||||
|
} from 'src/shared/actions/overlayTechnology'
|
||||||
|
|
||||||
|
import {Service} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showOverlay: ShowOverlay
|
||||||
|
service: Service
|
||||||
|
onGetTimeSeries: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class IFQLHeader extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {onGetTimeSeries} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-header full-width">
|
||||||
|
<div className="page-header__container">
|
||||||
|
<div className="page-header__left">
|
||||||
|
<h1 className="page-header__title">Time Machine</h1>
|
||||||
|
</div>
|
||||||
|
<div className="page-header__right">
|
||||||
|
<button onClick={this.overlay} className="btn btn-sm btn-default">
|
||||||
|
Edit Connection
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
onClick={onGetTimeSeries}
|
||||||
|
>
|
||||||
|
Get Data!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private overlay = () => {
|
||||||
|
const {showOverlay, service} = this.props
|
||||||
|
|
||||||
|
showOverlay(
|
||||||
|
<OverlayContext.Consumer>
|
||||||
|
{({onDismissOverlay}) => (
|
||||||
|
<IFQLOverlay
|
||||||
|
mode="edit"
|
||||||
|
service={service}
|
||||||
|
onDismiss={onDismissOverlay}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OverlayContext.Consumer>,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mdtp = {
|
||||||
|
showOverlay: showOverlayAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mdtp)(IFQLHeader)
|
|
@ -0,0 +1,86 @@
|
||||||
|
import React, {PureComponent, ChangeEvent, FormEvent} from 'react'
|
||||||
|
|
||||||
|
import IFQLForm from 'src/ifql/components/IFQLForm'
|
||||||
|
|
||||||
|
import {NewService, Source, Notification} from 'src/types'
|
||||||
|
import {ifqlCreated, ifqlNotCreated} from 'src/shared/copy/notifications'
|
||||||
|
import {CreateServiceAsync} from 'src/shared/actions/services'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: Source
|
||||||
|
onDismiss: () => void
|
||||||
|
createService: CreateServiceAsync
|
||||||
|
notify: (message: Notification) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
service: NewService
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = 8093
|
||||||
|
|
||||||
|
class IFQLNew extends PureComponent<Props, State> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
service: this.defaultService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<IFQLForm
|
||||||
|
service={this.state.service}
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
onInputChange={this.handleInputChange}
|
||||||
|
mode="new"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
const {value, name} = e.target
|
||||||
|
const update = {[name]: value}
|
||||||
|
|
||||||
|
this.setState({service: {...this.state.service, ...update}})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSubmit = async (
|
||||||
|
e: FormEvent<HTMLFormElement>
|
||||||
|
): Promise<void> => {
|
||||||
|
e.preventDefault()
|
||||||
|
const {notify, source, onDismiss, createService} = this.props
|
||||||
|
|
||||||
|
const {service} = this.state
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createService(source, service)
|
||||||
|
} catch (error) {
|
||||||
|
notify(ifqlNotCreated(error.message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(ifqlCreated)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
private get defaultService(): NewService {
|
||||||
|
return {
|
||||||
|
name: 'IFQL',
|
||||||
|
url: this.url,
|
||||||
|
username: '',
|
||||||
|
insecureSkipVerify: false,
|
||||||
|
type: 'ifql',
|
||||||
|
active: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get url(): string {
|
||||||
|
const parser = document.createElement('a')
|
||||||
|
parser.href = this.props.source.url
|
||||||
|
|
||||||
|
return `${parser.protocol}//${parser.hostname}:${port}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IFQLNew
|
|
@ -0,0 +1,86 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import IFQLNew from 'src/ifql/components/IFQLNew'
|
||||||
|
import IFQLEdit from 'src/ifql/components/IFQLEdit'
|
||||||
|
|
||||||
|
import {Service, Source, Notification} from 'src/types'
|
||||||
|
|
||||||
|
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||||
|
import {
|
||||||
|
updateServiceAsync,
|
||||||
|
UpdateServiceAsync,
|
||||||
|
createServiceAsync,
|
||||||
|
CreateServiceAsync,
|
||||||
|
} from 'src/shared/actions/services'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mode: string
|
||||||
|
source?: Source
|
||||||
|
service?: Service
|
||||||
|
onDismiss: () => void
|
||||||
|
notify: (message: Notification) => void
|
||||||
|
createService: CreateServiceAsync
|
||||||
|
updateService: UpdateServiceAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
class IFQLOverlay extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<div className="ifql-overlay">
|
||||||
|
<div className="template-variable-manager--header">
|
||||||
|
<div className="page-header__left">
|
||||||
|
<h1 className="page-header__title">Connect to IFQL</h1>
|
||||||
|
</div>
|
||||||
|
<div className="page-header__right">
|
||||||
|
<span
|
||||||
|
className="page-header__dismiss"
|
||||||
|
onClick={this.props.onDismiss}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.form}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get form(): JSX.Element {
|
||||||
|
const {
|
||||||
|
mode,
|
||||||
|
source,
|
||||||
|
service,
|
||||||
|
notify,
|
||||||
|
onDismiss,
|
||||||
|
createService,
|
||||||
|
updateService,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (mode === 'new') {
|
||||||
|
return (
|
||||||
|
<IFQLNew
|
||||||
|
source={source}
|
||||||
|
notify={notify}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
createService={createService}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IFQLEdit
|
||||||
|
notify={notify}
|
||||||
|
service={service}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
updateService={updateService}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mdtp = {
|
||||||
|
notify: notifyAction,
|
||||||
|
createService: createServiceAsync,
|
||||||
|
updateService: updateServiceAsync,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mdtp)(IFQLOverlay)
|
|
@ -3,16 +3,28 @@ import SchemaExplorer from 'src/ifql/components/SchemaExplorer'
|
||||||
import BodyBuilder from 'src/ifql/components/BodyBuilder'
|
import BodyBuilder from 'src/ifql/components/BodyBuilder'
|
||||||
import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor'
|
import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor'
|
||||||
import TimeMachineVis from 'src/ifql/components/TimeMachineVis'
|
import TimeMachineVis from 'src/ifql/components/TimeMachineVis'
|
||||||
import Threesizer from 'src/shared/components/Threesizer'
|
import Threesizer from 'src/shared/components/threesizer/Threesizer'
|
||||||
import {Suggestion, OnChangeScript, FlatBody} from 'src/types/ifql'
|
import {
|
||||||
|
Suggestion,
|
||||||
|
OnChangeScript,
|
||||||
|
OnSubmitScript,
|
||||||
|
FlatBody,
|
||||||
|
Status,
|
||||||
|
} from 'src/types/ifql'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants'
|
import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
data: string
|
||||||
script: string
|
script: string
|
||||||
suggestions: Suggestion[]
|
|
||||||
body: Body[]
|
body: Body[]
|
||||||
|
status: Status
|
||||||
|
suggestions: Suggestion[]
|
||||||
onChangeScript: OnChangeScript
|
onChangeScript: OnChangeScript
|
||||||
|
onSubmitScript: OnSubmitScript
|
||||||
|
onAppendFrom: () => void
|
||||||
|
onAppendJoin: () => void
|
||||||
|
onAnalyze: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Body extends FlatBody {
|
interface Body extends FlatBody {
|
||||||
|
@ -32,9 +44,12 @@ class TimeMachine extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get mainSplit() {
|
private get mainSplit() {
|
||||||
|
const {data} = this.props
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
handleDisplay: 'none',
|
handleDisplay: 'none',
|
||||||
|
menuOptions: [],
|
||||||
|
headerButtons: [],
|
||||||
render: () => (
|
render: () => (
|
||||||
<Threesizer
|
<Threesizer
|
||||||
divisions={this.divisions}
|
divisions={this.divisions}
|
||||||
|
@ -44,31 +59,67 @@ class TimeMachine extends PureComponent<Props> {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handlePixels: 8,
|
handlePixels: 8,
|
||||||
render: () => <TimeMachineVis blob="Visualizer" />,
|
menuOptions: [],
|
||||||
|
headerButtons: [],
|
||||||
|
render: () => <TimeMachineVis data={data} />,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private get divisions() {
|
private get divisions() {
|
||||||
const {body, suggestions, script, onChangeScript} = this.props
|
const {
|
||||||
|
body,
|
||||||
|
script,
|
||||||
|
status,
|
||||||
|
onAnalyze,
|
||||||
|
suggestions,
|
||||||
|
onAppendFrom,
|
||||||
|
onAppendJoin,
|
||||||
|
onChangeScript,
|
||||||
|
onSubmitScript,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Explore',
|
name: 'Explore',
|
||||||
|
headerButtons: [],
|
||||||
|
menuOptions: [],
|
||||||
render: () => <SchemaExplorer />,
|
render: () => <SchemaExplorer />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Script',
|
name: 'Script',
|
||||||
|
headerButtons: [
|
||||||
|
<div
|
||||||
|
key="analyze"
|
||||||
|
className="btn btn-default btn-sm analyze--button"
|
||||||
|
onClick={onAnalyze}
|
||||||
|
>
|
||||||
|
Analyze
|
||||||
|
</div>,
|
||||||
|
],
|
||||||
|
menuOptions: [],
|
||||||
render: visibility => (
|
render: visibility => (
|
||||||
<TimeMachineEditor
|
<TimeMachineEditor
|
||||||
|
status={status}
|
||||||
script={script}
|
script={script}
|
||||||
onChangeScript={onChangeScript}
|
|
||||||
visibility={visibility}
|
visibility={visibility}
|
||||||
|
onChangeScript={onChangeScript}
|
||||||
|
onSubmitScript={onSubmitScript}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Build',
|
name: 'Build',
|
||||||
render: () => <BodyBuilder body={body} suggestions={suggestions} />,
|
headerButtons: [],
|
||||||
|
menuOptions: [],
|
||||||
|
render: () => (
|
||||||
|
<BodyBuilder
|
||||||
|
body={body}
|
||||||
|
suggestions={suggestions}
|
||||||
|
onAppendFrom={onAppendFrom}
|
||||||
|
onAppendJoin={onAppendJoin}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,25 @@ import {Controlled as CodeMirror, IInstance} from 'react-codemirror2'
|
||||||
import {EditorChange} from 'codemirror'
|
import {EditorChange} from 'codemirror'
|
||||||
import 'src/external/codemirror'
|
import 'src/external/codemirror'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
import {OnChangeScript} from 'src/types/ifql'
|
import {OnChangeScript, OnSubmitScript} from 'src/types/ifql'
|
||||||
import {editor} from 'src/ifql/constants'
|
import {editor} from 'src/ifql/constants'
|
||||||
|
|
||||||
|
interface Gutter {
|
||||||
|
line: number
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Status {
|
||||||
|
type: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
script: string
|
script: string
|
||||||
onChangeScript: OnChangeScript
|
|
||||||
visibility: string
|
visibility: string
|
||||||
|
status: Status
|
||||||
|
onChangeScript: OnChangeScript
|
||||||
|
onSubmitScript: OnSubmitScript
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EditorInstance extends IInstance {
|
interface EditorInstance extends IInstance {
|
||||||
|
@ -19,12 +31,21 @@ interface EditorInstance extends IInstance {
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class TimeMachineEditor extends PureComponent<Props> {
|
class TimeMachineEditor extends PureComponent<Props> {
|
||||||
private editor: EditorInstance
|
private editor: EditorInstance
|
||||||
|
private prevKey: string
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps) {
|
public componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.status.type === 'error') {
|
||||||
|
this.makeError()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.status.type !== 'error') {
|
||||||
|
this.editor.clearGutter('error-gutter')
|
||||||
|
}
|
||||||
|
|
||||||
if (prevProps.visibility === this.props.visibility) {
|
if (prevProps.visibility === this.props.visibility) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -45,6 +66,7 @@ class TimeMachineEditor extends PureComponent<Props> {
|
||||||
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
||||||
completeSingle: false,
|
completeSingle: false,
|
||||||
autoRefresh: true,
|
autoRefresh: true,
|
||||||
|
gutters: ['error-gutter'],
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -58,17 +80,71 @@ class TimeMachineEditor extends PureComponent<Props> {
|
||||||
onBeforeChange={this.updateCode}
|
onBeforeChange={this.updateCode}
|
||||||
onTouchStart={this.onTouchStart}
|
onTouchStart={this.onTouchStart}
|
||||||
editorDidMount={this.handleMount}
|
editorDidMount={this.handleMount}
|
||||||
|
onBlur={this.handleBlur}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleBlur = (): void => {
|
||||||
|
this.props.onSubmitScript()
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeError(): void {
|
||||||
|
this.editor.clearGutter('error-gutter')
|
||||||
|
const lineNumbers = this.statusLine
|
||||||
|
lineNumbers.forEach(({line, text}) => {
|
||||||
|
this.editor.setGutterMarker(
|
||||||
|
line - 1,
|
||||||
|
'error-gutter',
|
||||||
|
this.errorMarker(text)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.editor.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private errorMarker(message: string): HTMLElement {
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.className = 'icon stop error-warning'
|
||||||
|
span.title = message
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
private get statusLine(): Gutter[] {
|
||||||
|
const {status} = this.props
|
||||||
|
const messages = status.text.split('\n')
|
||||||
|
const lineNumbers = messages.map(text => {
|
||||||
|
const [numbers] = text.split(' ')
|
||||||
|
const [lineNumber] = numbers.split(':')
|
||||||
|
return {line: Number(lineNumber), text}
|
||||||
|
})
|
||||||
|
|
||||||
|
return lineNumbers
|
||||||
|
}
|
||||||
|
|
||||||
private handleMount = (instance: EditorInstance) => {
|
private handleMount = (instance: EditorInstance) => {
|
||||||
|
instance.refresh() // required to for proper line height on mount
|
||||||
this.editor = instance
|
this.editor = instance
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleKeyUp = (instance: EditorInstance, e: KeyboardEvent) => {
|
private handleKeyUp = (instance: EditorInstance, e: KeyboardEvent) => {
|
||||||
const {key} = e
|
const {key} = e
|
||||||
|
const prevKey = this.prevKey
|
||||||
|
|
||||||
|
if (
|
||||||
|
prevKey === 'Control' ||
|
||||||
|
prevKey === 'Meta' ||
|
||||||
|
(prevKey === 'Shift' && key === '.')
|
||||||
|
) {
|
||||||
|
return (this.prevKey = key)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevKey = key
|
||||||
|
|
||||||
|
if (editor.EXCLUDED_KEYS.includes(key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (editor.EXCLUDED_KEYS.includes(key)) {
|
if (editor.EXCLUDED_KEYS.includes(key)) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,14 +1,41 @@
|
||||||
import React, {SFC} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||||
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
blob: string
|
data: string
|
||||||
}
|
}
|
||||||
const TimeMachineVis: SFC<Props> = ({blob}) => (
|
|
||||||
|
@ErrorHandling
|
||||||
|
class TimeMachineVis extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
<div className="time-machine-visualization">
|
<div className="time-machine-visualization">
|
||||||
<div className="time-machine--graph">
|
<div className="time-machine--graph">
|
||||||
<div className="time-machine--graph-body">{blob}</div>
|
<FancyScrollbar>
|
||||||
|
<div className="time-machine--graph-body">
|
||||||
|
{this.data.map((d, i) => {
|
||||||
|
return (
|
||||||
|
<div key={i} className="data-row">
|
||||||
|
{d}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</FancyScrollbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get data(): string[] {
|
||||||
|
const {data} = this.props
|
||||||
|
if (!data) {
|
||||||
|
return ['Your query was syntactically correct but returned no data']
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.data.split('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default TimeMachineVis
|
export default TimeMachineVis
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {PureComponent, MouseEvent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name?: string
|
name?: string
|
||||||
|
@ -22,78 +22,12 @@ export default class VariableName extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {isExpanded} = this.state
|
return <div className="variable-string">{this.nameElement}</div>
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="variable-string"
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
>
|
|
||||||
{this.nameElement}
|
|
||||||
{isExpanded && this.renderTooltip}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private get renderTooltip(): JSX.Element {
|
|
||||||
const {name} = this.props
|
|
||||||
|
|
||||||
if (name.includes('=')) {
|
|
||||||
const split = name.split('=')
|
|
||||||
const varName = split[0].substring(0, split[0].length - 1)
|
|
||||||
const varValue = split[1].substring(1)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="variable-name--tooltip">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control form-plutonium input-sm variable-name--input"
|
|
||||||
defaultValue={varName}
|
|
||||||
placeholder="Name"
|
|
||||||
/>
|
|
||||||
<span className="variable-name--operator">=</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control input-sm variable-name--input"
|
|
||||||
defaultValue={varValue}
|
|
||||||
placeholder="Value"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="variable-name--tooltip">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control form-plutonium input-sm variable-name--input"
|
|
||||||
defaultValue={name}
|
|
||||||
placeholder="Name this query..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleMouseEnter = (e: MouseEvent<HTMLElement>): void => {
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
this.setState({isExpanded: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleMouseLeave = (e: MouseEvent<HTMLElement>): void => {
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
this.setState({isExpanded: false})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private get nameElement(): JSX.Element {
|
private get nameElement(): JSX.Element {
|
||||||
const {name} = this.props
|
const {name} = this.props
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return <span className="variable-blank">Untitled</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.includes('=')) {
|
if (name.includes('=')) {
|
||||||
return this.colorizeSyntax
|
return this.colorizeSyntax
|
||||||
}
|
}
|
||||||
|
@ -105,7 +39,7 @@ export default class VariableName extends PureComponent<Props, State> {
|
||||||
const {name} = this.props
|
const {name} = this.props
|
||||||
const split = name.split('=')
|
const split = name.split('=')
|
||||||
const varName = split[0].substring(0, split[0].length - 1)
|
const varName = split[0].substring(0, split[0].length - 1)
|
||||||
const varValue = split[1].substring(1)
|
const varValue = this.props.name.replace(/^[^=]+=/, '')
|
||||||
|
|
||||||
const valueIsString = varValue.endsWith('"')
|
const valueIsString = varValue.endsWith('"')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const NEW_FROM = `from(db: "pick a db")\n\t|> filter(fn: (r) => r.tag == "value")\n\t|> range(start: -1m)`
|
||||||
|
export const NEW_JOIN = `join(tables: {fil:fil, tele:tele}, on:["host"], fn: (tables) => tables.fil["_value"] + tables.tele["_value"])`
|
|
@ -30,6 +30,9 @@ export const EXCLUDED_KEYS = [
|
||||||
'Subtract',
|
'Subtract',
|
||||||
'Decimal point',
|
'Decimal point',
|
||||||
'Divide',
|
'Divide',
|
||||||
|
'>',
|
||||||
|
'|',
|
||||||
|
')',
|
||||||
'F1',
|
'F1',
|
||||||
'F2',
|
'F2',
|
||||||
'F3',
|
'F3',
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export const FROM = 'from'
|
export const FROM = 'from'
|
||||||
export const FILTER = 'filter'
|
export const FILTER = 'filter'
|
||||||
|
export const JOIN = 'join'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as funcNames from 'src/ifql/constants/funcNames'
|
|
||||||
import * as argTypes from 'src/ifql/constants/argumentTypes'
|
|
||||||
import {ast} from 'src/ifql/constants/ast'
|
import {ast} from 'src/ifql/constants/ast'
|
||||||
import * as editor from 'src/ifql/constants/editor'
|
import * as editor from 'src/ifql/constants/editor'
|
||||||
|
import * as argTypes from 'src/ifql/constants/argumentTypes'
|
||||||
|
import * as funcNames from 'src/ifql/constants/funcNames'
|
||||||
|
import * as builder from 'src/ifql/constants/builder'
|
||||||
|
|
||||||
export {ast, funcNames, argTypes, editor}
|
export {ast, funcNames, argTypes, editor, builder}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React, {PureComponent, ReactChildren} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import {withRouter, WithRouterProps} from 'react-router'
|
||||||
|
|
||||||
|
import IFQLOverlay from 'src/ifql/components/IFQLOverlay'
|
||||||
|
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||||
|
import {Source, Service} from 'src/types'
|
||||||
|
import * as a from 'src/shared/actions/overlayTechnology'
|
||||||
|
import * as b from 'src/shared/actions/services'
|
||||||
|
|
||||||
|
const actions = {...a, ...b}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sources: Source[]
|
||||||
|
services: Service[]
|
||||||
|
children: ReactChildren
|
||||||
|
showOverlay: a.ShowOverlay
|
||||||
|
fetchServicesAsync: b.FetchServicesAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
||||||
|
public async componentDidMount() {
|
||||||
|
const source = this.props.sources.find(
|
||||||
|
s => s.id === this.props.params.sourceID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.props.fetchServicesAsync(source)
|
||||||
|
|
||||||
|
if (!this.props.services.length) {
|
||||||
|
this.overlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
|
||||||
|
private overlay() {
|
||||||
|
const {showOverlay, services, sources, params} = this.props
|
||||||
|
const source = sources.find(s => s.id === params.sourceID)
|
||||||
|
|
||||||
|
if (services.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showOverlay(
|
||||||
|
<OverlayContext.Consumer>
|
||||||
|
{({onDismissOverlay}) => (
|
||||||
|
<IFQLOverlay
|
||||||
|
mode="new"
|
||||||
|
source={source}
|
||||||
|
onDismiss={onDismissOverlay}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OverlayContext.Consumer>,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mdtp = {
|
||||||
|
fetchServicesAsync: actions.fetchServicesAsync,
|
||||||
|
showOverlay: actions.showOverlay,
|
||||||
|
}
|
||||||
|
|
||||||
|
const mstp = ({sources, services}) => ({sources, services})
|
||||||
|
|
||||||
|
export default connect(mstp, mdtp)(withRouter(CheckServices))
|
|
@ -1,20 +1,42 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import CheckServices from 'src/ifql/containers/CheckServices'
|
||||||
import TimeMachine from 'src/ifql/components/TimeMachine'
|
import TimeMachine from 'src/ifql/components/TimeMachine'
|
||||||
|
import IFQLHeader from 'src/ifql/components/IFQLHeader'
|
||||||
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
|
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
|
||||||
import {Suggestion, FlatBody, Links} from 'src/types/ifql'
|
|
||||||
import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql'
|
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||||
|
import {analyzeSuccess} from 'src/shared/copy/notifications'
|
||||||
|
|
||||||
import {bodyNodes} from 'src/ifql/helpers'
|
import {bodyNodes} from 'src/ifql/helpers'
|
||||||
import {getSuggestions, getAST} from 'src/ifql/apis'
|
import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis'
|
||||||
import * as argTypes from 'src/ifql/constants/argumentTypes'
|
import {builder, argTypes} from 'src/ifql/constants'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {funcNames} from 'src/ifql/constants'
|
||||||
|
|
||||||
|
import {Source, Service, Notification} from 'src/types'
|
||||||
|
import {
|
||||||
|
Suggestion,
|
||||||
|
FlatBody,
|
||||||
|
Links,
|
||||||
|
InputArg,
|
||||||
|
Handlers,
|
||||||
|
DeleteFuncNodeArgs,
|
||||||
|
Func,
|
||||||
|
} from 'src/types/ifql'
|
||||||
|
|
||||||
|
interface Status {
|
||||||
|
type: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
links: Links
|
links: Links
|
||||||
|
services: Service[]
|
||||||
|
sources: Source[]
|
||||||
|
notify: (message: Notification) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Body extends FlatBody {
|
interface Body extends FlatBody {
|
||||||
|
@ -25,7 +47,9 @@ interface State {
|
||||||
body: Body[]
|
body: Body[]
|
||||||
ast: object
|
ast: object
|
||||||
script: string
|
script: string
|
||||||
|
data: string
|
||||||
suggestions: Suggestion[]
|
suggestions: Suggestion[]
|
||||||
|
status: Status
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IFQLContext = React.createContext()
|
export const IFQLContext = React.createContext()
|
||||||
|
@ -37,11 +61,13 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
this.state = {
|
this.state = {
|
||||||
body: [],
|
body: [],
|
||||||
ast: null,
|
ast: null,
|
||||||
|
data: 'Hit "Get Data!" or Ctrl + Enter to run your script',
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
script: `from(db:"foo")
|
script: `fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()\n\n`,
|
||||||
|> filter(fn: (r) =>
|
status: {
|
||||||
(r["a"] == 1 OR r.b == "two") AND
|
type: 'none',
|
||||||
(r["b"] == true OR r.d == "four"))`,
|
text: '',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,39 +85,49 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {suggestions, script} = this.state
|
const {suggestions, script, data, body, status} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<CheckServices>
|
||||||
<IFQLContext.Provider value={this.handlers}>
|
<IFQLContext.Provider value={this.handlers}>
|
||||||
<KeyboardShortcuts onControlEnter={this.handleSubmitScript}>
|
<KeyboardShortcuts onControlEnter={this.getTimeSeries}>
|
||||||
<div className="page hosts-list-page">
|
<div className="page hosts-list-page">
|
||||||
<div className="page-header full-width">
|
{this.header}
|
||||||
<div className="page-header__container">
|
|
||||||
<div className="page-header__left">
|
|
||||||
<h1 className="page-header__title">Time Machine</h1>
|
|
||||||
</div>
|
|
||||||
<div className="page-header__right">
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-primary"
|
|
||||||
onClick={this.handleSubmitScript}
|
|
||||||
>
|
|
||||||
Submit Script
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<TimeMachine
|
<TimeMachine
|
||||||
|
data={data}
|
||||||
|
body={body}
|
||||||
script={script}
|
script={script}
|
||||||
body={this.state.body}
|
status={status}
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
|
onAnalyze={this.handleAnalyze}
|
||||||
|
onAppendFrom={this.handleAppendFrom}
|
||||||
|
onAppendJoin={this.handleAppendJoin}
|
||||||
onChangeScript={this.handleChangeScript}
|
onChangeScript={this.handleChangeScript}
|
||||||
|
onSubmitScript={this.handleSubmitScript}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</KeyboardShortcuts>
|
</KeyboardShortcuts>
|
||||||
</IFQLContext.Provider>
|
</IFQLContext.Provider>
|
||||||
|
</CheckServices>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get header(): JSX.Element {
|
||||||
|
const {services} = this.props
|
||||||
|
|
||||||
|
if (!services.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IFQLHeader service={this.service} onGetTimeSeries={this.getTimeSeries} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get service(): Service {
|
||||||
|
return this.props.services[0]
|
||||||
|
}
|
||||||
|
|
||||||
private get handlers(): Handlers {
|
private get handlers(): Handlers {
|
||||||
return {
|
return {
|
||||||
onAddNode: this.handleAddNode,
|
onAddNode: this.handleAddNode,
|
||||||
|
@ -223,6 +259,20 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleAppendFrom = (): void => {
|
||||||
|
const {script} = this.state
|
||||||
|
const newScript = `${script.trim()}\n\n${builder.NEW_FROM}\n\n`
|
||||||
|
|
||||||
|
this.getASTResponse(newScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleAppendJoin = (): void => {
|
||||||
|
const {script} = this.state
|
||||||
|
const newScript = `${script.trim()}\n\n${builder.NEW_JOIN}\n\n`
|
||||||
|
|
||||||
|
this.getASTResponse(newScript)
|
||||||
|
}
|
||||||
|
|
||||||
private handleChangeScript = (script: string): void => {
|
private handleChangeScript = (script: string): void => {
|
||||||
this.setState({script})
|
this.setState({script})
|
||||||
}
|
}
|
||||||
|
@ -325,21 +375,77 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
}, '')
|
}, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleAnalyze = async () => {
|
||||||
|
const {links, notify} = this.props
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ast = await getAST({url: links.ast, body: this.state.script})
|
||||||
|
const body = bodyNodes(ast, this.state.suggestions)
|
||||||
|
const status = {type: 'success', text: ''}
|
||||||
|
notify(analyzeSuccess)
|
||||||
|
|
||||||
|
this.setState({ast, body, status})
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({status: this.parseError(error)})
|
||||||
|
return console.error('Could not parse AST', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getASTResponse = async (script: string) => {
|
private getASTResponse = async (script: string) => {
|
||||||
const {links} = this.props
|
const {links} = this.props
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ast = await getAST({url: links.ast, body: script})
|
const ast = await getAST({url: links.ast, body: script})
|
||||||
const body = bodyNodes(ast, this.state.suggestions)
|
const suggestions = this.state.suggestions.map(s => {
|
||||||
this.setState({ast, script, body})
|
if (s.name === funcNames.JOIN) {
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
params: {
|
||||||
|
tables: 'object',
|
||||||
|
on: 'array',
|
||||||
|
fn: 'function',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
const body = bodyNodes(ast, suggestions)
|
||||||
|
const status = {type: 'success', text: ''}
|
||||||
|
this.setState({ast, script, body, status})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Could not parse AST', error)
|
this.setState({status: this.parseError(error)})
|
||||||
|
return console.error('Could not parse AST', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTimeSeries = async () => {
|
||||||
|
const {script} = this.state
|
||||||
|
this.setState({data: 'fetching data...'})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {data} = await getTimeSeries(script)
|
||||||
|
this.setState({data})
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({data: 'Error fetching data'})
|
||||||
|
console.error('Could not get timeSeries', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getASTResponse(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseError = (error): Status => {
|
||||||
|
const s = error.data.slice(0, -5) // There is a 'null\n' at the end of these responses
|
||||||
|
const data = JSON.parse(s)
|
||||||
|
return {type: 'error', text: `${data.message}`}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = ({links}) => {
|
const mapStateToProps = ({links, services, sources}) => {
|
||||||
return {links: links.ifql}
|
return {links: links.ifql, services, sources}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(IFQLPage)
|
const mapDispatchToProps = {
|
||||||
|
notify: notifyAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage)
|
||||||
|
|
|
@ -49,7 +49,17 @@ export const bodyNodes = (ast, suggestions): Body[] => {
|
||||||
|
|
||||||
const functions = (funcs, suggestions): Func[] => {
|
const functions = (funcs, suggestions): Func[] => {
|
||||||
const funcList = funcs.map(func => {
|
const funcList = funcs.map(func => {
|
||||||
const {params, name} = suggestions.find(f => f.name === func.name)
|
const suggestion = suggestions.find(f => f.name === func.name)
|
||||||
|
if (!suggestion) {
|
||||||
|
return {
|
||||||
|
id: uuid.v4(),
|
||||||
|
source: func.source,
|
||||||
|
name: func.name,
|
||||||
|
args: func.args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {params, name} = suggestion
|
||||||
const args = Object.entries(params).map(([key, type]) => {
|
const args = Object.entries(params).map(([key, type]) => {
|
||||||
const value = _.get(func.args.find(arg => arg.key === key), 'value', '')
|
const value = _.get(func.args.find(arg => arg.key === key), 'value', '')
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
import IFQLPage from 'src/ifql/containers/IFQLPage'
|
import IFQLPage from 'src/ifql/containers/IFQLPage'
|
||||||
|
import CheckServices from 'src/ifql/containers/CheckServices'
|
||||||
|
|
||||||
export {IFQLPage}
|
export {IFQLPage, CheckServices}
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
} from 'src/kapacitor'
|
} from 'src/kapacitor'
|
||||||
import {AdminChronografPage, AdminInfluxDBPage} from 'src/admin'
|
import {AdminChronografPage, AdminInfluxDBPage} from 'src/admin'
|
||||||
import {SourcePage, ManageSources} from 'src/sources'
|
import {SourcePage, ManageSources} from 'src/sources'
|
||||||
import {IFQLPage} from 'src/ifql/index'
|
import {IFQLPage} from 'src/ifql'
|
||||||
import NotFound from 'src/shared/components/NotFound'
|
import NotFound from 'src/shared/components/NotFound'
|
||||||
|
|
||||||
import {getLinksAsync} from 'src/shared/actions/links'
|
import {getLinksAsync} from 'src/shared/actions/links'
|
||||||
|
|
|
@ -5,9 +5,10 @@ interface Props {
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
|
||||||
maxLength?: number
|
maxLength?: number
|
||||||
inputType?: string
|
inputType?: string
|
||||||
|
customClass?: string
|
||||||
|
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const KapacitorFormInput: SFC<Props> = ({
|
const KapacitorFormInput: SFC<Props> = ({
|
||||||
|
@ -18,8 +19,9 @@ const KapacitorFormInput: SFC<Props> = ({
|
||||||
onChange,
|
onChange,
|
||||||
maxLength,
|
maxLength,
|
||||||
inputType,
|
inputType,
|
||||||
|
customClass,
|
||||||
}) => (
|
}) => (
|
||||||
<div className="form-group">
|
<div className={`form-group ${customClass}`}>
|
||||||
<label htmlFor={name}>{label}</label>
|
<label htmlFor={name}>{label}</label>
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
|
@ -37,6 +39,7 @@ const KapacitorFormInput: SFC<Props> = ({
|
||||||
|
|
||||||
KapacitorFormInput.defaultProps = {
|
KapacitorFormInput.defaultProps = {
|
||||||
inputType: '',
|
inputType: '',
|
||||||
|
customClass: 'col-sm-6',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KapacitorFormInput
|
export default KapacitorFormInput
|
||||||
|
|
|
@ -169,6 +169,7 @@ export class KapacitorPage extends PureComponent<Props, State> {
|
||||||
return (
|
return (
|
||||||
<KapacitorForm
|
<KapacitorForm
|
||||||
hash={hash}
|
hash={hash}
|
||||||
|
notify={notify}
|
||||||
source={source}
|
source={source}
|
||||||
exists={exists}
|
exists={exists}
|
||||||
kapacitor={kapacitor}
|
kapacitor={kapacitor}
|
||||||
|
@ -176,7 +177,6 @@ export class KapacitorPage extends PureComponent<Props, State> {
|
||||||
onChangeUrl={this.handleChangeUrl}
|
onChangeUrl={this.handleChangeUrl}
|
||||||
onReset={this.handleResetToDefaults}
|
onReset={this.handleResetToDefaults}
|
||||||
onInputChange={this.handleInputChange}
|
onInputChange={this.handleInputChange}
|
||||||
notify={notify}
|
|
||||||
onCheckboxChange={this.handleCheckboxChange}
|
onCheckboxChange={this.handleCheckboxChange}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,19 @@ interface Options {
|
||||||
transitionTime?: number
|
transitionTime?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ShowOverlay = (
|
||||||
|
OverlayNode: OverlayNodeType,
|
||||||
|
options: Options
|
||||||
|
) => ActionOverlayNode
|
||||||
|
|
||||||
|
export interface ActionOverlayNode {
|
||||||
|
type: 'SHOW_OVERLAY'
|
||||||
|
payload: {
|
||||||
|
OverlayNode
|
||||||
|
options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const showOverlay = (
|
export const showOverlay = (
|
||||||
OverlayNode: OverlayNodeType,
|
OverlayNode: OverlayNodeType,
|
||||||
options: Options
|
options: Options
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
import {Source, Service, NewService} from 'src/types'
|
||||||
|
import {
|
||||||
|
updateService as updateServiceAJAX,
|
||||||
|
getServices as getServicesAJAX,
|
||||||
|
createService as createServiceAJAX,
|
||||||
|
} from 'src/shared/apis'
|
||||||
|
import {notify} from './notifications'
|
||||||
|
import {couldNotGetServices} from 'src/shared/copy/notifications'
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| ActionLoadServices
|
||||||
|
| ActionAddService
|
||||||
|
| ActionDeleteService
|
||||||
|
| ActionUpdateService
|
||||||
|
| ActionSetActiveService
|
||||||
|
|
||||||
|
// Load Services
|
||||||
|
export type LoadServices = (services: Service[]) => ActionLoadServices
|
||||||
|
export interface ActionLoadServices {
|
||||||
|
type: 'LOAD_SERVICES'
|
||||||
|
payload: {
|
||||||
|
services: Service[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadServices = (services: Service[]): ActionLoadServices => ({
|
||||||
|
type: 'LOAD_SERVICES',
|
||||||
|
payload: {
|
||||||
|
services,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add a Service
|
||||||
|
export type AddService = (service: Service) => ActionAddService
|
||||||
|
export interface ActionAddService {
|
||||||
|
type: 'ADD_SERVICE'
|
||||||
|
payload: {
|
||||||
|
service: Service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addService = (service: Service): ActionAddService => ({
|
||||||
|
type: 'ADD_SERVICE',
|
||||||
|
payload: {
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete Service
|
||||||
|
export type DeleteService = (service: Service) => ActionDeleteService
|
||||||
|
export interface ActionDeleteService {
|
||||||
|
type: 'DELETE_SERVICE'
|
||||||
|
payload: {
|
||||||
|
service: Service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteService = (service: Service): ActionDeleteService => ({
|
||||||
|
type: 'DELETE_SERVICE',
|
||||||
|
payload: {
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update Service
|
||||||
|
export type UpdateService = (service: Service) => ActionUpdateService
|
||||||
|
export interface ActionUpdateService {
|
||||||
|
type: 'UPDATE_SERVICE'
|
||||||
|
payload: {
|
||||||
|
service: Service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateService = (service: Service): ActionUpdateService => ({
|
||||||
|
type: 'UPDATE_SERVICE',
|
||||||
|
payload: {
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set Active Service
|
||||||
|
export type SetActiveService = (
|
||||||
|
source: Source,
|
||||||
|
service: Service
|
||||||
|
) => ActionSetActiveService
|
||||||
|
export interface ActionSetActiveService {
|
||||||
|
type: 'SET_ACTIVE_SERVICE'
|
||||||
|
payload: {
|
||||||
|
source: Source
|
||||||
|
service: Service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setActiveService = (
|
||||||
|
source: Source,
|
||||||
|
service: Service
|
||||||
|
): ActionSetActiveService => ({
|
||||||
|
type: 'SET_ACTIVE_SERVICE',
|
||||||
|
payload: {
|
||||||
|
source,
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type FetchServicesAsync = (source: Source) => (dispatch) => Promise<void>
|
||||||
|
export const fetchServicesAsync = (source: Source) => async (
|
||||||
|
dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const services = await getServicesAJAX(source.links.services)
|
||||||
|
dispatch(loadServices(services))
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(notify(couldNotGetServices))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateServiceAsync = (
|
||||||
|
source: Source,
|
||||||
|
service: NewService
|
||||||
|
) => (dispatch) => Promise<void>
|
||||||
|
|
||||||
|
export const createServiceAsync = (
|
||||||
|
source: Source,
|
||||||
|
service: NewService
|
||||||
|
) => async (dispatch): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const s = await createServiceAJAX(source, service)
|
||||||
|
dispatch(addService(s))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.data)
|
||||||
|
throw err.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateServiceAsync = (
|
||||||
|
service: Service
|
||||||
|
) => (dispatch) => Promise<void>
|
||||||
|
export const updateServiceAsync = (service: Service) => async (
|
||||||
|
dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const s = await updateServiceAJAX(service)
|
||||||
|
dispatch(updateService(s))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.data)
|
||||||
|
throw err.data
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,120 +0,0 @@
|
||||||
import {
|
|
||||||
deleteSource,
|
|
||||||
getSources as getSourcesAJAX,
|
|
||||||
getKapacitors as getKapacitorsAJAX,
|
|
||||||
updateKapacitor as updateKapacitorAJAX,
|
|
||||||
deleteKapacitor as deleteKapacitorAJAX,
|
|
||||||
} from 'shared/apis'
|
|
||||||
import {notify} from './notifications'
|
|
||||||
import {errorThrown} from 'shared/actions/errors'
|
|
||||||
|
|
||||||
import {HTTP_NOT_FOUND} from 'shared/constants'
|
|
||||||
import {
|
|
||||||
notifyServerError,
|
|
||||||
notifyCouldNotRetrieveKapacitors,
|
|
||||||
notifyCouldNotDeleteKapacitor,
|
|
||||||
} from 'shared/copy/notifications'
|
|
||||||
|
|
||||||
export const loadSources = sources => ({
|
|
||||||
type: 'LOAD_SOURCES',
|
|
||||||
payload: {
|
|
||||||
sources,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateSource = source => ({
|
|
||||||
type: 'SOURCE_UPDATED',
|
|
||||||
payload: {
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const addSource = source => ({
|
|
||||||
type: 'SOURCE_ADDED',
|
|
||||||
payload: {
|
|
||||||
source,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchKapacitors = (source, kapacitors) => ({
|
|
||||||
type: 'LOAD_KAPACITORS',
|
|
||||||
payload: {
|
|
||||||
source,
|
|
||||||
kapacitors,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const setActiveKapacitor = kapacitor => ({
|
|
||||||
type: 'SET_ACTIVE_KAPACITOR',
|
|
||||||
payload: {
|
|
||||||
kapacitor,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteKapacitor = kapacitor => ({
|
|
||||||
type: 'DELETE_KAPACITOR',
|
|
||||||
payload: {
|
|
||||||
kapacitor,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Async action creators
|
|
||||||
|
|
||||||
export const removeAndLoadSources = source => async dispatch => {
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
await deleteSource(source)
|
|
||||||
} catch (err) {
|
|
||||||
// A 404 means that either a concurrent write occurred or the source
|
|
||||||
// passed to this action creator doesn't exist (or is undefined)
|
|
||||||
if (err.status !== HTTP_NOT_FOUND) {
|
|
||||||
// eslint-disable-line no-magic-numbers
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: {sources: newSources},
|
|
||||||
} = await getSourcesAJAX()
|
|
||||||
dispatch(loadSources(newSources))
|
|
||||||
} catch (err) {
|
|
||||||
dispatch(notify(notifyServerError()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchKapacitorsAsync = source => async dispatch => {
|
|
||||||
try {
|
|
||||||
const {data} = await getKapacitorsAJAX(source)
|
|
||||||
dispatch(fetchKapacitors(source, data.kapacitors))
|
|
||||||
} catch (err) {
|
|
||||||
dispatch(notify(notifyCouldNotRetrieveKapacitors(source.id)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setActiveKapacitorAsync = kapacitor => async dispatch => {
|
|
||||||
// eagerly update the redux state
|
|
||||||
dispatch(setActiveKapacitor(kapacitor))
|
|
||||||
const kapacitorPost = {...kapacitor, active: true}
|
|
||||||
await updateKapacitorAJAX(kapacitorPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteKapacitorAsync = kapacitor => async dispatch => {
|
|
||||||
try {
|
|
||||||
await deleteKapacitorAJAX(kapacitor)
|
|
||||||
dispatch(deleteKapacitor(kapacitor))
|
|
||||||
} catch (err) {
|
|
||||||
dispatch(notify(notifyCouldNotDeleteKapacitor()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSourcesAsync = () => async dispatch => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
data: {sources},
|
|
||||||
} = await getSourcesAJAX()
|
|
||||||
dispatch(loadSources(sources))
|
|
||||||
return sources
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(errorThrown(error))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
import {
|
||||||
|
deleteSource,
|
||||||
|
getSources as getSourcesAJAX,
|
||||||
|
getKapacitors as getKapacitorsAJAX,
|
||||||
|
updateKapacitor as updateKapacitorAJAX,
|
||||||
|
deleteKapacitor as deleteKapacitorAJAX,
|
||||||
|
} from 'src/shared/apis'
|
||||||
|
|
||||||
|
import {notify} from './notifications'
|
||||||
|
import {errorThrown} from 'src/shared/actions/errors'
|
||||||
|
|
||||||
|
import {HTTP_NOT_FOUND} from 'src/shared/constants'
|
||||||
|
import {
|
||||||
|
notifyServerError,
|
||||||
|
notifyCouldNotRetrieveKapacitors,
|
||||||
|
notifyCouldNotDeleteKapacitor,
|
||||||
|
} from 'src/shared/copy/notifications'
|
||||||
|
|
||||||
|
import {Source, Kapacitor} from 'src/types'
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| ActionLoadSources
|
||||||
|
| ActionUpdateSource
|
||||||
|
| ActionAddSource
|
||||||
|
| ActionFetchKapacitors
|
||||||
|
| ActionSetActiveKapacitor
|
||||||
|
| ActionDeleteKapacitor
|
||||||
|
|
||||||
|
// Load Sources
|
||||||
|
export type LoadSources = (sources: Source[]) => ActionLoadSources
|
||||||
|
export interface ActionLoadSources {
|
||||||
|
type: 'LOAD_SOURCES'
|
||||||
|
payload: {
|
||||||
|
sources: Source[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadSources = (sources: Source[]): ActionLoadSources => ({
|
||||||
|
type: 'LOAD_SOURCES',
|
||||||
|
payload: {
|
||||||
|
sources,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type UpdateSource = (source: Source) => ActionUpdateSource
|
||||||
|
export interface ActionUpdateSource {
|
||||||
|
type: 'SOURCE_UPDATED'
|
||||||
|
payload: {
|
||||||
|
source: Source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateSource = (source: Source): ActionUpdateSource => ({
|
||||||
|
type: 'SOURCE_UPDATED',
|
||||||
|
payload: {
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AddSource = (source: Source) => ActionAddSource
|
||||||
|
export interface ActionAddSource {
|
||||||
|
type: 'SOURCE_ADDED'
|
||||||
|
payload: {
|
||||||
|
source: Source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addSource = (source: Source): ActionAddSource => ({
|
||||||
|
type: 'SOURCE_ADDED',
|
||||||
|
payload: {
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type FetchKapacitors = (
|
||||||
|
source: Source,
|
||||||
|
kapacitors: Kapacitor[]
|
||||||
|
) => ActionFetchKapacitors
|
||||||
|
|
||||||
|
export interface ActionFetchKapacitors {
|
||||||
|
type: 'LOAD_KAPACITORS'
|
||||||
|
payload: {
|
||||||
|
source: Source
|
||||||
|
kapacitors: Kapacitor[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchKapacitors = (
|
||||||
|
source: Source,
|
||||||
|
kapacitors: Kapacitor[]
|
||||||
|
): ActionFetchKapacitors => ({
|
||||||
|
type: 'LOAD_KAPACITORS',
|
||||||
|
payload: {
|
||||||
|
source,
|
||||||
|
kapacitors,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SetActiveKapacitor = (
|
||||||
|
kapacitor: Kapacitor
|
||||||
|
) => ActionSetActiveKapacitor
|
||||||
|
|
||||||
|
export interface ActionSetActiveKapacitor {
|
||||||
|
type: 'SET_ACTIVE_KAPACITOR'
|
||||||
|
payload: {
|
||||||
|
kapacitor: Kapacitor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setActiveKapacitor = (
|
||||||
|
kapacitor: Kapacitor
|
||||||
|
): ActionSetActiveKapacitor => ({
|
||||||
|
type: 'SET_ACTIVE_KAPACITOR',
|
||||||
|
payload: {
|
||||||
|
kapacitor,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DeleteKapacitor = (kapacitor: Kapacitor) => ActionDeleteKapacitor
|
||||||
|
export interface ActionDeleteKapacitor {
|
||||||
|
type: 'DELETE_KAPACITOR'
|
||||||
|
payload: {
|
||||||
|
kapacitor: Kapacitor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteKapacitor = (kapacitor: Kapacitor) => ({
|
||||||
|
type: 'DELETE_KAPACITOR',
|
||||||
|
payload: {
|
||||||
|
kapacitor,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type RemoveAndLoadSources = (
|
||||||
|
source: Source
|
||||||
|
) => (dispatch) => Promise<void>
|
||||||
|
// Async action creators
|
||||||
|
export const removeAndLoadSources = (source: Source) => async (
|
||||||
|
dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
await deleteSource(source)
|
||||||
|
} catch (err) {
|
||||||
|
// A 404 means that either a concurrent write occurred or the source
|
||||||
|
// passed to this action creator doesn't exist (or is undefined)
|
||||||
|
if (err.status !== HTTP_NOT_FOUND) {
|
||||||
|
// eslint-disable-line no-magic-numbers
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: {sources: newSources},
|
||||||
|
} = await getSourcesAJAX()
|
||||||
|
dispatch(loadSources(newSources))
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(notify(notifyServerError()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FetchKapacitorsAsync = (
|
||||||
|
source: Source
|
||||||
|
) => (dispatch) => Promise<void>
|
||||||
|
|
||||||
|
export const fetchKapacitorsAsync = (source: Source) => async (
|
||||||
|
dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const {data} = await getKapacitorsAJAX(source)
|
||||||
|
dispatch(fetchKapacitors(source, data.kapacitors))
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(notify(notifyCouldNotRetrieveKapacitors(source.id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SetActiveKapacitorAsync = (
|
||||||
|
source: Source
|
||||||
|
) => (dispatch) => Promise<void>
|
||||||
|
|
||||||
|
export const setActiveKapacitorAsync = (kapacitor: Kapacitor) => async (
|
||||||
|
dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
// eagerly update the redux state
|
||||||
|
dispatch(setActiveKapacitor(kapacitor))
|
||||||
|
const kapacitorPost = {...kapacitor, active: true}
|
||||||
|
await updateKapacitorAJAX(kapacitorPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeleteKapacitorAsync = (
|
||||||
|
source: Source
|
||||||
|
) => (dispatch) => Promise<void>
|
||||||
|
|
||||||
|
export const deleteKapacitorAsync = (kapacitor: Kapacitor) => async (
|
||||||
|
dispatch
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await deleteKapacitorAJAX(kapacitor)
|
||||||
|
dispatch(deleteKapacitor(kapacitor))
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(notify(notifyCouldNotDeleteKapacitor()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSourcesAsync = () => async (dispatch): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: {sources},
|
||||||
|
} = await getSourcesAJAX()
|
||||||
|
dispatch(loadSources(sources))
|
||||||
|
return sources
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(errorThrown(error))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
import AJAX from 'utils/ajax'
|
import AJAX from 'src/utils/ajax'
|
||||||
import {AlertTypes} from 'src/kapacitor/constants'
|
import {AlertTypes} from 'src/kapacitor/constants'
|
||||||
|
import {Kapacitor, Source, Service, NewService} from 'src/types'
|
||||||
|
|
||||||
export function getSources() {
|
export function getSources() {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
|
url: null,
|
||||||
resource: 'sources',
|
resource: 'sources',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSource(id) {
|
export function getSource(id) {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
|
url: null,
|
||||||
resource: 'sources',
|
resource: 'sources',
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
|
@ -16,6 +19,7 @@ export function getSource(id) {
|
||||||
|
|
||||||
export function createSource(attributes) {
|
export function createSource(attributes) {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
|
url: null,
|
||||||
resource: 'sources',
|
resource: 'sources',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: attributes,
|
data: attributes,
|
||||||
|
@ -123,12 +127,12 @@ export function createKapacitor(
|
||||||
export function updateKapacitor({
|
export function updateKapacitor({
|
||||||
links,
|
links,
|
||||||
url,
|
url,
|
||||||
name = 'My Kapacitor',
|
name = 'My Kaacitor',
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
active,
|
active,
|
||||||
insecureSkipVerify,
|
insecureSkipVerify,
|
||||||
}) {
|
}: Kapacitor) {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
url: links.self,
|
url: links.self,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
@ -282,7 +286,7 @@ export function deleteKapacitorTask(kapacitor, id) {
|
||||||
return kapacitorProxy(kapacitor, 'DELETE', `/kapacitor/v1/tasks/${id}`, '')
|
return kapacitorProxy(kapacitor, 'DELETE', `/kapacitor/v1/tasks/${id}`, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function kapacitorProxy(kapacitor, method, path, body) {
|
export function kapacitorProxy(kapacitor, method, path, body?) {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method,
|
method,
|
||||||
url: kapacitor.links.proxy,
|
url: kapacitor.links.proxy,
|
||||||
|
@ -293,9 +297,63 @@ export function kapacitorProxy(kapacitor, method, path, body) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getQueryConfigAndStatus = (url, queries, tempVars) =>
|
export const getQueryConfigAndStatus = (url, queries, tempVars = []) =>
|
||||||
AJAX({
|
AJAX({
|
||||||
url,
|
url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {queries, tempVars},
|
data: {queries, tempVars},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getServices = async (url: string): Promise<Service[]> => {
|
||||||
|
try {
|
||||||
|
const {data} = await AJAX({
|
||||||
|
url,
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
|
||||||
|
return data.services
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createService = async (
|
||||||
|
source: Source,
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
name = 'My IFQLD',
|
||||||
|
type,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
insecureSkipVerify,
|
||||||
|
}: NewService
|
||||||
|
): Promise<Service> => {
|
||||||
|
try {
|
||||||
|
const {data} = await AJAX({
|
||||||
|
url: source.links.services,
|
||||||
|
method: 'POST',
|
||||||
|
data: {url, name, type, username, password, insecureSkipVerify},
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateService = async (service: Service): Promise<Service> => {
|
||||||
|
try {
|
||||||
|
const {data} = await AJAX({
|
||||||
|
url: service.links.self,
|
||||||
|
method: 'PATCH',
|
||||||
|
data: service,
|
||||||
|
})
|
||||||
|
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,16 @@ import React, {PureComponent, ReactElement, MouseEvent} from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import calculateSize from 'calculate-size'
|
import calculateSize from 'calculate-size'
|
||||||
|
|
||||||
|
import DivisionHeader from 'src/shared/components/threesizer/DivisionHeader'
|
||||||
import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants/index'
|
import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants/index'
|
||||||
|
import {MenuItem} from 'src/shared/components/threesizer/DivisionMenu'
|
||||||
|
|
||||||
const NOOP = () => {}
|
const NOOP = () => {}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name?: string
|
name?: string
|
||||||
handleDisplay?: string
|
handleDisplay?: string
|
||||||
|
menuOptions?: MenuItem[]
|
||||||
handlePixels: number
|
handlePixels: number
|
||||||
id: string
|
id: string
|
||||||
size: number
|
size: number
|
||||||
|
@ -19,6 +22,9 @@ interface Props {
|
||||||
render: (visibility: string) => ReactElement<any>
|
render: (visibility: string) => ReactElement<any>
|
||||||
onHandleStartDrag: (id: string, e: MouseEvent<HTMLElement>) => void
|
onHandleStartDrag: (id: string, e: MouseEvent<HTMLElement>) => void
|
||||||
onDoubleClick: (id: string) => void
|
onDoubleClick: (id: string) => void
|
||||||
|
onMaximize: (id: string) => void
|
||||||
|
onMinimize: (id: string) => void
|
||||||
|
headerButtons: JSX.Element[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Style {
|
interface Style {
|
||||||
|
@ -59,7 +65,7 @@ class Division extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {name, render, draggable} = this.props
|
const {name, render, draggable, menuOptions, headerButtons} = this.props
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={this.containerClass}
|
className={this.containerClass}
|
||||||
|
@ -77,7 +83,14 @@ class Division extends PureComponent<Props> {
|
||||||
<div className={this.titleClass}>{name}</div>
|
<div className={this.titleClass}>{name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={this.contentsClass} style={this.contentStyle}>
|
<div className={this.contentsClass} style={this.contentStyle}>
|
||||||
{name && <div className="threesizer--header" />}
|
{name && (
|
||||||
|
<DivisionHeader
|
||||||
|
buttons={headerButtons}
|
||||||
|
menuOptions={menuOptions}
|
||||||
|
onMinimize={this.handleMinimize}
|
||||||
|
onMaximize={this.handleMaximize}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="threesizer--body">{render(this.visibility)}</div>
|
<div className="threesizer--body">{render(this.visibility)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -162,7 +175,10 @@ class Division extends PureComponent<Props> {
|
||||||
private get handleClass(): string {
|
private get handleClass(): string {
|
||||||
const {draggable, orientation} = this.props
|
const {draggable, orientation} = this.props
|
||||||
|
|
||||||
|
const collapsed = orientation === HANDLE_VERTICAL && this.isTitleObscured
|
||||||
|
|
||||||
return classnames('threesizer--handle', {
|
return classnames('threesizer--handle', {
|
||||||
|
'threesizer--collapsed': collapsed,
|
||||||
disabled: !draggable,
|
disabled: !draggable,
|
||||||
dragging: this.isDragging,
|
dragging: this.isDragging,
|
||||||
vertical: orientation === HANDLE_VERTICAL,
|
vertical: orientation === HANDLE_VERTICAL,
|
||||||
|
@ -223,6 +239,16 @@ class Division extends PureComponent<Props> {
|
||||||
|
|
||||||
onDoubleClick(id)
|
onDoubleClick(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleMinimize = (): void => {
|
||||||
|
const {id, onMinimize} = this.props
|
||||||
|
onMinimize(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMaximize = (): void => {
|
||||||
|
const {id, onMaximize} = this.props
|
||||||
|
onMaximize(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Division
|
export default Division
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import DivisionMenu, {
|
||||||
|
MenuItem,
|
||||||
|
} from 'src/shared/components/threesizer/DivisionMenu'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onMinimize: () => void
|
||||||
|
onMaximize: () => void
|
||||||
|
buttons: JSX.Element[]
|
||||||
|
menuOptions?: MenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
class DivisionHeader extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<div className="threesizer--header">
|
||||||
|
{this.props.buttons.map(b => b)}
|
||||||
|
<DivisionMenu menuItems={this.menuItems} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get menuItems(): MenuItem[] {
|
||||||
|
const {onMaximize, onMinimize, menuOptions} = this.props
|
||||||
|
return [
|
||||||
|
...menuOptions,
|
||||||
|
{
|
||||||
|
action: onMaximize,
|
||||||
|
text: 'Maximize',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: onMinimize,
|
||||||
|
text: 'Minimize',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DivisionHeader
|
|
@ -0,0 +1,88 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import uuid from 'uuid'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||||
|
|
||||||
|
export interface MenuItem {
|
||||||
|
text: string
|
||||||
|
action: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
menuItems: MenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
expanded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class DivisionMenu extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
expanded: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {expanded} = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClickOutside onClickOutside={this.handleCollapseMenu}>
|
||||||
|
<div className={this.menuClass}>
|
||||||
|
<button className={this.buttonClass} onClick={this.handleExpandMenu}>
|
||||||
|
<span className="icon caret-down" />
|
||||||
|
</button>
|
||||||
|
{expanded && this.renderMenu}
|
||||||
|
</div>
|
||||||
|
</ClickOutside>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleExpandMenu = (): void => {
|
||||||
|
this.setState({expanded: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCollapseMenu = (): void => {
|
||||||
|
this.setState({expanded: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMenuItemClick = action => (): void => {
|
||||||
|
this.setState({expanded: false})
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
|
||||||
|
private get menuClass(): string {
|
||||||
|
const {expanded} = this.state
|
||||||
|
|
||||||
|
return classnames('dropdown threesizer--menu', {open: expanded})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get buttonClass(): string {
|
||||||
|
const {expanded} = this.state
|
||||||
|
|
||||||
|
return classnames('btn btn-sm btn-square btn-default', {
|
||||||
|
active: expanded,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get renderMenu(): JSX.Element {
|
||||||
|
const {menuItems} = this.props
|
||||||
|
return (
|
||||||
|
<ul className="dropdown-menu">
|
||||||
|
{menuItems.map(item => (
|
||||||
|
<li
|
||||||
|
key={uuid.v4()}
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={this.handleMenuItemClick(item.action)}
|
||||||
|
>
|
||||||
|
<a href="#">{item.text}</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DivisionMenu
|
|
@ -3,8 +3,10 @@ import classnames from 'classnames'
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import ResizeDivision from 'src/shared/components/ResizeDivision'
|
import Division from 'src/shared/components/threesizer/Division'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
import {MenuItem} from 'src/shared/components/threesizer/DivisionMenu'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HANDLE_NONE,
|
HANDLE_NONE,
|
||||||
HANDLE_PIXELS,
|
HANDLE_PIXELS,
|
||||||
|
@ -28,20 +30,22 @@ interface State {
|
||||||
dragEvent: any
|
dragEvent: any
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Division {
|
interface DivisionProps {
|
||||||
name?: string
|
name?: string
|
||||||
handleDisplay?: string
|
handleDisplay?: string
|
||||||
handlePixels?: number
|
handlePixels?: number
|
||||||
|
headerButtons?: JSX.Element[]
|
||||||
|
menuOptions: MenuItem[]
|
||||||
render: (visibility?: string) => ReactElement<any>
|
render: (visibility?: string) => ReactElement<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DivisionState extends Division {
|
interface DivisionState extends DivisionProps {
|
||||||
id: string
|
id: string
|
||||||
size: number
|
size: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
divisions: Division[]
|
divisions: DivisionProps[]
|
||||||
orientation: string
|
orientation: string
|
||||||
containerClass?: string
|
containerClass?: string
|
||||||
}
|
}
|
||||||
|
@ -129,7 +133,7 @@ class Threesizer extends Component<Props, State> {
|
||||||
ref={r => (this.containerRef = r)}
|
ref={r => (this.containerRef = r)}
|
||||||
>
|
>
|
||||||
{divisions.map((d, i) => (
|
{divisions.map((d, i) => (
|
||||||
<ResizeDivision
|
<Division
|
||||||
key={d.id}
|
key={d.id}
|
||||||
id={d.id}
|
id={d.id}
|
||||||
name={d.name}
|
name={d.name}
|
||||||
|
@ -140,9 +144,13 @@ class Threesizer extends Component<Props, State> {
|
||||||
handlePixels={d.handlePixels}
|
handlePixels={d.handlePixels}
|
||||||
handleDisplay={d.handleDisplay}
|
handleDisplay={d.handleDisplay}
|
||||||
activeHandleID={activeHandleID}
|
activeHandleID={activeHandleID}
|
||||||
|
onMaximize={this.handleMaximize}
|
||||||
|
onMinimize={this.handleMinimize}
|
||||||
onDoubleClick={this.handleDoubleClick}
|
onDoubleClick={this.handleDoubleClick}
|
||||||
onHandleStartDrag={this.handleStartDrag}
|
|
||||||
render={this.props.divisions[i].render}
|
render={this.props.divisions[i].render}
|
||||||
|
onHandleStartDrag={this.handleStartDrag}
|
||||||
|
menuOptions={this.props.divisions[i].menuOptions}
|
||||||
|
headerButtons={this.props.divisions[i].headerButtons}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -209,6 +217,50 @@ class Threesizer extends Component<Props, State> {
|
||||||
this.setState({divisions})
|
this.setState({divisions})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleMaximize = (id: string): void => {
|
||||||
|
const maxDiv = this.state.divisions.find(d => d.id === id)
|
||||||
|
|
||||||
|
if (!maxDiv) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const divisions = this.state.divisions.map(d => {
|
||||||
|
if (d.id !== id) {
|
||||||
|
return {...d, size: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...d, size: 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({divisions})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMinimize = (id: string): void => {
|
||||||
|
const minDiv = this.state.divisions.find(d => d.id === id)
|
||||||
|
const numDivisions = this.state.divisions.length
|
||||||
|
|
||||||
|
if (!minDiv) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let size
|
||||||
|
if (numDivisions <= 1) {
|
||||||
|
size = 1
|
||||||
|
} else {
|
||||||
|
size = 1 / (this.state.divisions.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const divisions = this.state.divisions.map(d => {
|
||||||
|
if (d.id !== id) {
|
||||||
|
return {...d, size}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...d, size: 0}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({divisions})
|
||||||
|
}
|
||||||
|
|
||||||
private equalize = () => {
|
private equalize = () => {
|
||||||
const denominator = this.state.divisions.length
|
const denominator = this.state.divisions.length
|
||||||
const divisions = this.state.divisions.map(d => {
|
const divisions = this.state.divisions.map(d => {
|
|
@ -1,7 +1,7 @@
|
||||||
// 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 {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'shared/constants/index'
|
import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index'
|
||||||
|
|
||||||
const defaultErrorNotification = {
|
const defaultErrorNotification = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
@ -131,7 +131,7 @@ export const notifySourceUdpateFailed = (sourceName, errorMessage) => ({
|
||||||
message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`,
|
message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const notifySourceDeleted = sourceName => ({
|
export const notifySourceDeleted = (sourceName: string) => ({
|
||||||
...defaultSuccessNotification,
|
...defaultSuccessNotification,
|
||||||
icon: 'server2',
|
icon: 'server2',
|
||||||
message: `${sourceName} deleted successfully.`,
|
message: `${sourceName} deleted successfully.`,
|
||||||
|
@ -536,7 +536,7 @@ export const notifyTestAlertSent = endpoint => ({
|
||||||
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, errorMessage?) => ({
|
||||||
...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 ? `: ${errorMessage}` : '.'
|
||||||
|
@ -608,3 +608,35 @@ export const notifyKapacitorNotFound = () => ({
|
||||||
...defaultErrorNotification,
|
...defaultErrorNotification,
|
||||||
message: 'We could not find a Kapacitor configuration for this source.',
|
message: 'We could not find a Kapacitor configuration for this source.',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// IFQL notifications
|
||||||
|
export const analyzeSuccess = {
|
||||||
|
...defaultSuccessNotification,
|
||||||
|
message: 'No errors found. Happy Happy Joy Joy!',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service notifications
|
||||||
|
export const couldNotGetServices = {
|
||||||
|
...defaultErrorNotification,
|
||||||
|
message: 'We could not get services',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ifqlCreated = {
|
||||||
|
...defaultSuccessNotification,
|
||||||
|
message: 'IFQL Connection Created. Script your heart out!',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ifqlNotCreated = (message: string) => ({
|
||||||
|
...defaultErrorNotification,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ifqlNotUpdated = (message: string) => ({
|
||||||
|
...defaultErrorNotification,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ifqlUpdated = {
|
||||||
|
...defaultSuccessNotification,
|
||||||
|
message: 'Connection Updated. Rejoice!',
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import {Action} from 'src/shared/actions/services'
|
||||||
|
import {Service} from 'src/types'
|
||||||
|
|
||||||
|
export const initialState: Service[] = []
|
||||||
|
|
||||||
|
const servicesReducer = (state = initialState, action: Action): Service[] => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'LOAD_SERVICES': {
|
||||||
|
return action.payload.services
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ADD_SERVICE': {
|
||||||
|
const {service} = action.payload
|
||||||
|
return [...state, service]
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'DELETE_SERVICE': {
|
||||||
|
const {service} = action.payload
|
||||||
|
|
||||||
|
return state.filter(s => s.id !== service.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'UPDATE_SERVICE': {
|
||||||
|
const {service} = action.payload
|
||||||
|
const newState = state.map(s => {
|
||||||
|
if (s.id === service.id) {
|
||||||
|
return {...s, ...service}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...s}
|
||||||
|
})
|
||||||
|
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_ACTIVE_SERVICE': {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
export default servicesReducer
|
|
@ -1,10 +1,10 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import {Source, Kapacitor} from 'src/types'
|
||||||
|
import {Action} from 'src/shared/actions/sources'
|
||||||
|
|
||||||
const getInitialState = () => []
|
export const initialState: Source[] = []
|
||||||
|
|
||||||
const initialState = getInitialState()
|
const sourcesReducer = (state = initialState, action: Action): Source[] => {
|
||||||
|
|
||||||
const sourcesReducer = (state = initialState, action) => {
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'LOAD_SOURCES': {
|
case 'LOAD_SOURCES': {
|
||||||
return action.payload.sources
|
return action.payload.sources
|
||||||
|
@ -59,7 +59,11 @@ const sourcesReducer = (state = initialState, action) => {
|
||||||
const {kapacitor} = action.payload
|
const {kapacitor} = action.payload
|
||||||
const updatedSources = _.cloneDeep(state)
|
const updatedSources = _.cloneDeep(state)
|
||||||
updatedSources.forEach(source => {
|
updatedSources.forEach(source => {
|
||||||
const index = _.findIndex(source.kapacitors, k => k.id === kapacitor.id)
|
const index = _.findIndex<Kapacitor>(
|
||||||
|
source.kapacitors,
|
||||||
|
k => k.id === kapacitor.id
|
||||||
|
)
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
source.kapacitors.splice(index, 1)
|
source.kapacitors.splice(index, 1)
|
||||||
}
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import {Link} from 'react-router'
|
||||||
|
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||||
|
|
||||||
|
import {Source} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: Source
|
||||||
|
currentSource: Source
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionLink extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {source} = this.props
|
||||||
|
return (
|
||||||
|
<h5 className="margin-zero">
|
||||||
|
<Authorized
|
||||||
|
requiredRole={EDITOR_ROLE}
|
||||||
|
replaceWithIfNotAuthorized={<strong>{source.name}</strong>}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={`${location.pathname}/${source.id}/edit`}
|
||||||
|
className={this.className}
|
||||||
|
>
|
||||||
|
<strong>{source.name}</strong>
|
||||||
|
{this.default}
|
||||||
|
</Link>
|
||||||
|
</Authorized>
|
||||||
|
</h5>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get className(): string {
|
||||||
|
if (this.isCurrentSource) {
|
||||||
|
return 'link-success'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private get default(): string {
|
||||||
|
const {source} = this.props
|
||||||
|
if (source.default) {
|
||||||
|
return ' (Default)'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isCurrentSource(): boolean {
|
||||||
|
const {source, currentSource} = this.props
|
||||||
|
return source.id === currentSource.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConnectionLink
|
|
@ -1,247 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {Link, withRouter} from 'react-router'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
|
|
||||||
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
|
||||||
|
|
||||||
import Dropdown from 'shared/components/Dropdown'
|
|
||||||
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
|
|
||||||
import ConfirmButton from 'shared/components/ConfirmButton'
|
|
||||||
|
|
||||||
const kapacitorDropdown = (
|
|
||||||
kapacitors,
|
|
||||||
source,
|
|
||||||
router,
|
|
||||||
setActiveKapacitor,
|
|
||||||
handleDeleteKapacitor
|
|
||||||
) => {
|
|
||||||
if (!kapacitors || kapacitors.length === 0) {
|
|
||||||
return (
|
|
||||||
<Authorized requiredRole={EDITOR_ROLE}>
|
|
||||||
<Link
|
|
||||||
to={`/sources/${source.id}/kapacitors/new`}
|
|
||||||
className="btn btn-xs btn-default"
|
|
||||||
>
|
|
||||||
<span className="icon plus" /> Add Kapacitor Connection
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const kapacitorItems = kapacitors.map(k => {
|
|
||||||
return {
|
|
||||||
text: k.name,
|
|
||||||
resource: `/sources/${source.id}/kapacitors/${k.id}`,
|
|
||||||
kapacitor: k,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const activeKapacitor = kapacitors.find(k => k.active)
|
|
||||||
|
|
||||||
let selected = ''
|
|
||||||
if (activeKapacitor) {
|
|
||||||
selected = activeKapacitor.name
|
|
||||||
} else {
|
|
||||||
selected = kapacitorItems[0].text
|
|
||||||
}
|
|
||||||
|
|
||||||
const unauthorizedDropdown = (
|
|
||||||
<div className="source-table--kapacitor__view-only">{selected}</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Authorized
|
|
||||||
requiredRole={EDITOR_ROLE}
|
|
||||||
replaceWithIfNotAuthorized={unauthorizedDropdown}
|
|
||||||
>
|
|
||||||
<Dropdown
|
|
||||||
className="dropdown-260"
|
|
||||||
buttonColor="btn-primary"
|
|
||||||
buttonSize="btn-xs"
|
|
||||||
items={kapacitorItems}
|
|
||||||
onChoose={setActiveKapacitor}
|
|
||||||
addNew={{
|
|
||||||
url: `/sources/${source.id}/kapacitors/new`,
|
|
||||||
text: 'Add Kapacitor Connection',
|
|
||||||
}}
|
|
||||||
actions={[
|
|
||||||
{
|
|
||||||
icon: 'pencil',
|
|
||||||
text: 'edit',
|
|
||||||
handler: item => {
|
|
||||||
router.push(`${item.resource}/edit`)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'trash',
|
|
||||||
text: 'delete',
|
|
||||||
handler: item => {
|
|
||||||
handleDeleteKapacitor(item.kapacitor)
|
|
||||||
},
|
|
||||||
confirmable: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
selected={selected}
|
|
||||||
/>
|
|
||||||
</Authorized>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const InfluxTable = ({
|
|
||||||
source,
|
|
||||||
router,
|
|
||||||
sources,
|
|
||||||
location,
|
|
||||||
setActiveKapacitor,
|
|
||||||
handleDeleteSource,
|
|
||||||
handleDeleteKapacitor,
|
|
||||||
isUsingAuth,
|
|
||||||
me,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-md-12">
|
|
||||||
<div className="panel">
|
|
||||||
<div className="panel-heading">
|
|
||||||
<h2 className="panel-title">
|
|
||||||
{isUsingAuth ? (
|
|
||||||
<span>
|
|
||||||
Connections for <em>{me.currentOrganization.name}</em>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>Connections</span>
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
<Authorized requiredRole={EDITOR_ROLE}>
|
|
||||||
<Link
|
|
||||||
to={`/sources/${source.id}/manage-sources/new`}
|
|
||||||
className="btn btn-sm btn-primary"
|
|
||||||
>
|
|
||||||
<span className="icon plus" /> Add Connection
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
|
||||||
</div>
|
|
||||||
<div className="panel-body">
|
|
||||||
<table className="table v-center margin-bottom-zero table-highlight">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="source-table--connect-col" />
|
|
||||||
<th>InfluxDB Connection</th>
|
|
||||||
<th className="text-right" />
|
|
||||||
<th>
|
|
||||||
Kapacitor Connection{' '}
|
|
||||||
<QuestionMarkTooltip
|
|
||||||
tipID="kapacitor-node-helper"
|
|
||||||
tipContent={
|
|
||||||
'<p>Kapacitor Connections are<br/>scoped per InfluxDB Connection.<br/>Only one can be active at a time.</p>'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{sources.map(s => {
|
|
||||||
return (
|
|
||||||
<tr
|
|
||||||
key={s.id}
|
|
||||||
className={s.id === source.id ? 'highlight' : null}
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
{s.id === source.id ? (
|
|
||||||
<div className="btn btn-success btn-xs source-table--connect">
|
|
||||||
Connected
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
className="btn btn-default btn-xs source-table--connect"
|
|
||||||
to={`/sources/${s.id}/hosts`}
|
|
||||||
>
|
|
||||||
Connect
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<h5 className="margin-zero">
|
|
||||||
<Authorized
|
|
||||||
requiredRole={EDITOR_ROLE}
|
|
||||||
replaceWithIfNotAuthorized={
|
|
||||||
<strong>{s.name}</strong>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
to={`${location.pathname}/${s.id}/edit`}
|
|
||||||
className={
|
|
||||||
s.id === source.id ? 'link-success' : null
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<strong>{s.name}</strong>
|
|
||||||
{s.default ? ' (Default)' : null}
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
|
||||||
</h5>
|
|
||||||
<span>{s.url}</span>
|
|
||||||
</td>
|
|
||||||
<td className="text-right">
|
|
||||||
<Authorized requiredRole={EDITOR_ROLE}>
|
|
||||||
<ConfirmButton
|
|
||||||
customClass="delete-source table--show-on-row-hover"
|
|
||||||
type="btn-danger"
|
|
||||||
size="btn-xs"
|
|
||||||
text="Delete Connection"
|
|
||||||
confirmAction={handleDeleteSource(s)}
|
|
||||||
/>
|
|
||||||
</Authorized>
|
|
||||||
</td>
|
|
||||||
<td className="source-table--kapacitor">
|
|
||||||
{kapacitorDropdown(
|
|
||||||
s.kapacitors,
|
|
||||||
s,
|
|
||||||
router,
|
|
||||||
setActiveKapacitor,
|
|
||||||
handleDeleteKapacitor
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const {array, bool, func, shape, string} = PropTypes
|
|
||||||
|
|
||||||
InfluxTable.propTypes = {
|
|
||||||
handleDeleteSource: func.isRequired,
|
|
||||||
location: shape({
|
|
||||||
pathname: string.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
router: PropTypes.shape({
|
|
||||||
push: PropTypes.func.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
source: shape({
|
|
||||||
id: string.isRequired,
|
|
||||||
links: shape({
|
|
||||||
proxy: string.isRequired,
|
|
||||||
self: string.isRequired,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
sources: array.isRequired,
|
|
||||||
setActiveKapacitor: func.isRequired,
|
|
||||||
handleDeleteKapacitor: func.isRequired,
|
|
||||||
me: shape({
|
|
||||||
currentOrganization: shape({
|
|
||||||
id: string.isRequired,
|
|
||||||
name: string.isRequired,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
isUsingAuth: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me})
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withRouter(InfluxTable))
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {SetActiveKapacitor, DeleteKapacitor} from 'src/shared/actions/sources'
|
||||||
|
|
||||||
|
import InfluxTableHead from 'src/sources/components/InfluxTableHead'
|
||||||
|
import InfluxTableHeader from 'src/sources/components/InfluxTableHeader'
|
||||||
|
import InfluxTableRow from 'src/sources/components/InfluxTableRow'
|
||||||
|
|
||||||
|
import {Source, Me} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
me: Me
|
||||||
|
source: Source
|
||||||
|
sources: Source[]
|
||||||
|
isUsingAuth: boolean
|
||||||
|
deleteKapacitor: DeleteKapacitor
|
||||||
|
setActiveKapacitor: SetActiveKapacitor
|
||||||
|
onDeleteSource: (source: Source) => () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfluxTable extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
sources,
|
||||||
|
setActiveKapacitor,
|
||||||
|
onDeleteSource,
|
||||||
|
deleteKapacitor,
|
||||||
|
isUsingAuth,
|
||||||
|
me,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-12">
|
||||||
|
<div className="panel">
|
||||||
|
<InfluxTableHeader
|
||||||
|
me={me}
|
||||||
|
source={source}
|
||||||
|
isUsingAuth={isUsingAuth}
|
||||||
|
/>
|
||||||
|
<div className="panel-body">
|
||||||
|
<table className="table v-center margin-bottom-zero table-highlight">
|
||||||
|
<InfluxTableHead />
|
||||||
|
<tbody>
|
||||||
|
{sources.map(s => {
|
||||||
|
return (
|
||||||
|
<InfluxTableRow
|
||||||
|
key={s.id}
|
||||||
|
source={s}
|
||||||
|
currentSource={source}
|
||||||
|
onDeleteSource={onDeleteSource}
|
||||||
|
deleteKapacitor={deleteKapacitor}
|
||||||
|
setActiveKapacitor={setActiveKapacitor}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me})
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(InfluxTable)
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, {SFC, ReactElement} from 'react'
|
||||||
|
|
||||||
|
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
|
||||||
|
|
||||||
|
import {KAPACITOR_TOOLTIP_COPY} from 'src/sources/constants'
|
||||||
|
|
||||||
|
const InfluxTableHead: SFC<{}> = (): ReactElement<
|
||||||
|
HTMLTableHeaderCellElement
|
||||||
|
> => {
|
||||||
|
return (
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="source-table--connect-col" />
|
||||||
|
<th>InfluxDB Connection</th>
|
||||||
|
<th className="text-right" />
|
||||||
|
<th>
|
||||||
|
Kapacitor Connection
|
||||||
|
<QuestionMarkTooltip
|
||||||
|
tipID="kapacitor-node-helper"
|
||||||
|
tipContent={KAPACITOR_TOOLTIP_COPY}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfluxTableHead
|
|
@ -0,0 +1,47 @@
|
||||||
|
import React, {PureComponent, ReactElement} from 'react'
|
||||||
|
import {Link} from 'react-router'
|
||||||
|
|
||||||
|
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||||
|
|
||||||
|
import {Me, Source} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
me: Me
|
||||||
|
source: Source
|
||||||
|
isUsingAuth: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfluxTableHeader extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {source} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="panel-heading">
|
||||||
|
<h2 className="panel-title">{this.title}</h2>
|
||||||
|
<Authorized requiredRole={EDITOR_ROLE}>
|
||||||
|
<Link
|
||||||
|
to={`/sources/${source.id}/manage-sources/new`}
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
>
|
||||||
|
<span className="icon plus" /> Add Connection
|
||||||
|
</Link>
|
||||||
|
</Authorized>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get title(): ReactElement<HTMLSpanElement> {
|
||||||
|
const {isUsingAuth, me} = this.props
|
||||||
|
if (isUsingAuth) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
Connections for <em>{me.currentOrganization.name}</em>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>Connections</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfluxTableHeader
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React, {PureComponent, ReactElement} from 'react'
|
||||||
|
import {Link} from 'react-router'
|
||||||
|
|
||||||
|
import * as actions from 'src/shared/actions/sources'
|
||||||
|
|
||||||
|
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||||
|
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||||
|
import KapacitorDropdown from 'src/sources/components/KapacitorDropdown'
|
||||||
|
import ConnectionLink from 'src/sources/components/ConnectionLink'
|
||||||
|
|
||||||
|
import {Source} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: Source
|
||||||
|
currentSource: Source
|
||||||
|
onDeleteSource: (source: Source) => void
|
||||||
|
setActiveKapacitor: actions.SetActiveKapacitor
|
||||||
|
deleteKapacitor: actions.DeleteKapacitor
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfluxTableRow extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
currentSource,
|
||||||
|
setActiveKapacitor,
|
||||||
|
deleteKapacitor,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={this.className}>
|
||||||
|
<td>{this.connectButton}</td>
|
||||||
|
<td>
|
||||||
|
<ConnectionLink source={source} currentSource={currentSource} />
|
||||||
|
<span>{source.url}</span>
|
||||||
|
</td>
|
||||||
|
<td className="text-right">
|
||||||
|
<Authorized requiredRole={EDITOR_ROLE}>
|
||||||
|
<ConfirmButton
|
||||||
|
type="btn-danger"
|
||||||
|
size="btn-xs"
|
||||||
|
text="Delete Connection"
|
||||||
|
confirmAction={this.handleDeleteSource}
|
||||||
|
customClass="delete-source table--show-on-row-hover"
|
||||||
|
/>
|
||||||
|
</Authorized>
|
||||||
|
</td>
|
||||||
|
<td className="source-table--kapacitor">
|
||||||
|
<KapacitorDropdown
|
||||||
|
source={source}
|
||||||
|
kapacitors={source.kapacitors}
|
||||||
|
deleteKapacitor={deleteKapacitor}
|
||||||
|
setActiveKapacitor={setActiveKapacitor}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDeleteSource = (): void => {
|
||||||
|
this.props.onDeleteSource(this.props.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get connectButton(): ReactElement<HTMLDivElement> {
|
||||||
|
const {source} = this.props
|
||||||
|
if (this.isCurrentSource) {
|
||||||
|
return (
|
||||||
|
<div className="btn btn-success btn-xs source-table--connect">
|
||||||
|
Connected
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="btn btn-default btn-xs source-table--connect"
|
||||||
|
to={`/sources/${source.id}/hosts`}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get className(): string {
|
||||||
|
if (this.isCurrentSource) {
|
||||||
|
return 'hightlight'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isCurrentSource(): boolean {
|
||||||
|
const {source, currentSource} = this.props
|
||||||
|
return source.id === currentSource.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfluxTableRow
|
|
@ -0,0 +1,118 @@
|
||||||
|
import React, {PureComponent, ReactElement} from 'react'
|
||||||
|
import {Link, withRouter, RouteComponentProps} from 'react-router'
|
||||||
|
|
||||||
|
import Dropdown from 'src/shared/components/Dropdown'
|
||||||
|
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||||
|
import {Source, Kapacitor} from 'src/types'
|
||||||
|
import {SetActiveKapacitor} from 'src/shared/actions/sources'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: Source
|
||||||
|
kapacitors: Kapacitor[]
|
||||||
|
setActiveKapacitor: SetActiveKapacitor
|
||||||
|
deleteKapacitor: (Kapacitor: Kapacitor) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KapacitorItem {
|
||||||
|
text: string
|
||||||
|
resource: string
|
||||||
|
kapacitor: Kapacitor
|
||||||
|
}
|
||||||
|
|
||||||
|
class KapacitorDropdown extends PureComponent<
|
||||||
|
Props & RouteComponentProps<any, any>
|
||||||
|
> {
|
||||||
|
public render() {
|
||||||
|
const {source, router, setActiveKapacitor, deleteKapacitor} = this.props
|
||||||
|
|
||||||
|
if (this.isKapacitorsEmpty) {
|
||||||
|
return (
|
||||||
|
<Authorized requiredRole={EDITOR_ROLE}>
|
||||||
|
<Link
|
||||||
|
to={`/sources/${source.id}/kapacitors/new`}
|
||||||
|
className="btn btn-xs btn-default"
|
||||||
|
>
|
||||||
|
<span className="icon plus" /> Add Kapacitor Connection
|
||||||
|
</Link>
|
||||||
|
</Authorized>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Authorized
|
||||||
|
requiredRole={EDITOR_ROLE}
|
||||||
|
replaceWithIfNotAuthorized={this.UnauthorizedDropdown}
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
className="dropdown-260"
|
||||||
|
buttonColor="btn-primary"
|
||||||
|
buttonSize="btn-xs"
|
||||||
|
items={this.kapacitorItems}
|
||||||
|
onChoose={setActiveKapacitor}
|
||||||
|
addNew={{
|
||||||
|
url: `/sources/${source.id}/kapacitors/new`,
|
||||||
|
text: 'Add Kapacitor Connection',
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
icon: 'pencil',
|
||||||
|
text: 'edit',
|
||||||
|
handler: item => {
|
||||||
|
router.push(`${item.resource}/edit`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'trash',
|
||||||
|
text: 'delete',
|
||||||
|
handler: item => {
|
||||||
|
deleteKapacitor(item.kapacitor)
|
||||||
|
},
|
||||||
|
confirmable: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
selected={this.selected}
|
||||||
|
/>
|
||||||
|
</Authorized>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get UnauthorizedDropdown(): ReactElement<HTMLDivElement> {
|
||||||
|
return (
|
||||||
|
<div className="source-table--kapacitor__view-only">{this.selected}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isKapacitorsEmpty(): boolean {
|
||||||
|
const {kapacitors} = this.props
|
||||||
|
return !kapacitors || kapacitors.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private get kapacitorItems(): KapacitorItem[] {
|
||||||
|
const {kapacitors, source} = this.props
|
||||||
|
|
||||||
|
return kapacitors.map(k => {
|
||||||
|
return {
|
||||||
|
text: k.name,
|
||||||
|
resource: `/sources/${source.id}/kapacitors/${k.id}`,
|
||||||
|
kapacitor: k,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get activeKapacitor(): Kapacitor {
|
||||||
|
return this.props.kapacitors.find(k => k.active)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get selected(): string {
|
||||||
|
let selected = ''
|
||||||
|
if (this.activeKapacitor) {
|
||||||
|
selected = this.activeKapacitor.name
|
||||||
|
} else {
|
||||||
|
selected = this.kapacitorItems[0].text
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter<Props>(KapacitorDropdown)
|
|
@ -0,0 +1,119 @@
|
||||||
|
import React, {PureComponent, ReactElement} from 'react'
|
||||||
|
import {Link, withRouter, RouteComponentProps} from 'react-router'
|
||||||
|
|
||||||
|
import Dropdown from 'src/shared/components/Dropdown'
|
||||||
|
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||||
|
|
||||||
|
import {Source, Service} from 'src/types'
|
||||||
|
import {SetActiveService} from 'src/shared/actions/services'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: Source
|
||||||
|
services: Service[]
|
||||||
|
setActiveService: SetActiveService
|
||||||
|
deleteService: (service: Service) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceItem {
|
||||||
|
text: string
|
||||||
|
resource: string
|
||||||
|
service: Service
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceDropdown extends PureComponent<
|
||||||
|
Props & RouteComponentProps<any, any>
|
||||||
|
> {
|
||||||
|
public render() {
|
||||||
|
const {source, router, setActiveService, deleteService} = this.props
|
||||||
|
|
||||||
|
if (this.isServicesEmpty) {
|
||||||
|
return (
|
||||||
|
<Authorized requiredRole={EDITOR_ROLE}>
|
||||||
|
<Link
|
||||||
|
to={`/sources/${source.id}/services/new`}
|
||||||
|
className="btn btn-xs btn-default"
|
||||||
|
>
|
||||||
|
<span className="icon plus" /> Add Service Connection
|
||||||
|
</Link>
|
||||||
|
</Authorized>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Authorized
|
||||||
|
requiredRole={EDITOR_ROLE}
|
||||||
|
replaceWithIfNotAuthorized={this.UnauthorizedDropdown}
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
className="dropdown-260"
|
||||||
|
buttonColor="btn-primary"
|
||||||
|
buttonSize="btn-xs"
|
||||||
|
items={this.serviceItems}
|
||||||
|
onChoose={setActiveService}
|
||||||
|
addNew={{
|
||||||
|
url: `/sources/${source.id}/services/new`,
|
||||||
|
text: 'Add Service Connection',
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
icon: 'pencil',
|
||||||
|
text: 'edit',
|
||||||
|
handler: item => {
|
||||||
|
router.push(`${item.resource}/edit`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'trash',
|
||||||
|
text: 'delete',
|
||||||
|
handler: item => {
|
||||||
|
deleteService(item.service)
|
||||||
|
},
|
||||||
|
confirmable: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
selected={this.selected}
|
||||||
|
/>
|
||||||
|
</Authorized>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get UnauthorizedDropdown(): ReactElement<HTMLDivElement> {
|
||||||
|
return (
|
||||||
|
<div className="source-table--service__view-only">{this.selected}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isServicesEmpty(): boolean {
|
||||||
|
const {services} = this.props
|
||||||
|
return !services || services.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private get serviceItems(): ServiceItem[] {
|
||||||
|
const {services, source} = this.props
|
||||||
|
|
||||||
|
return services.map(service => {
|
||||||
|
return {
|
||||||
|
text: service.name,
|
||||||
|
resource: `/sources/${source.id}/services/${service.id}`,
|
||||||
|
service,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get activeService(): Service {
|
||||||
|
return this.props.services.find(s => s.active)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get selected(): string {
|
||||||
|
let selected = ''
|
||||||
|
if (this.activeService) {
|
||||||
|
selected = this.activeService.name
|
||||||
|
} else {
|
||||||
|
selected = this.serviceItems[0].text
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter<Props>(ServiceDropdown)
|
|
@ -1,2 +0,0 @@
|
||||||
export const REQUIRED_ROLE_COPY =
|
|
||||||
'The minimum Role a user must have<br />in order to access this source.'
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const REQUIRED_ROLE_COPY =
|
||||||
|
'The minimum Role a user must have<br />in order to access this source.'
|
||||||
|
|
||||||
|
export const KAPACITOR_TOOLTIP_COPY =
|
||||||
|
'<p>Kapacitor Connections are<br/>scoped per InfluxDB Connection.<br/>Only one can be active at a time.</p>'
|
|
@ -1,41 +1,43 @@
|
||||||
import React, {Component} from 'react'
|
import React, {PureComponent} 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 {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
import {
|
import * as actions from 'src/shared/actions/sources'
|
||||||
removeAndLoadSources,
|
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||||
fetchKapacitorsAsync,
|
|
||||||
setActiveKapacitorAsync,
|
|
||||||
deleteKapacitorAsync,
|
|
||||||
} from 'shared/actions/sources'
|
|
||||||
import {notify as notifyAction} from 'shared/actions/notifications'
|
|
||||||
|
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||||
import SourceIndicator from 'shared/components/SourceIndicator'
|
import SourceIndicator from 'src/shared/components/SourceIndicator'
|
||||||
import InfluxTable from 'src/sources/components/InfluxTable'
|
import InfluxTable from 'src/sources/components/InfluxTable'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
notifySourceDeleted,
|
notifySourceDeleted,
|
||||||
notifySourceDeleteFailed,
|
notifySourceDeleteFailed,
|
||||||
} from 'shared/copy/notifications'
|
} from 'src/shared/copy/notifications'
|
||||||
|
|
||||||
const V_NUMBER = VERSION // eslint-disable-line no-undef
|
import {Source, NotificationFunc} from 'src/types'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: Source
|
||||||
|
sources: Source[]
|
||||||
|
notify: NotificationFunc
|
||||||
|
deleteKapacitor: actions.DeleteKapacitorAsync
|
||||||
|
fetchKapacitors: actions.FetchKapacitorsAsync
|
||||||
|
removeAndLoadSources: actions.RemoveAndLoadSources
|
||||||
|
setActiveKapacitor: actions.SetActiveKapacitorAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var VERSION: string
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class ManageSources extends Component {
|
class ManageSources extends PureComponent<Props> {
|
||||||
constructor(props) {
|
public componentDidMount() {
|
||||||
super(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.sources.forEach(source => {
|
this.props.sources.forEach(source => {
|
||||||
this.props.fetchKapacitors(source)
|
this.props.fetchKapacitors(source)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
public componentDidUpdate(prevProps: Props) {
|
||||||
if (prevProps.sources.length !== this.props.sources.length) {
|
if (prevProps.sources.length !== this.props.sources.length) {
|
||||||
this.props.sources.forEach(source => {
|
this.props.sources.forEach(source => {
|
||||||
this.props.fetchKapacitors(source)
|
this.props.fetchKapacitors(source)
|
||||||
|
@ -43,22 +45,7 @@ class ManageSources extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteSource = source => () => {
|
public render() {
|
||||||
const {notify} = this.props
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.props.removeAndLoadSources(source)
|
|
||||||
notify(notifySourceDeleted(source.name))
|
|
||||||
} catch (e) {
|
|
||||||
notify(notifySourceDeleteFailed(source.name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSetActiveKapacitor = ({kapacitor}) => {
|
|
||||||
this.props.setActiveKapacitor(kapacitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {sources, source, deleteKapacitor} = this.props
|
const {sources, source, deleteKapacitor} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -78,34 +65,31 @@ class ManageSources extends Component {
|
||||||
<InfluxTable
|
<InfluxTable
|
||||||
source={source}
|
source={source}
|
||||||
sources={sources}
|
sources={sources}
|
||||||
handleDeleteKapacitor={deleteKapacitor}
|
deleteKapacitor={deleteKapacitor}
|
||||||
handleDeleteSource={this.handleDeleteSource}
|
onDeleteSource={this.handleDeleteSource}
|
||||||
setActiveKapacitor={this.handleSetActiveKapacitor}
|
setActiveKapacitor={this.handleSetActiveKapacitor}
|
||||||
/>
|
/>
|
||||||
<p className="version-number">Chronograf Version: {V_NUMBER}</p>
|
<p className="version-number">Chronograf Version: {VERSION}</p>
|
||||||
</div>
|
</div>
|
||||||
</FancyScrollbar>
|
</FancyScrollbar>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const {array, func, shape, string} = PropTypes
|
private handleDeleteSource = (source: Source) => () => {
|
||||||
|
const {notify} = this.props
|
||||||
|
|
||||||
ManageSources.propTypes = {
|
try {
|
||||||
source: shape({
|
this.props.removeAndLoadSources(source)
|
||||||
id: string.isRequired,
|
notify(notifySourceDeleted(source.name))
|
||||||
links: shape({
|
} catch (e) {
|
||||||
proxy: string.isRequired,
|
notify(notifySourceDeleteFailed(source.name))
|
||||||
self: string.isRequired,
|
}
|
||||||
}),
|
}
|
||||||
}),
|
|
||||||
sources: array,
|
private handleSetActiveKapacitor = ({kapacitor}) => {
|
||||||
notify: func.isRequired,
|
this.props.setActiveKapacitor(kapacitor)
|
||||||
removeAndLoadSources: func.isRequired,
|
}
|
||||||
fetchKapacitors: func.isRequired,
|
|
||||||
setActiveKapacitor: func.isRequired,
|
|
||||||
deleteKapacitor: func.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = ({sources}) => ({
|
const mapStateToProps = ({sources}) => ({
|
||||||
|
@ -113,10 +97,16 @@ const mapStateToProps = ({sources}) => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
removeAndLoadSources: bindActionCreators(removeAndLoadSources, dispatch),
|
removeAndLoadSources: bindActionCreators(
|
||||||
fetchKapacitors: bindActionCreators(fetchKapacitorsAsync, dispatch),
|
actions.removeAndLoadSources,
|
||||||
setActiveKapacitor: bindActionCreators(setActiveKapacitorAsync, dispatch),
|
dispatch
|
||||||
deleteKapacitor: bindActionCreators(deleteKapacitorAsync, dispatch),
|
),
|
||||||
|
fetchKapacitors: bindActionCreators(actions.fetchKapacitorsAsync, dispatch),
|
||||||
|
setActiveKapacitor: bindActionCreators(
|
||||||
|
actions.setActiveKapacitorAsync,
|
||||||
|
dispatch
|
||||||
|
),
|
||||||
|
deleteKapacitor: bindActionCreators(actions.deleteKapacitorAsync, dispatch),
|
||||||
notify: bindActionCreators(notifyAction, dispatch),
|
notify: bindActionCreators(notifyAction, dispatch),
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ import cellEditorOverlay from 'src/dashboards/reducers/cellEditorOverlay'
|
||||||
import overlayTechnology from 'src/shared/reducers/overlayTechnology'
|
import overlayTechnology from 'src/shared/reducers/overlayTechnology'
|
||||||
import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1'
|
import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1'
|
||||||
import persistStateEnhancer from './persistStateEnhancer'
|
import persistStateEnhancer from './persistStateEnhancer'
|
||||||
|
import servicesReducer from 'src/shared/reducers/services'
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
...statusReducers,
|
...statusReducers,
|
||||||
|
@ -28,6 +29,7 @@ const rootReducer = combineReducers({
|
||||||
overlayTechnology,
|
overlayTechnology,
|
||||||
dashTimeV1,
|
dashTimeV1,
|
||||||
routing: routerReducer,
|
routing: routerReducer,
|
||||||
|
services: servicesReducer,
|
||||||
})
|
})
|
||||||
|
|
||||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||||
|
|
|
@ -4,22 +4,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$threesizer-handle: 30px;
|
$threesizer-handle: 30px;
|
||||||
|
|
||||||
.threesizer {
|
.threesizer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
&.dragging .threesizer--division {
|
&.dragging .threesizer--division {
|
||||||
@include no-user-select();
|
@include no-user-select();
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical {
|
&.vertical {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
@ -29,55 +25,45 @@ $threesizer-handle: 30px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
transition: height 0.25s ease-in-out, width 0.25s ease-in-out;
|
transition: height 0.25s ease-in-out, width 0.25s ease-in-out;
|
||||||
|
|
||||||
&.dragging {
|
&.dragging {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical {
|
&.vertical {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draggable Handle With Title */
|
/* Draggable Handle With Title */
|
||||||
|
|
||||||
.threesizer--handle {
|
.threesizer--handle {
|
||||||
@include no-user-select();
|
@include no-user-select();
|
||||||
background-color: $g4-onyx;
|
background-color: $g4-onyx;
|
||||||
transition: background-color 0.25s ease, color 0.25s ease;
|
transition: background-color 0.25s ease, color 0.25s ease;
|
||||||
|
|
||||||
&.vertical {
|
&.vertical {
|
||||||
border-right: solid 2px $g3-castle;
|
border-right: solid 2px $g3-castle;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&.dragging {
|
&.dragging {
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
border-bottom: solid 2px $g3-castle;
|
border-bottom: solid 2px $g3-castle;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&.dragging {
|
&.dragging {
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
color: $g16-pearl;
|
color: $g16-pearl;
|
||||||
background-color: $g5-pepper;
|
background-color: $g5-pepper;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dragging {
|
&.dragging {
|
||||||
color: $c-laser;
|
color: $c-laser;
|
||||||
background-color: $g5-pepper;
|
background-color: $g5-pepper;
|
||||||
|
@ -93,10 +79,8 @@ $threesizer-handle: 30px;
|
||||||
color: $g11-sidewalk;
|
color: $g11-sidewalk;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: transform 0.25s ease;
|
transition: transform 0.25s ease;
|
||||||
|
|
||||||
&.vertical {
|
&.vertical {
|
||||||
transform: translate(28px, 14px);
|
transform: translate(28px, 14px);
|
||||||
|
|
||||||
&.threesizer--collapsed {
|
&.threesizer--collapsed {
|
||||||
transform: translate(0, 3px) rotate(90deg);
|
transform: translate(0, 3px) rotate(90deg);
|
||||||
}
|
}
|
||||||
|
@ -107,22 +91,17 @@ $threesizer-shadow-size: 9px;
|
||||||
$threesizer-z-index: 2;
|
$threesizer-z-index: 2;
|
||||||
$threesizer-shadow-start: fade-out($g0-obsidian, 0.82);
|
$threesizer-shadow-start: fade-out($g0-obsidian, 0.82);
|
||||||
$threesizer-shadow-stop: fade-out($g0-obsidian, 1);
|
$threesizer-shadow-stop: fade-out($g0-obsidian, 1);
|
||||||
|
|
||||||
.threesizer--contents {
|
.threesizer--contents {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical {
|
&.vertical {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
} // Bottom Shadow
|
||||||
|
|
||||||
// Bottom Shadow
|
|
||||||
&.horizontal:after,
|
&.horizontal:after,
|
||||||
&.vertical:after {
|
&.vertical:after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -131,13 +110,11 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1);
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: $threesizer-z-index;
|
z-index: $threesizer-z-index;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.horizontal:after {
|
&.horizontal:after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $threesizer-shadow-size;
|
height: $threesizer-shadow-size;
|
||||||
@include gradient-v($threesizer-shadow-stop, $threesizer-shadow-start);
|
@include gradient-v($threesizer-shadow-stop, $threesizer-shadow-start);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical:after {
|
&.vertical:after {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: $threesizer-shadow-size;
|
width: $threesizer-shadow-size;
|
||||||
|
@ -155,29 +132,177 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
.threesizer--header {
|
.threesizer--header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0 11px;
|
||||||
background-color: $g2-kevlar;
|
background-color: $g2-kevlar;
|
||||||
|
.horizontal>& {
|
||||||
.horizontal > & {
|
|
||||||
width: 50px;
|
width: 50px;
|
||||||
border-right: 2px solid $g4-onyx;
|
border-right: 2px solid $g4-onyx;
|
||||||
}
|
}
|
||||||
|
.vertical>& {
|
||||||
.vertical > & {
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-bottom: 2px solid $g4-onyx;
|
border-bottom: 2px solid $g4-onyx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.threesizer--body {
|
.threesizer--body {
|
||||||
.horizontal > &:only-child {
|
.horizontal>&:only-child {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.vertical>&:only-child {
|
||||||
.vertical > &:only-child {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.threesizer--header+& {
|
||||||
.threesizer--header + & {
|
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Division context menus
|
||||||
|
.threesizer-context--buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analyze--button {
|
||||||
|
margin-right: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-graph-context--button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
margin-right: 2px;
|
||||||
|
transition: color 0.25s ease, background-color 0.25s ease;
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $g20-white;
|
||||||
|
background-color: $g5-pepper;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
>.icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
position: relative;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-graph-context--menu,
|
||||||
|
.dash-graph-context--menu.default {
|
||||||
|
z-index: 3;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
left: 50%;
|
||||||
|
background-color: $g6-smoke;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-radius: 3px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
border: 6px solid transparent;
|
||||||
|
border-bottom-color: $g6-smoke;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
transition: border-color 0.25s ease;
|
||||||
|
}
|
||||||
|
.dash-graph-context--menu-item {
|
||||||
|
@include no-user-select();
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 26px;
|
||||||
|
height: 26px;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: $g20-white;
|
||||||
|
transition: background-color 0.25s ease;
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-left-radius: 3px;
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: $g8-storm;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&.disabled,
|
||||||
|
&.disabled:hover {
|
||||||
|
cursor: default;
|
||||||
|
background-color: transparent;
|
||||||
|
font-style: italic;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-graph-context--menu.primary {
|
||||||
|
background-color: $c-ocean;
|
||||||
|
&:before {
|
||||||
|
border-bottom-color: $c-ocean;
|
||||||
|
}
|
||||||
|
.dash-graph-context--menu-item:hover {
|
||||||
|
background-color: $c-pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-graph-context--menu.warning {
|
||||||
|
background-color: $c-star;
|
||||||
|
&:before {
|
||||||
|
border-bottom-color: $c-star;
|
||||||
|
}
|
||||||
|
.dash-graph-context--menu-item:hover {
|
||||||
|
background-color: $c-comet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-graph-context--menu.success {
|
||||||
|
background-color: $c-rainforest;
|
||||||
|
&:before {
|
||||||
|
border-bottom-color: $c-rainforest;
|
||||||
|
}
|
||||||
|
.dash-graph-context--menu-item:hover {
|
||||||
|
background-color: $c-honeydew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-graph-context--menu.danger {
|
||||||
|
background-color: $c-curacao;
|
||||||
|
&:before {
|
||||||
|
border-bottom-color: $c-curacao;
|
||||||
|
}
|
||||||
|
.dash-graph-context--menu-item:hover {
|
||||||
|
background-color: $c-dreamsicle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header Dropdown Menu
|
||||||
|
.threesizer--menu {
|
||||||
|
.dropdown-menu {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide Header children when collapsed
|
||||||
|
.threesizer--handle.vertical.threesizer--collapsed+.threesizer--contents>.threesizer--header>* {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -19,3 +19,8 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-warning {
|
||||||
|
color: $c-dreamsicle;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
.ifql-overlay {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@import '../components/time-machine/ifql-overlay';
|
||||||
@import '../components/time-machine/ifql-editor';
|
@import '../components/time-machine/ifql-editor';
|
||||||
@import '../components/time-machine/ifql-builder';
|
@import '../components/time-machine/ifql-builder';
|
||||||
@import '../components/time-machine/ifql-explorer';
|
@import '../components/time-machine/ifql-explorer';
|
||||||
|
|
|
@ -10,6 +10,11 @@ export type OnGenerateScript = (script: string) => void
|
||||||
export type OnChangeScript = (script: string) => void
|
export type OnChangeScript = (script: string) => void
|
||||||
export type OnSubmitScript = () => void
|
export type OnSubmitScript = () => void
|
||||||
|
|
||||||
|
export interface Status {
|
||||||
|
type: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Handlers {
|
export interface Handlers {
|
||||||
onAddNode: OnAddNode
|
onAddNode: OnAddNode
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {Service, NewService} from './services'
|
||||||
import {AuthLinks, Organization, Role, User, Me} from './auth'
|
import {AuthLinks, Organization, Role, User, Me} from './auth'
|
||||||
import {Template, Cell, CellQuery, Legend, Axes} from './dashboard'
|
import {Template, Cell, CellQuery, Legend, Axes} from './dashboard'
|
||||||
import {
|
import {
|
||||||
|
@ -53,4 +54,6 @@ export {
|
||||||
Notification,
|
Notification,
|
||||||
NotificationFunc,
|
NotificationFunc,
|
||||||
Axes,
|
Axes,
|
||||||
|
Service,
|
||||||
|
NewService,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,4 @@ export interface Notification {
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationFunc = () => Notification
|
export type NotificationFunc = (message: any) => Notification
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
export interface NewService {
|
||||||
|
url: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
active: boolean
|
||||||
|
insecureSkipVerify: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Service {
|
||||||
|
id?: string
|
||||||
|
url: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
active: boolean
|
||||||
|
insecureSkipVerify: boolean
|
||||||
|
links: {
|
||||||
|
source: string
|
||||||
|
self: string
|
||||||
|
proxy: string
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {Kapacitor} from './'
|
import {Kapacitor, Service} from './'
|
||||||
|
|
||||||
export interface Source {
|
export interface Source {
|
||||||
id: string
|
id: string
|
||||||
|
@ -17,6 +17,7 @@ export interface Source {
|
||||||
defaultRP: string
|
defaultRP: string
|
||||||
links: SourceLinks
|
links: SourceLinks
|
||||||
kapacitors?: Kapacitor[] // this field does not exist on the server type for Source and is added in the client in the reducer for loading kapacitors.
|
kapacitors?: Kapacitor[] // this field does not exist on the server type for Source and is added in the client in the reducer for loading kapacitors.
|
||||||
|
services?: Service[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SourceLinks {
|
export interface SourceLinks {
|
||||||
|
@ -31,4 +32,5 @@ export interface SourceLinks {
|
||||||
databases: string
|
databases: string
|
||||||
annotations: string
|
annotations: string
|
||||||
health: string
|
health: string
|
||||||
|
services: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {ColorString, ColorNumber} from 'src/types/colors'
|
||||||
import {CellType} from 'src/types/dashboard'
|
import {CellType} from 'src/types/dashboard'
|
||||||
|
|
||||||
export const sourceLinks: SourceLinks = {
|
export const sourceLinks: SourceLinks = {
|
||||||
|
services: '/chronograf/v1/sources/4',
|
||||||
self: '/chronograf/v1/sources/4',
|
self: '/chronograf/v1/sources/4',
|
||||||
kapacitors: '/chronograf/v1/sources/4/kapacitors',
|
kapacitors: '/chronograf/v1/sources/4/kapacitors',
|
||||||
proxy: '/chronograf/v1/sources/4/proxy',
|
proxy: '/chronograf/v1/sources/4/proxy',
|
||||||
|
|
|
@ -215,3 +215,133 @@ export const ArrowFunction = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Fork = {
|
||||||
|
type: 'Program',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 1},
|
||||||
|
end: {line: 1, column: 42},
|
||||||
|
source: 'tele = from(db: "telegraf")\ntele |\u003e sum()',
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 1},
|
||||||
|
end: {line: 1, column: 28},
|
||||||
|
source: 'tele = from(db: "telegraf")',
|
||||||
|
},
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclarator',
|
||||||
|
id: {
|
||||||
|
type: 'Identifier',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 1},
|
||||||
|
end: {line: 1, column: 5},
|
||||||
|
source: 'tele',
|
||||||
|
},
|
||||||
|
name: 'tele',
|
||||||
|
},
|
||||||
|
init: {
|
||||||
|
type: 'CallExpression',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 8},
|
||||||
|
end: {line: 1, column: 28},
|
||||||
|
source: 'from(db: "telegraf")',
|
||||||
|
},
|
||||||
|
callee: {
|
||||||
|
type: 'Identifier',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 8},
|
||||||
|
end: {line: 1, column: 12},
|
||||||
|
source: 'from',
|
||||||
|
},
|
||||||
|
name: 'from',
|
||||||
|
},
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: 'ObjectExpression',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 13},
|
||||||
|
end: {line: 1, column: 27},
|
||||||
|
source: 'db: "telegraf"',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
type: 'Property',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 13},
|
||||||
|
end: {line: 1, column: 27},
|
||||||
|
source: 'db: "telegraf"',
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
type: 'Identifier',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 13},
|
||||||
|
end: {line: 1, column: 15},
|
||||||
|
source: 'db',
|
||||||
|
},
|
||||||
|
name: 'db',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'StringLiteral',
|
||||||
|
location: {
|
||||||
|
start: {line: 1, column: 17},
|
||||||
|
end: {line: 1, column: 27},
|
||||||
|
source: '"telegraf"',
|
||||||
|
},
|
||||||
|
value: 'telegraf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ExpressionStatement',
|
||||||
|
location: {
|
||||||
|
start: {line: 2, column: 1},
|
||||||
|
end: {line: 2, column: 14},
|
||||||
|
source: 'tele |\u003e sum()',
|
||||||
|
},
|
||||||
|
expression: {
|
||||||
|
type: 'PipeExpression',
|
||||||
|
location: {
|
||||||
|
start: {line: 2, column: 6},
|
||||||
|
end: {line: 2, column: 14},
|
||||||
|
source: '|\u003e sum()',
|
||||||
|
},
|
||||||
|
argument: {
|
||||||
|
type: 'Identifier',
|
||||||
|
location: {
|
||||||
|
start: {line: 2, column: 1},
|
||||||
|
end: {line: 2, column: 5},
|
||||||
|
source: 'tele',
|
||||||
|
},
|
||||||
|
name: 'tele',
|
||||||
|
},
|
||||||
|
call: {
|
||||||
|
type: 'CallExpression',
|
||||||
|
location: {
|
||||||
|
start: {line: 2, column: 9},
|
||||||
|
end: {line: 2, column: 14},
|
||||||
|
source: 'sum()',
|
||||||
|
},
|
||||||
|
callee: {
|
||||||
|
type: 'Identifier',
|
||||||
|
location: {
|
||||||
|
start: {line: 2, column: 9},
|
||||||
|
end: {line: 2, column: 12},
|
||||||
|
source: 'sum',
|
||||||
|
},
|
||||||
|
name: 'sum',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import Walker from 'src/ifql/ast/walker'
|
import Walker from 'src/ifql/ast/walker'
|
||||||
import From from 'test/ifql/ast/from'
|
import From from 'test/ifql/ast/from'
|
||||||
import Complex from 'test/ifql/ast/complex'
|
import Complex from 'test/ifql/ast/complex'
|
||||||
import {StringLiteral, Expression, ArrowFunction} from 'test/ifql/ast/variable'
|
import {
|
||||||
|
StringLiteral,
|
||||||
|
Expression,
|
||||||
|
ArrowFunction,
|
||||||
|
Fork,
|
||||||
|
} from 'test/ifql/ast/variable'
|
||||||
|
|
||||||
describe('IFQL.AST.Walker', () => {
|
describe('IFQL.AST.Walker', () => {
|
||||||
describe('Walker#functions', () => {
|
describe('Walker#functions', () => {
|
||||||
|
@ -80,7 +85,7 @@ describe('IFQL.AST.Walker', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.only('a single ArrowFunction variable', () => {
|
describe('a single ArrowFunction variable', () => {
|
||||||
it('returns the expected list', () => {
|
it('returns the expected list', () => {
|
||||||
const walker = new Walker(ArrowFunction)
|
const walker = new Walker(ArrowFunction)
|
||||||
expect(walker.body).toEqual([
|
expect(walker.body).toEqual([
|
||||||
|
@ -104,6 +109,45 @@ describe('IFQL.AST.Walker', () => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('forking', () => {
|
||||||
|
it('return the expected list of objects', () => {
|
||||||
|
const walker = new Walker(Fork)
|
||||||
|
expect(walker.body).toEqual([
|
||||||
|
{
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
source: 'tele = from(db: "telegraf")',
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
name: 'tele',
|
||||||
|
type: 'CallExpression',
|
||||||
|
source: 'tele = from(db: "telegraf")',
|
||||||
|
funcs: [
|
||||||
|
{
|
||||||
|
name: 'from',
|
||||||
|
source: 'from(db: "telegraf")',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
key: 'db',
|
||||||
|
value: 'telegraf',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'PipeExpression',
|
||||||
|
source: 'tele |> sum()',
|
||||||
|
funcs: [
|
||||||
|
{args: [], name: 'tele', source: 'tele'},
|
||||||
|
{args: [], name: 'sum', source: '|> sum()'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,9 +6,14 @@ const setup = () => {
|
||||||
const props = {
|
const props = {
|
||||||
script: '',
|
script: '',
|
||||||
body: [],
|
body: [],
|
||||||
|
data: '',
|
||||||
|
status: {type: '', text: ''},
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
onSubmitScript: () => {},
|
onSubmitScript: () => {},
|
||||||
onChangeScript: () => {},
|
onChangeScript: () => {},
|
||||||
|
onAnalyze: () => {},
|
||||||
|
onAppendFrom: () => {},
|
||||||
|
onAppendJoin: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = shallow(<TimeMachine {...props} />)
|
const wrapper = shallow(<TimeMachine {...props} />)
|
||||||
|
|
|
@ -13,6 +13,9 @@ const setup = () => {
|
||||||
suggestions: '',
|
suggestions: '',
|
||||||
ast: '',
|
ast: '',
|
||||||
},
|
},
|
||||||
|
services: [],
|
||||||
|
sources: [],
|
||||||
|
notify: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = shallow(<IFQLPage {...props} />)
|
const wrapper = shallow(<IFQLPage {...props} />)
|
||||||
|
|
|
@ -21,6 +21,7 @@ export const me = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sourceLinks: SourceLinks = {
|
export const sourceLinks: SourceLinks = {
|
||||||
|
services: '/chronograf/v1/sources/16/services',
|
||||||
self: '/chronograf/v1/sources/16',
|
self: '/chronograf/v1/sources/16',
|
||||||
kapacitors: '/chronograf/v1/sources/16/kapacitors',
|
kapacitors: '/chronograf/v1/sources/16/kapacitors',
|
||||||
proxy: '/chronograf/v1/sources/16/proxy',
|
proxy: '/chronograf/v1/sources/16/proxy',
|
||||||
|
@ -93,6 +94,22 @@ export const kapacitor = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const service = {
|
||||||
|
id: '1',
|
||||||
|
url: 'localhost:8082',
|
||||||
|
type: 'ifql',
|
||||||
|
name: 'IFQL',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
active: false,
|
||||||
|
insecureSkipVerify: false,
|
||||||
|
links: {
|
||||||
|
source: '/chronograf/v1/sources/1',
|
||||||
|
proxy: '/chronograf/v1/sources/1/services/2/proxy',
|
||||||
|
self: '/chronograf/v1/sources/1/services/2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const kapacitorRules = [
|
export const kapacitorRules = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import reducer, {initialState} from 'src/shared/reducers/services'
|
||||||
|
|
||||||
|
import {
|
||||||
|
addService,
|
||||||
|
loadServices,
|
||||||
|
deleteService,
|
||||||
|
updateService,
|
||||||
|
} from 'src/shared/actions/services'
|
||||||
|
|
||||||
|
import {Service} from 'src/types'
|
||||||
|
import {service} from 'test/resources'
|
||||||
|
|
||||||
|
const services = (): Service[] => {
|
||||||
|
return reducer(initialState, addService(service))
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Shared.Reducers.services', () => {
|
||||||
|
describe('LOAD_SERVICES', () => {
|
||||||
|
it('correctly loads services', () => {
|
||||||
|
const s1 = {...service, id: '1'}
|
||||||
|
const s2 = {...service, id: '2'}
|
||||||
|
|
||||||
|
const expected = [s1, s2]
|
||||||
|
const actual = reducer(initialState, loadServices(expected))
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ADD_SERVICE', () => {
|
||||||
|
it('can add a service', () => {
|
||||||
|
const expected = service
|
||||||
|
const [actual] = reducer(initialState, addService(service))
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('DELETE_SERVICE', () => {
|
||||||
|
it('can delete a service', () => {
|
||||||
|
const state = services()
|
||||||
|
const actual = reducer(state, deleteService(service))
|
||||||
|
|
||||||
|
expect(actual).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('UPDATE_SERVICE', () => {
|
||||||
|
it('can update a service', () => {
|
||||||
|
const name = 'updated name'
|
||||||
|
const type = 'updated type'
|
||||||
|
const expected = {...service, name, type}
|
||||||
|
|
||||||
|
const state = services()
|
||||||
|
const [actual] = reducer(state, updateService(expected))
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,58 +0,0 @@
|
||||||
import reducer from 'shared/reducers/sources'
|
|
||||||
|
|
||||||
import {updateSource, addSource} from 'shared/actions/sources'
|
|
||||||
|
|
||||||
describe('Shared.Reducers.sources', () => {
|
|
||||||
it('can correctly show default sources when adding a source', () => {
|
|
||||||
let state = []
|
|
||||||
|
|
||||||
state = reducer(
|
|
||||||
state,
|
|
||||||
addSource({
|
|
||||||
id: '1',
|
|
||||||
default: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
state = reducer(
|
|
||||||
state,
|
|
||||||
addSource({
|
|
||||||
id: '2',
|
|
||||||
default: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(state.filter(s => s.default).length).toBe(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can correctly show default sources when updating a source', () => {
|
|
||||||
let state = []
|
|
||||||
|
|
||||||
state = reducer(
|
|
||||||
state,
|
|
||||||
addSource({
|
|
||||||
id: '1',
|
|
||||||
default: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
state = reducer(
|
|
||||||
state,
|
|
||||||
addSource({
|
|
||||||
id: '2',
|
|
||||||
default: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
state = reducer(
|
|
||||||
state,
|
|
||||||
updateSource({
|
|
||||||
id: '1',
|
|
||||||
default: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(state.find(({id}) => id === '1').default).toBe(true)
|
|
||||||
expect(state.find(({id}) => id === '2').default).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import reducer, {initialState} from 'src/shared/reducers/sources'
|
||||||
|
|
||||||
|
import {updateSource, addSource, loadSources} from 'src/shared/actions/sources'
|
||||||
|
|
||||||
|
import {source} from 'test/resources'
|
||||||
|
|
||||||
|
describe('Shared.Reducers.sources', () => {
|
||||||
|
it('can LOAD_SOURCES', () => {
|
||||||
|
const expected = [{...source, id: '1'}]
|
||||||
|
const actual = reducer(initialState, loadSources(expected))
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ADD_SOURCES', () => {
|
||||||
|
it('can ADD_SOURCES', () => {
|
||||||
|
let state = []
|
||||||
|
|
||||||
|
state = reducer(
|
||||||
|
state,
|
||||||
|
addSource({
|
||||||
|
...source,
|
||||||
|
id: '1',
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
state = reducer(
|
||||||
|
state,
|
||||||
|
addSource({
|
||||||
|
...source,
|
||||||
|
id: '2',
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(state.filter(s => s.default).length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can correctly show default sources when updating a source', () => {
|
||||||
|
let state = []
|
||||||
|
|
||||||
|
state = reducer(
|
||||||
|
initialState,
|
||||||
|
addSource({
|
||||||
|
...source,
|
||||||
|
id: '1',
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
state = reducer(
|
||||||
|
state,
|
||||||
|
addSource({
|
||||||
|
...source,
|
||||||
|
id: '2',
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
state = reducer(
|
||||||
|
state,
|
||||||
|
updateSource({
|
||||||
|
...source,
|
||||||
|
id: '1',
|
||||||
|
default: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(state.find(({id}) => id === '1').default).toBe(true)
|
||||||
|
expect(state.find(({id}) => id === '2').default).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue