Merge branch 'master' into bugfix/task_enabled_correct_check

pull/10616/head
Jared Scheib 2018-03-22 13:53:07 -07:00
commit e4088ce61a
275 changed files with 5106 additions and 4376 deletions

1
.eslintrc Symbolic link
View File

@ -0,0 +1 @@
ui/.eslintrc

View File

@ -284,6 +284,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
SortBy: sortBy,
Wrapping: c.TableOptions.Wrapping,
ColumnNames: columnNames,
FixFirstColumn: c.TableOptions.FixFirstColumn,
}
cells[i] = &DashboardCell{
@ -447,6 +448,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
tableOptions.TimeFormat = c.TableOptions.TimeFormat
tableOptions.VerticalTimeAxis = c.TableOptions.VerticalTimeAxis
tableOptions.Wrapping = c.TableOptions.Wrapping
tableOptions.FixFirstColumn = c.TableOptions.FixFirstColumn
}

View File

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

View File

@ -46,6 +46,7 @@ message TableOptions {
TableColumn sortBy = 3; // which column should a table be sorted by
string wrapping = 4; // option for text wrapping
repeated TableColumn columnNames = 5; // names and renames for columns
bool fixFirstColumn = 6; // first column should be fixed/frozen
}
message TableColumn {

View File

@ -573,6 +573,7 @@ type TableOptions struct {
SortBy TableColumn `json:"sortBy"`
Wrapping string `json:"wrapping"`
ColumnNames []TableColumn `json:"columnNames"`
FixFirstColumn bool `json:"fixFirstColumn"`
}
// DashboardsStore is the storage and retrieval of dashboards

View File

@ -550,7 +550,8 @@ func TestServer(t *testing.T) {
"internalName": "",
"displayName": ""},
"wrapping": "",
"columnNames": null
"columnNames": null,
"fixFirstColumn": false
},
"links": {
"self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093"
@ -799,7 +800,8 @@ func TestServer(t *testing.T) {
"displayName":""
},
"wrapping":"",
"columnNames":null
"columnNames":null,
"fixFirstColumn":false
},
"legend":{
"type": "static",

View File

@ -532,7 +532,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
}
}
`))),
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"tableOptions":{"timeFormat":"","verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":""},"wrapping":"","columnNames":null},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"tableOptions":{"timeFormat":"","verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":""},"wrapping":"","columnNames":null,"fixFirstColumn":false},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
`,
},
{

View File

@ -1,260 +1,292 @@
{
"parser": "babel-eslint",
plugins: [
'react',
'prettier',
'babel',
'jest',
],
extends: [
"prettier",
"prettier/react"
],
env: {
browser: true,
mocha: true,
"jest": true,
node: true,
es6: true,
},
globals: {
expect: true,
},
parserOptions: {
ecmaFeatures: {
arrowFunctions: true,
binaryLiterals: true,
blockBindings: true,
classes: true,
defaultParams: false,
destructuring: true,
forOf: false,
generators: false,
modules: true,
objectLiteralComputedProperties: true,
objectLiteralDuplicateProperties: false,
objectLiteralShorthandMethods: true,
objectLiteralShorthandProperties: true,
octalLiterals: false,
regexUFlag: false,
regexYFlag: false,
restParams: true,
spread: true,
superInFunctions: false,
templateStrings: true,
unicodeCodePointEscapes: false,
globalReturn: false,
jsx: true,
},
},
rules: {
'quotes': [1, 'single'],
'func-style': 0,
'func-names': 0,
'arrow-parens': 0,
'comma-dangle': [2, 'always-multiline'],
'no-cond-assign': 2,
'no-console': ['error', {allow: ['error', 'warn']}],
'no-constant-condition': 2,
'no-control-regex': 2,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty': 2,
'no-ex-assign': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': 0,
'no-extra-semi': 2,
'no-func-assign': 2,
'no-inner-declarations': [2, 'both'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-negated-in-lhs': 2,
'no-obj-calls': 2,
'no-regex-spaces': 2,
'no-sparse-arrays': 2,
'no-unexpected-multiline': 2,
'no-unreachable': 2,
'use-isnan': 2,
'valid-jsdoc': 0,
'valid-typeof': 2,
'accessor-pairs': 2,
'block-scoped-var': 2,
'complexity': 0, // TODO: revisit
'consistent-return': 0,
'curly': 2,
'default-case': 0, // TODO: revisit
'dot-location': [2, 'property'],
'dot-notation': 2,
'eqeqeq': 2,
'no-alert': 2,
'no-caller': 2,
'no-case-declarations': 2,
'no-div-regex': 2,
'no-else-return': 2,
'no-labels': 2,
'no-empty-pattern': 2,
'no-eq-null': 2,
'no-eval': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-implicit-coercion': 0,
'no-implied-eval': 2,
'no-iterator': 2,
'no-lone-blocks': 2,
'no-loop-func': 2,
'no-magic-numbers': [0, {ignore: [-1, 0, 1, 2]}],
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-native-reassign': 2,
'no-new-func': 2,
'no-new-wrappers': 2,
'no-new': 2,
'no-octal-escape': 2,
'no-octal': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-script-url': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-throw-literal': 2,
'no-unused-expressions': 2,
'no-useless-call': 2,
'no-useless-concat': 2,
'no-void': 2,
'no-warning-comments': 0,
'no-with': 2,
'radix': 2,
'vars-on-top': 2,
'strict': [2, 'never'],
'init-declarations': 0,
'no-catch-shadow': 2,
'no-delete-var': 2,
'no-label-var': 2,
'no-shadow-restricted-names': 2,
'no-shadow': 2,
'no-undef-init': 2,
'no-undef': 2,
'no-unused-vars': [2, {args: 'after-used', 'argsIgnorePattern': '^_'}],
'no-use-before-define': [2, 'nofunc'],
'array-bracket-spacing': [2, 'never'],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs'],
'camelcase': [2, {properties: 'never'}],
'comma-spacing': [2, {before: false, after: true}],
'comma-style': [2, 'last'],
'computed-property-spacing': [2, 'never'],
'consistent-this': [2, 'self'],
'eol-last': 0, // TODO: revisit
'id-length': 0,
'id-match': 0,
'indent': [0, 2, {SwitchCase: 1}],
'key-spacing': [2, {beforeColon: false, afterColon: true}],
'linebreak-style': [2, 'unix'],
'lines-around-comment': 0,
'max-depth': 0,
'max-len': 0,
'max-nested-callbacks': 0,
'max-params': 0,
'max-statements': 0,
'new-cap': 0,
'new-parens': 2,
'newline-after-var': 0,
'no-array-constructor': 2,
'no-negated-condition': 2,
'no-inline-comments': 0,
'no-lonely-if': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multiple-empty-lines': 2,
'no-nested-ternary': 2,
'no-new-object': 2,
'no-plusplus': [2, {allowForLoopAfterthoughts: true}],
'no-spaced-func': 2,
'no-ternary': 0,
'no-trailing-spaces': 2,
'no-underscore-dangle': 0,
'no-unneeded-ternary': 2,
'object-curly-spacing': [2, 'never'],
'one-var': 0,
'operator-assignment': [2, 'always'],
'padded-blocks': [2, 'never'],
'quote-props': [2, 'as-needed', {keywords: false, numbers: false }],
'require-jsdoc': 0,
'semi-spacing': [2, {before: false, after: true}],
'semi': [2, 'never'],
'sort-vars': 0,
'keyword-spacing': 'error',
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': 2,
'spaced-comment': [2, 'always'],
'wrap-regex': 0,
'arrow-body-style': 0,
'arrow-spacing': [2, {before: true, after: true}],
'no-confusing-arrow': 0,
'no-class-assign': 2,
'no-const-assign': 2,
'no-dupe-class-members': 2,
'no-this-before-super': 2,
'no-var': 2,
'object-shorthand': [2, 'always'],
'prefer-arrow-callback': 0,
'prefer-const': 2,
'prefer-template': 2,
// React
'jsx-quotes': [1, "prefer-double"],
'react/display-name': 0,
'react/jsx-no-bind': [2, {ignoreRefs: true}],
'react/jsx-boolean-value': [2, 'always'],
'react/jsx-curly-spacing': [2, 'never'],
'react/jsx-equals-spacing': [2, 'never'],
'react/jsx-key': 2,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-undef': 2,
'react/jsx-sort-props': 0,
'react/jsx-sort-prop-types': 0,
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/no-danger': 2,
'react/no-did-mount-set-state': 0,
'react/no-did-update-set-state': 2,
'react/no-direct-mutation-state': 2,
'react/no-is-mounted': 2,
'react/no-multi-comp': 0,
'react/no-set-state': 0,
'react/no-string-refs': 0, // TODO: 2
'react/no-unknown-property': 2,
'react/prop-types': 2,
'react/prefer-es6-class': [0, 'never'],
'react/react-in-jsx-scope': 2,
'react/require-extension': 0,
'react/self-closing-comp': 0, // TODO: we can re-enable this if some brave soul wants to update the code (mostly spans acting as icons)
'react/sort-comp': 0, // TODO: 2
// Prettier
'prettier/prettier': ['error', {
'singleQuote': true,
'trailingComma': 'es5',
'bracketSpacing': false,
'semi': false,
}],
// jest
'jest/no-disabled-tests': "warn",
'jest/no-focused-tests': "error",
// Babel
'babel/no-invalid-this': 1
},
"parser": "babel-eslint",
"plugins": [
"react",
"prettier",
"babel",
"jest"
],
"extends": [
"prettier",
"prettier/react"
],
"env": {
"browser": true,
"mocha": true,
"jest": true,
"node": true,
"es6": true
},
"globals": {
"expect": true
},
"parserOptions": {
"ecmaFeatures": {
"arrowFunctions": true,
"binaryLiterals": true,
"blockBindings": true,
"classes": true,
"defaultParams": false,
"destructuring": true,
"forOf": false,
"generators": false,
"modules": true,
"objectLiteralComputedProperties": true,
"objectLiteralDuplicateProperties": false,
"objectLiteralShorthandMethods": true,
"objectLiteralShorthandProperties": true,
"octalLiterals": false,
"regexUFlag": false,
"regexYFlag": false,
"restParams": true,
"spread": true,
"superInFunctions": false,
"templateStrings": true,
"unicodeCodePointEscapes": false,
"globalReturn": false,
"jsx": true
}
},
"rules": {
"func-style": 0,
"func-names": 0,
"arrow-parens": 0,
"no-cond-assign": 2,
"no-console": [
"error",
{
"allow": [
"error",
"warn"
]
}
],
"no-constant-condition": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-empty-character-class": 2,
"no-empty": 2,
"no-ex-assign": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-func-assign": 2,
"no-inner-declarations": [
2,
"both"
],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-negated-in-lhs": 2,
"no-obj-calls": 2,
"no-regex-spaces": 2,
"no-sparse-arrays": 2,
"no-unreachable": 2,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"accessor-pairs": 2,
"block-scoped-var": 2,
"complexity": 0,
"consistent-return": 0,
"curly": 2,
"default-case": 0,
"dot-notation": 2,
"eqeqeq": 2,
"no-alert": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-div-regex": 2,
"no-else-return": 2,
"no-labels": 2,
"no-empty-pattern": 2,
"no-eq-null": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-fallthrough": 2,
"no-implicit-coercion": 0,
"no-implied-eval": 2,
"no-iterator": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-magic-numbers": [
0,
{
"ignore": [
-1,
0,
1,
2
]
}
],
"no-multi-str": 2,
"no-native-reassign": 2,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-new": 2,
"no-octal-escape": 2,
"no-octal": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-script-url": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-throw-literal": 2,
"no-unused-expressions": 2,
"no-useless-call": 2,
"no-useless-concat": 2,
"no-void": 2,
"no-warning-comments": 0,
"no-with": 2,
"radix": 2,
"vars-on-top": 2,
"strict": [
2,
"never"
],
"init-declarations": 0,
"no-catch-shadow": 2,
"no-delete-var": 2,
"no-label-var": 2,
"no-shadow-restricted-names": 2,
"no-shadow": 2,
"no-undef-init": 2,
"no-undef": 2,
"no-unused-vars": [
2,
{
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"no-use-before-define": [
2,
"nofunc"
],
"camelcase": [
2,
{
"properties": "never"
}
],
"consistent-this": [
2,
"self"
],
"eol-last": 0,
"id-length": 0,
"id-match": 0,
"indent": [
0,
2,
{
"SwitchCase": 1
}
],
"linebreak-style": [
2,
"unix"
],
"lines-around-comment": 0,
"max-depth": 0,
"max-len": 0,
"max-nested-callbacks": 0,
"max-params": 0,
"max-statements": 0,
"new-cap": 0,
"newline-after-var": 0,
"no-array-constructor": 2,
"no-negated-condition": 2,
"no-inline-comments": 0,
"no-lonely-if": 2,
"no-nested-ternary": 2,
"no-new-object": 2,
"no-plusplus": [
2,
{
"allowForLoopAfterthoughts": true
}
],
"no-ternary": 0,
"no-underscore-dangle": 0,
"no-unneeded-ternary": 2,
"one-var": 0,
"operator-assignment": [
2,
"always"
],
"require-jsdoc": 0,
"sort-vars": 0,
"spaced-comment": [
2,
"always"
],
"wrap-regex": 0,
"arrow-body-style": 0,
"no-confusing-arrow": 0,
"no-class-assign": 2,
"no-const-assign": 2,
"no-dupe-class-members": 2,
"no-this-before-super": 2,
"no-var": 2,
"object-shorthand": [
2,
"always"
],
"prefer-arrow-callback": 0,
"prefer-const": 2,
"prefer-template": 2,
"react/display-name": 0,
"react/jsx-no-bind": [
2,
{
"ignoreRefs": true
}
],
"react/jsx-boolean-value": [
2,
"always"
],
"react/jsx-key": 2,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,
"react/jsx-sort-props": 0,
"react/jsx-sort-prop-types": 0,
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/no-danger": 2,
"react/no-did-mount-set-state": 0,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-is-mounted": 2,
"react/no-multi-comp": 0,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-unknown-property": 2,
"react/prop-types": 2,
"react/prefer-es6-class": [
0,
"never"
],
"react/react-in-jsx-scope": 2,
"react/require-extension": 0,
"react/self-closing-comp": 0,
"react/sort-comp": 0,
"prettier/prettier": [
"error",
{
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": false,
"semi": false
}
],
"jest/no-disabled-tests": "warn",
"jest/no-focused-tests": "error",
"babel/no-invalid-this": 1
}
}

View File

@ -18,8 +18,17 @@ module.exports = {
},
{
runner: 'jest-runner-eslint',
displayName: 'lint',
displayName: 'eslint',
testMatch: ['<rootDir>/test/**/*.test.js'],
},
{
runner: 'jest-runner-tslint',
displayName: 'tslint',
moduleFileExtensions: ['ts', 'tsx'],
testMatch: [
'<rootDir>/test/**/*.test.ts',
'<rootDir>/test/**/*.test.tsx',
],
},
],
}

View File

@ -64,7 +64,7 @@
"eslint-config-prettier": "^2.9.0",
"eslint-loader": "^2.0.0",
"eslint-plugin-jest": "^21.12.2",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-react": "6.6.0",
"eslint-watch": "^3.1.2",
"express": "^4.14.0",
@ -77,6 +77,7 @@
"imports-loader": "^0.6.5",
"jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0",
"jest-runner-tslint": "^1.0.3",
"jsdom": "^9.0.0",
"json-loader": "^0.5.7",
"node-sass": "^4.5.3",
@ -86,7 +87,7 @@
"postcss-loader": "^0.8.0",
"postcss-reporter": "^1.3.1",
"precss": "^1.4.0",
"prettier": "1.5.3",
"prettier": "^1.11.1",
"react-addons-test-utils": "^15.0.2",
"react-test-renderer": "^15.6.1",
"resolve-url-loader": "^2.2.1",
@ -96,6 +97,11 @@
"ts-jest": "^22.4.1",
"ts-loader": "^3.5.0",
"tslib": "^1.9.0",
"tslint": "^5.9.1",
"tslint-config-prettier": "^1.10.0",
"tslint-loader": "^3.6.0",
"tslint-plugin-prettier": "^1.3.0",
"tslint-react": "^3.5.1",
"typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^1.2.2",
"webpack": "^3.11.0",

View File

@ -4,12 +4,13 @@ import PropTypes from 'prop-types'
import SideNav from 'src/side_nav'
import Notifications from 'shared/components/Notifications'
const App = ({children}) =>
const App = ({children}) => (
<div className="chronograf-root">
<Notifications />
<SideNav />
{children}
</div>
)
const {node} = PropTypes

View File

@ -168,9 +168,9 @@ export const createMappingAsync = (url, mapping) => async dispatch => {
const {data} = await createMappingAJAX(url, mapping)
dispatch(updateMapping(mappingWithTempId, data))
} catch (error) {
const message = `${_.upperFirst(
_.toLower(error.data.message)
)}: Scheme: ${mapping.scheme} Provider: ${mapping.provider}`
const message = `${_.upperFirst(_.toLower(error.data.message))}: Scheme: ${
mapping.scheme
} Provider: ${mapping.provider}`
dispatch(errorThrown(error, message))
setTimeout(
() => dispatch(removeMapping(mappingWithTempId)),
@ -212,9 +212,9 @@ export const createUserAsync = (url, user) => async dispatch => {
const {data} = await createUserAJAX(url, user)
dispatch(syncUser(userWithTempID, data))
} catch (error) {
const message = `${_.upperFirst(
_.toLower(error.data.message)
)}: ${user.scheme}::${user.provider}::${user.name}`
const message = `${_.upperFirst(_.toLower(error.data.message))}: ${
user.scheme
}::${user.provider}::${user.name}`
dispatch(errorThrown(error, message))
// undo optimistic update
setTimeout(() => dispatch(removeUser(userWithTempID)), REVERT_STATE_DELAY)
@ -277,9 +277,9 @@ export const createOrganizationAsync = (
const {data} = await createOrganizationAJAX(url, organization)
dispatch(syncOrganization(organization, data))
} catch (error) {
const message = `${_.upperFirst(
_.toLower(error.data.message)
)}: ${organization.name}`
const message = `${_.upperFirst(_.toLower(error.data.message))}: ${
organization.name
}`
dispatch(errorThrown(error, message))
// undo optimistic update
setTimeout(

View File

@ -89,18 +89,12 @@ const AdminTabs = ({
return (
<Tabs className="row">
<TabList customClass="col-md-2 admin-tabs">
{tabs.map((t, i) =>
<Tab key={tabs[i].type}>
{tabs[i].type}
</Tab>
)}
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
</TabList>
<TabPanels customClass="col-md-10 admin-tabs--content">
{tabs.map((t, i) =>
<TabPanel key={tabs[i].type}>
{t.component}
</TabPanel>
)}
{tabs.map((t, i) => (
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
))}
</TabPanels>
</Tabs>
)

View File

@ -41,7 +41,7 @@ const DatabaseManager = ({
</button>
</div>
<div className="panel-body">
{databases.map(db =>
{databases.map(db => (
<DatabaseTable
key={db.links.self}
database={db}
@ -62,7 +62,7 @@ const DatabaseManager = ({
onRemoveRetentionPolicy={onRemoveRetentionPolicy}
onDeleteRetentionPolicy={onDeleteRetentionPolicy}
/>
)}
))}
</div>
</div>
)

View File

@ -148,19 +148,21 @@ class DatabaseRow extends Component {
return (
<tr>
<td style={{width: `${DATABASE_TABLE.colRetentionPolicy}px`}}>
{isNew
? <input
className="form-control input-xs"
type="text"
defaultValue={name}
placeholder="Name this RP"
onKeyDown={this.handleKeyDown}
ref={r => (this.name = r)}
autoFocus={true}
spellCheck={false}
autoComplete={false}
/>
: name}
{isNew ? (
<input
className="form-control input-xs"
type="text"
defaultValue={name}
placeholder="Name this RP"
onKeyDown={this.handleKeyDown}
ref={r => (this.name = r)}
autoFocus={true}
spellCheck={false}
autoComplete={false}
/>
) : (
name
)}
</td>
<td style={{width: `${DATABASE_TABLE.colDuration}px`}}>
<input
@ -176,22 +178,22 @@ class DatabaseRow extends Component {
autoComplete={false}
/>
</td>
{isRFDisplayed
? <td style={{width: `${DATABASE_TABLE.colReplication}px`}}>
<input
className="form-control input-xs"
name="name"
type="number"
min="1"
defaultValue={replication || 1}
placeholder="# of Nodes"
onKeyDown={this.handleKeyDown}
ref={r => (this.replication = r)}
spellCheck={false}
autoComplete={false}
/>
</td>
: null}
{isRFDisplayed ? (
<td style={{width: `${DATABASE_TABLE.colReplication}px`}}>
<input
className="form-control input-xs"
name="name"
type="number"
min="1"
defaultValue={replication || 1}
placeholder="# of Nodes"
onKeyDown={this.handleKeyDown}
ref={r => (this.replication = r)}
spellCheck={false}
autoComplete={false}
/>
</td>
) : null}
<td
className="text-right"
style={{width: `${DATABASE_TABLE.colDelete}px`}}
@ -210,9 +212,9 @@ class DatabaseRow extends Component {
<tr>
<td>
{`${name} `}
{isDefault
? <span className="default-source-label">default</span>
: null}
{isDefault ? (
<span className="default-source-label">default</span>
) : null}
</td>
<td
onClick={this.handleStartEdit}
@ -220,31 +222,33 @@ class DatabaseRow extends Component {
>
{formattedDuration}
</td>
{isRFDisplayed
? <td
onClick={this.handleStartEdit}
style={{width: `${DATABASE_TABLE.colReplication}px`}}
>
{replication}
</td>
: null}
{isRFDisplayed ? (
<td
onClick={this.handleStartEdit}
style={{width: `${DATABASE_TABLE.colReplication}px`}}
>
{replication}
</td>
) : null}
<td
className="text-right"
style={{width: `${DATABASE_TABLE.colDelete}px`}}
>
{isDeleting
? <YesNoButtons
onConfirm={onDelete(database, retentionPolicy)}
onCancel={this.handleEndDelete}
buttonSize="btn-xs"
/>
: <button
className="btn btn-danger btn-xs table--show-on-row-hover"
style={isDeletable ? {} : {visibility: 'hidden'}}
onClick={this.handleStartDelete}
>
{`Delete ${name}`}
</button>}
{isDeleting ? (
<YesNoButtons
onConfirm={onDelete(database, retentionPolicy)}
onCancel={this.handleEndDelete}
buttonSize="btn-xs"
/>
) : (
<button
className="btn btn-danger btn-xs table--show-on-row-hover"
style={isDeletable ? {} : {visibility: 'hidden'}}
onClick={this.handleStartDelete}
>
{`Delete ${name}`}
</button>
)}
</td>
</tr>
)

View File

@ -57,11 +57,11 @@ const DatabaseTable = ({
<th style={{width: `${DATABASE_TABLE.colDuration}px`}}>
Duration
</th>
{isRFDisplayed
? <th style={{width: `${DATABASE_TABLE.colReplication}px`}}>
Replication Factor
</th>
: null}
{isRFDisplayed ? (
<th style={{width: `${DATABASE_TABLE.colReplication}px`}}>
Replication Factor
</th>
) : null}
<th style={{width: `${DATABASE_TABLE.colDelete}px`}} />
</tr>
</thead>

View File

@ -68,18 +68,18 @@ const Header = ({
>
<span className="icon plus" /> Add Retention Policy
</button>
{database.name === '_internal'
? null
: <button
className="btn btn-xs btn-danger"
onClick={onStartDelete(database)}
>
Delete
</button>}
{database.name === '_internal' ? null : (
<button
className="btn btn-xs btn-danger"
onClick={onStartDelete(database)}
>
Delete
</button>
)}
</div>
)
const onConfirm = db => {
function onConfirm(db) {
if (database.deleteCode !== `DELETE ${database.name}`) {
return notify(NOTIFY_DATABASE_DELETE_CONFIRMATION_REQUIRED(database.name))
}
@ -112,15 +112,13 @@ const Header = ({
return (
<div className="db-manager-header">
<h4>
{database.name}
</h4>
<h4>{database.name}</h4>
{database.hasOwnProperty('deleteCode') ? deleteConfirmation : buttons}
</div>
)
}
const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) =>
const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) => (
<div className="db-manager-header db-manager-header--edit">
<input
className="form-control input-sm"
@ -136,6 +134,7 @@ const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) =>
/>
<ConfirmButtons item={database} onConfirm={onConfirm} onCancel={onCancel} />
</div>
)
const {func, shape, bool} = PropTypes

View File

@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
const EmptyRow = ({tableName}) =>
const EmptyRow = ({tableName}) => (
<tr className="table-empty-state">
<th colSpan="5">
<p>
@ -9,6 +9,7 @@ const EmptyRow = ({tableName}) =>
</p>
</th>
</tr>
)
const {string} = PropTypes

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import QueryRow from 'src/admin/components/QueryRow'
import {QUERIES_TABLE} from 'src/admin/constants/tableSizing'
const QueriesTable = ({queries, onKillQuery}) =>
const QueriesTable = ({queries, onKillQuery}) => (
<div>
<div className="panel panel-solid">
<div className="panel-body">
@ -20,14 +20,15 @@ const QueriesTable = ({queries, onKillQuery}) =>
</tr>
</thead>
<tbody>
{queries.map(q =>
{queries.map(q => (
<QueryRow key={q.id} query={q} onKill={onKillQuery} />
)}
))}
</tbody>
</table>
</div>
</div>
</div>
)
const {arrayOf, func, shape} = PropTypes

View File

@ -37,9 +37,7 @@ class QueryRow extends Component {
{database}
</td>
<td>
<code>
{query}
</code>
<code>{query}</code>
</td>
<td
style={{width: `${QUERIES_TABLE.colRunning}px`}}
@ -51,18 +49,20 @@ class QueryRow extends Component {
style={{width: `${QUERIES_TABLE.colKillQuery}px`}}
className="text-right"
>
{this.state.confirmingKill
? <ConfirmButtons
onConfirm={this.handleFinishHim}
onCancel={this.handleShowMercy}
buttonSize="btn-xs"
/>
: <button
className="btn btn-xs btn-danger table--show-on-row-hover"
onClick={this.handleInitiateKill}
>
Kill
</button>}
{this.state.confirmingKill ? (
<ConfirmButtons
onConfirm={this.handleFinishHim}
onCancel={this.handleShowMercy}
buttonSize="btn-xs"
/>
) : (
<button
className="btn btn-xs btn-danger table--show-on-row-hover"
onClick={this.handleInitiateKill}
>
Kill
</button>
)}
</td>
</tr>
)

View File

@ -24,11 +24,11 @@ const RoleRow = ({
onUpdateRoleUsers,
onUpdateRolePermissions,
}) => {
const handleUpdateUsers = usrs => {
function handleUpdateUsers(usrs) {
onUpdateRoleUsers(role, usrs)
}
const handleUpdatePermissions = allowed => {
function handleUpdatePermissions(allowed) {
onUpdateRolePermissions(role, [
{scope: 'all', allowed: allowed.map(({name}) => name)},
])
@ -64,41 +64,36 @@ const RoleRow = ({
return (
<tr>
<td style={{width: `${ROLES_TABLE.colName}px`}}>
{roleName}
<td style={{width: `${ROLES_TABLE.colName}px`}}>{roleName}</td>
<td>
{allPermissions && allPermissions.length ? (
<MultiSelectDropdown
items={allPermissions.map(name => ({name}))}
selectedItems={perms.map(name => ({name}))}
label={perms.length ? '' : 'Select Permissions'}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${ROLES_TABLE.colPermissions}`, {
'admin-table--multi-select-empty': !permissions.length,
})}
/>
) : null}
</td>
<td>
{allPermissions && allPermissions.length
? <MultiSelectDropdown
items={allPermissions.map(name => ({name}))}
selectedItems={perms.map(name => ({name}))}
label={perms.length ? '' : 'Select Permissions'}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(
`dropdown-${ROLES_TABLE.colPermissions}`,
{
'admin-table--multi-select-empty': !permissions.length,
}
)}
/>
: null}
</td>
<td>
{allUsers && allUsers.length
? <MultiSelectDropdown
items={allUsers}
selectedItems={users}
label={users.length ? '' : 'Select Users'}
onApply={handleUpdateUsers}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${ROLES_TABLE.colUsers}`, {
'admin-table--multi-select-empty': !users.length,
})}
/>
: null}
{allUsers && allUsers.length ? (
<MultiSelectDropdown
items={allUsers}
selectedItems={users}
label={users.length ? '' : 'Select Users'}
onApply={handleUpdateUsers}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${ROLES_TABLE.colUsers}`, {
'admin-table--multi-select-empty': !users.length,
})}
/>
) : null}
</td>
<DeleteConfirmTableCell
onDelete={onDelete}

View File

@ -17,7 +17,7 @@ const RolesTable = ({
onFilter,
onUpdateRoleUsers,
onUpdateRolePermissions,
}) =>
}) => (
<div className="panel panel-solid">
<FilterBar
type="roles"
@ -36,30 +36,33 @@ const RolesTable = ({
</tr>
</thead>
<tbody>
{roles.length
? roles
.filter(r => !r.hidden)
.map(role =>
<RoleRow
key={role.links.self}
allUsers={allUsers}
allPermissions={permissions}
role={role}
onEdit={onEdit}
onSave={onSave}
onCancel={onCancel}
onDelete={onDelete}
onUpdateRoleUsers={onUpdateRoleUsers}
onUpdateRolePermissions={onUpdateRolePermissions}
isEditing={role.isEditing}
isNew={role.isNew}
/>
)
: <EmptyRow tableName={'Roles'} />}
{roles.length ? (
roles
.filter(r => !r.hidden)
.map(role => (
<RoleRow
key={role.links.self}
allUsers={allUsers}
allPermissions={permissions}
role={role}
onEdit={onEdit}
onSave={onSave}
onCancel={onCancel}
onDelete={onDelete}
onUpdateRoleUsers={onUpdateRoleUsers}
onUpdateRolePermissions={onUpdateRolePermissions}
isEditing={role.isEditing}
isNew={role.isNew}
/>
))
) : (
<EmptyRow tableName={'Roles'} />
)}
</tbody>
</table>
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes

View File

@ -22,19 +22,21 @@ class UserNewPassword extends Component {
const {user, isNew} = this.props
return (
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
{isNew
? <input
className="form-control input-xs"
name="password"
type="password"
value={user.password || ''}
placeholder="Password"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
spellCheck={false}
autoComplete={false}
/>
: '--'}
{isNew ? (
<input
className="form-control input-xs"
name="password"
type="password"
value={user.password || ''}
placeholder="Password"
onChange={this.handleEdit(user)}
onKeyPress={this.handleKeyPress(user)}
spellCheck={false}
autoComplete={false}
/>
) : (
'--'
)}
</td>
)
}

View File

@ -28,19 +28,19 @@ const UserRow = ({
onUpdateRoles,
onUpdatePassword,
}) => {
const handleUpdatePermissions = perms => {
function handleUpdatePermissions(perms) {
const allowed = perms.map(p => p.name)
onUpdatePermissions(user, [{scope: 'all', allowed}])
}
const handleUpdateRoles = roleNames => {
function handleUpdateRoles(roleNames) {
onUpdateRoles(
user,
allRoles.filter(r => roleNames.find(rn => rn.name === r.name))
)
}
const handleUpdatePassword = () => {
function handleUpdatePassword() {
onUpdatePassword(user, password)
}
@ -75,9 +75,7 @@ const UserRow = ({
return (
<tr>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>
{name}
</td>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>{name}</td>
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
<ChangePassRow
onEdit={onEdit}
@ -86,40 +84,37 @@ const UserRow = ({
buttonSize="btn-xs"
/>
</td>
{hasRoles
? <td>
<MultiSelectDropdown
items={allRoles}
selectedItems={roles.map(r => ({name: r.name}))}
label={roles.length ? '' : 'Select Roles'}
onApply={handleUpdateRoles}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colRoles}`, {
'admin-table--multi-select-empty': !roles.length,
})}
/>
</td>
: null}
{hasRoles ? (
<td>
<MultiSelectDropdown
items={allRoles}
selectedItems={roles.map(r => ({name: r.name}))}
label={roles.length ? '' : 'Select Roles'}
onApply={handleUpdateRoles}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colRoles}`, {
'admin-table--multi-select-empty': !roles.length,
})}
/>
</td>
) : null}
<td>
{allPermissions && allPermissions.length
? <MultiSelectDropdown
items={allPermissions.map(p => ({name: p}))}
selectedItems={perms.map(p => ({name: p}))}
label={
permissions && permissions.length ? '' : 'Select Permissions'
}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(
`dropdown-${USERS_TABLE.colPermissions}`,
{
'admin-table--multi-select-empty': !permissions.length,
}
)}
/>
: null}
{allPermissions && allPermissions.length ? (
<MultiSelectDropdown
items={allPermissions.map(p => ({name: p}))}
selectedItems={perms.map(p => ({name: p}))}
label={
permissions && permissions.length ? '' : 'Select Permissions'
}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colPermissions}`, {
'admin-table--multi-select-empty': !permissions.length,
})}
/>
) : null}
</td>
<DeleteConfirmTableCell
onDelete={onDelete}

View File

@ -20,7 +20,7 @@ const UsersTable = ({
onUpdatePermissions,
onUpdateRoles,
onUpdatePassword,
}) =>
}) => (
<div className="panel panel-solid">
<FilterBar
type="users"
@ -40,32 +40,35 @@ const UsersTable = ({
</tr>
</thead>
<tbody>
{users.length
? users
.filter(u => !u.hidden)
.map(user =>
<UserRow
key={user.links.self}
user={user}
onEdit={onEdit}
onSave={onSave}
onCancel={onCancel}
onDelete={onDelete}
isEditing={user.isEditing}
isNew={user.isNew}
allRoles={allRoles}
hasRoles={hasRoles}
allPermissions={permissions}
onUpdatePermissions={onUpdatePermissions}
onUpdateRoles={onUpdateRoles}
onUpdatePassword={onUpdatePassword}
/>
)
: <EmptyRow tableName={'Users'} />}
{users.length ? (
users
.filter(u => !u.hidden)
.map(user => (
<UserRow
key={user.links.self}
user={user}
onEdit={onEdit}
onSave={onSave}
onCancel={onCancel}
onDelete={onDelete}
isEditing={user.isEditing}
isNew={user.isNew}
allRoles={allRoles}
hasRoles={hasRoles}
allPermissions={permissions}
onUpdatePermissions={onUpdatePermissions}
onUpdateRoles={onUpdateRoles}
onUpdatePassword={onUpdatePassword}
/>
))
) : (
<EmptyRow tableName={'Users'} />
)}
</tbody>
</table>
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes

View File

@ -51,18 +51,12 @@ const AdminTabs = ({
return (
<Tabs className="row">
<TabList customClass="col-md-2 admin-tabs">
{tabs.map((t, i) =>
<Tab key={tabs[i].type}>
{tabs[i].type}
</Tab>
)}
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
</TabList>
<TabPanels customClass="col-md-10 admin-tabs--content">
{tabs.map((t, i) =>
<TabPanel key={tabs[i].type}>
{t.component}
</TabPanel>
)}
{tabs.map((t, i) => (
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
))}
</TabPanels>
</Tabs>
)

View File

@ -133,33 +133,33 @@ class AllUsersTable extends Component {
</tr>
</thead>
<tbody>
{users.length
? users.map(user =>
<AllUsersTableRow
user={user}
key={uuid.v4()}
organizations={organizations}
onAddToOrganization={this.handleAddToOrganization}
onRemoveFromOrganization={
this.handleRemoveFromOrganization
}
onChangeSuperAdmin={this.handleChangeSuperAdmin}
onDelete={onDeleteUser}
meID={meID}
/>
)
: <tr className="table-empty-state">
<th colSpan="6">
<p>No Users to display</p>
</th>
</tr>}
{isCreatingUser
? <AllUsersTableRowNew
{users.length ? (
users.map(user => (
<AllUsersTableRow
user={user}
key={uuid.v4()}
organizations={organizations}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
onAddToOrganization={this.handleAddToOrganization}
onRemoveFromOrganization={this.handleRemoveFromOrganization}
onChangeSuperAdmin={this.handleChangeSuperAdmin}
onDelete={onDeleteUser}
meID={meID}
/>
: null}
))
) : (
<tr className="table-empty-state">
<th colSpan="6">
<p>No Users to display</p>
</th>
</tr>
)}
{isCreatingUser ? (
<AllUsersTableRowNew
organizations={organizations}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
/>
) : null}
</tbody>
</table>
</div>

View File

@ -12,10 +12,9 @@ const AllUsersTableHeader = ({
onChangeAuthConfig,
}) => {
const numUsersString = `${numUsers} User${numUsers === 1 ? '' : 's'}`
const numOrganizationsString = `${numOrganizations} Org${numOrganizations ===
1
? ''
: 's'}`
const numOrganizationsString = `${numOrganizations} Org${
numOrganizations === 1 ? '' : 's'
}`
return (
<div className="panel-heading">

View File

@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import Tags from 'shared/components/Tags'
import SlideToggle from 'shared/components/SlideToggle'
@ -37,7 +38,7 @@ const AllUsersTableRow = ({
name: organizations.find(o => r.organization === o.id).name,
}))
const wrappedDelete = () => onDelete(user)
const wrappedDelete = _.curry(onDelete, user)
const removeWarning = userIsMe
? 'Delete your user record\nand log yourself out?'
@ -46,14 +47,14 @@ const AllUsersTableRow = ({
return (
<tr className={'chronograf-admin-table--user'}>
<td>
{userIsMe
? <strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
: <strong>
{user.name}
</strong>}
{userIsMe ? (
<strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
) : (
<strong>{user.name}</strong>
)}
</td>
<td style={{width: colOrganizations}}>
<Tags
@ -64,12 +65,8 @@ const AllUsersTableRow = ({
addMenuChoose={onAddToOrganization(user)}
/>
</td>
<td style={{width: colProvider}}>
{user.provider}
</td>
<td style={{width: colScheme}}>
{user.scheme}
</td>
<td style={{width: colProvider}}>{user.provider}</td>
<td style={{width: colScheme}}>{user.scheme}</td>
<td style={{width: colSuperAdmin}} className="text-center">
<SlideToggle
active={user.superAdmin}
@ -83,7 +80,8 @@ const AllUsersTableRow = ({
confirmText={removeWarning}
confirmAction={wrappedDelete}
size="btn-xs"
text="Remove"
type="btn-danger"
text="Delete"
customClass="table--show-on-row-hover"
/>
</td>

View File

@ -39,10 +39,9 @@ class OrganizationsTable extends Component {
} = this.props
const {isCreatingOrganization} = this.state
const tableTitle = `${organizations.length} Organization${organizations.length ===
1
? ''
: 's'}`
const tableTitle = `${organizations.length} Organization${
organizations.length === 1 ? '' : 's'
}`
if (!organizations.length) {
return (
@ -56,9 +55,7 @@ class OrganizationsTable extends Component {
return (
<div className="panel panel-solid">
<div className="panel-heading">
<h2 className="panel-title">
{tableTitle}
</h2>
<h2 className="panel-title">{tableTitle}</h2>
<button
className="btn btn-sm btn-primary"
onClick={this.handleClickCreateOrganization}
@ -76,13 +73,13 @@ class OrganizationsTable extends Component {
</div>
<div className="fancytable--th orgs-table--delete" />
</div>
{isCreatingOrganization
? <OrganizationsTableRowNew
onCreateOrganization={this.handleCreateOrganization}
onCancelCreateOrganization={this.handleCancelCreateOrganization}
/>
: null}
{organizations.map(org =>
{isCreatingOrganization ? (
<OrganizationsTableRowNew
onCreateOrganization={this.handleCreateOrganization}
onCancelCreateOrganization={this.handleCancelCreateOrganization}
/>
) : null}
{organizations.map(org => (
<OrganizationsTableRow
key={uuid.v4()}
organization={org}
@ -91,7 +88,7 @@ class OrganizationsTable extends Component {
onChooseDefaultRole={onChooseDefaultRole}
currentOrganization={currentOrganization}
/>
)}
))}
</div>
</div>
)

View File

@ -14,19 +14,21 @@ import {DEFAULT_ORG_ID} from 'src/admin/constants/chronografAdmin'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
const OrganizationsTableRowDeleteButton = ({organization, onClickDelete}) =>
organization.id === DEFAULT_ORG_ID
? <button
className="btn btn-sm btn-default btn-square orgs-table--delete"
disabled={true}
>
<span className="icon trash" />
</button>
: <button
className="btn btn-sm btn-default btn-square"
onClick={onClickDelete}
>
<span className="icon trash" />
</button>
organization.id === DEFAULT_ORG_ID ? (
<button
className="btn btn-sm btn-default btn-square orgs-table--delete"
disabled={true}
>
<span className="icon trash" />
</button>
) : (
<button
className="btn btn-sm btn-default btn-square"
onClick={onClickDelete}
>
<span className="icon trash" />
</button>
)
class OrganizationsTableRow extends Component {
constructor(props) {
@ -81,16 +83,18 @@ class OrganizationsTableRow extends Component {
return (
<div className="fancytable--row">
<div className="fancytable--td orgs-table--active">
{organization.id === currentOrganization.id
? <button className="btn btn-sm btn-success">
<span className="icon checkmark" /> Current
</button>
: <button
className="btn btn-sm btn-default"
onClick={this.handleChangeCurrentOrganization}
>
<span className="icon shuffle" /> Switch to
</button>}
{organization.id === currentOrganization.id ? (
<button className="btn btn-sm btn-success">
<span className="icon checkmark" /> Current
</button>
) : (
<button
className="btn btn-sm btn-default"
onClick={this.handleChangeCurrentOrganization}
>
<span className="icon shuffle" /> Switch to
</button>
)}
</div>
<InputClickToEdit
value={organization.name}
@ -105,19 +109,21 @@ class OrganizationsTableRow extends Component {
className="dropdown-stretch"
/>
</div>
{isDeleting
? <ConfirmButtons
item={organization}
onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteOrg}
onClickOutside={this.handleDismissDeleteConfirmation}
confirmLeft={true}
confirmTitle="Delete"
/>
: <OrganizationsTableRowDeleteButton
organization={organization}
onClickDelete={this.handleDeleteClick}
/>}
{isDeleting ? (
<ConfirmButtons
item={organization}
onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteOrg}
onClickOutside={this.handleDismissDeleteConfirmation}
confirmLeft={true}
confirmTitle="Delete"
/>
) : (
<OrganizationsTableRowDeleteButton
organization={organization}
onClickDelete={this.handleDeleteClick}
/>
)}
</div>
)
}

View File

@ -55,9 +55,7 @@ class ProvidersTable extends Component {
return (
<div className="panel panel-solid">
<div className="panel-heading">
<h2 className="panel-title">
{tableTitle}
</h2>
<h2 className="panel-title">{tableTitle}</h2>
<button
className="btn btn-sm btn-primary"
onClick={this.handleClickCreateMap}
@ -66,59 +64,58 @@ class ProvidersTable extends Component {
<span className="icon plus" /> Create Mapping
</button>
</div>
{mappings.length || isCreatingMap
? <div className="panel-body">
<div className="fancytable--labels">
<div className="fancytable--th provider--scheme">Scheme</div>
<div className="fancytable--th provider--provider">
Provider
</div>
<div className="fancytable--th provider--providerorg">
Provider Org
</div>
<div className="fancytable--th provider--arrow" />
<div className="fancytable--th provider--redirect">
Organization
</div>
<div className="fancytable--th" />
<div className="fancytable--th provider--delete" />
{mappings.length || isCreatingMap ? (
<div className="panel-body">
<div className="fancytable--labels">
<div className="fancytable--th provider--scheme">Scheme</div>
<div className="fancytable--th provider--provider">Provider</div>
<div className="fancytable--th provider--providerorg">
Provider Org
</div>
{mappings.map((mapping, i) =>
<ProvidersTableRow
key={uuid.v4()}
mapping={mapping}
organizations={organizations}
schemes={SCHEMES}
onDelete={onDeleteMap}
onUpdate={onUpdateMap}
rowIndex={i + 1}
/>
)}
{isCreatingMap
? <ProvidersTableRowNew
organizations={organizations}
schemes={SCHEMES}
onCreate={this.handleCreateMap}
onCancel={this.handleCancelCreateMap}
rowIndex={mappings.length + 1}
/>
: null}
<div className="fancytable--th provider--arrow" />
<div className="fancytable--th provider--redirect">
Organization
</div>
<div className="fancytable--th provider--delete" />
</div>
: <div className="panel-body">
<div className="generic-empty-state">
<h4 style={{margin: '50px 0'}}>
Looks like you have no mappings<br />
New users will not be able to sign up automatically
</h4>
<button
className="btn btn-sm btn-primary"
onClick={this.handleClickCreateMap}
disabled={isCreatingMap}
>
<span className="icon plus" /> Create Mapping
</button>
</div>
</div>}
{mappings.map((mapping, i) => (
<ProvidersTableRow
key={uuid.v4()}
mapping={mapping}
organizations={organizations}
schemes={SCHEMES}
onDelete={onDeleteMap}
onUpdate={onUpdateMap}
rowIndex={i + 1}
/>
))}
{isCreatingMap ? (
<ProvidersTableRowNew
organizations={organizations}
schemes={SCHEMES}
onCreate={this.handleCreateMap}
onCancel={this.handleCancelCreateMap}
rowIndex={mappings.length + 1}
/>
) : null}
</div>
) : (
<div className="panel-body">
<div className="generic-empty-state">
<h4 style={{margin: '50px 0'}}>
Looks like you have no mappings<br />
New users will not be able to sign up automatically
</h4>
<button
className="btn btn-sm btn-primary"
onClick={this.handleClickCreateMap}
disabled={isCreatingMap}
>
<span className="icon plus" /> Create Mapping
</button>
</div>
</div>
)}
</div>
)
}

View File

@ -71,13 +71,15 @@ class ProvidersTableRow extends Component {
const isDefaultMapping = DEFAULT_MAPPING_ID === mapping.id
return (
<div className="fancytable--row">
<Dropdown
items={schemes}
onChoose={this.handleChooseScheme}
selected={scheme}
className="fancytable--td provider--scheme"
disabled={isDefaultMapping}
/>
<div className="fancytable--td provider--scheme">
<Dropdown
items={schemes}
onChoose={this.handleChooseScheme}
selected={scheme}
className="dropdown-stretch"
disabled={isDefaultMapping}
/>
</div>
<InputClickToEdit
value={provider}
wrapperClass="fancytable--td provider--provider"
@ -104,20 +106,22 @@ class ProvidersTableRow extends Component {
disabled={isDefaultMapping}
/>
</div>
{isDeleting
? <ConfirmButtons
item={mapping}
onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteMap}
onClickOutside={this.handleDismissDeleteConfirmation}
confirmTitle="Delete"
/>
: <button
className="btn btn-sm btn-default btn-square"
onClick={this.handleDeleteClick}
>
<span className="icon trash" />
</button>}
{isDeleting ? (
<ConfirmButtons
item={mapping}
onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteMap}
onClickOutside={this.handleDismissDeleteConfirmation}
confirmTitle="Delete"
/>
) : (
<button
className="btn btn-sm btn-default btn-square"
onClick={this.handleDeleteClick}
>
<span className="icon trash" />
</button>
)}
</div>
)
}

View File

@ -4,12 +4,12 @@ import ConfirmButtons from 'src/shared/components/ConfirmButtons'
import Dropdown from 'src/shared/components/Dropdown'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
type Organization = {
interface Organization {
id: string
name: string
}
type Scheme = {
interface Scheme {
text: string
}
@ -33,10 +33,10 @@ class ProvidersTableRowNew extends PureComponent<Props, State> {
super(props)
this.state = {
scheme: '*',
organizationId: 'default',
provider: null,
providerOrganization: null,
organizationId: 'default',
scheme: '*',
}
this.handleChooseScheme = this.handleChooseScheme.bind(this)
@ -46,28 +46,7 @@ class ProvidersTableRowNew extends PureComponent<Props, State> {
this.handleSaveNewMapping = this.handleSaveNewMapping.bind(this)
}
handleChooseScheme(scheme: Scheme) {
this.setState({scheme: scheme.text})
}
handleChangeProvider(provider: string) {
this.setState({provider})
}
handleChangeProviderOrg(providerOrganization: string) {
this.setState({providerOrganization})
}
handleChooseOrganization(org: Organization) {
this.setState({organizationId: org.id})
}
handleSaveNewMapping() {
const {onCreate} = this.props
onCreate(this.state)
}
render() {
public render() {
const {scheme, provider, providerOrganization, organizationId} = this.state
const {organizations, onCancel, schemes, rowIndex} = this.props
@ -83,12 +62,14 @@ class ProvidersTableRowNew extends PureComponent<Props, State> {
return (
<div className="fancytable--row">
<Dropdown
items={schemes}
onChoose={this.handleChooseScheme}
selected={scheme}
className={'fancytable--td provider--scheme'}
/>
<div className="fancytable--td provider--scheme">
<Dropdown
items={schemes}
onChoose={this.handleChooseScheme}
selected={scheme}
className="dropdown-stretch"
/>
</div>
<InputClickToEdit
value={provider}
wrapperClass="fancytable--td provider--provider"
@ -124,6 +105,27 @@ class ProvidersTableRowNew extends PureComponent<Props, State> {
</div>
)
}
private handleChooseScheme(scheme: Scheme) {
this.setState({scheme: scheme.text})
}
private handleChangeProvider(provider: string) {
this.setState({provider})
}
private handleChangeProviderOrg(providerOrganization: string) {
this.setState({providerOrganization})
}
private handleChooseOrganization(org: Organization) {
this.setState({organizationId: org.id})
}
private handleSaveNewMapping() {
const {onCreate} = this.props
onCreate(this.state)
}
}
export default ProvidersTableRowNew

View File

@ -71,29 +71,31 @@ class UsersTable extends Component {
</tr>
</thead>
<tbody>
{isCreatingUser
? <UsersTableRowNew
{isCreatingUser ? (
<UsersTableRowNew
organization={organization}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
/>
) : null}
{users.length ? (
users.map(user => (
<UsersTableRow
user={user}
key={uuid.v4()}
organization={organization}
onBlur={this.handleBlurCreateUserRow}
onCreateUser={onCreateUser}
onChangeUserRole={this.handleChangeUserRole}
onDelete={this.handleDeleteUser}
meID={meID}
/>
: null}
{users.length
? users.map(user =>
<UsersTableRow
user={user}
key={uuid.v4()}
organization={organization}
onChangeUserRole={this.handleChangeUserRole}
onDelete={this.handleDeleteUser}
meID={meID}
/>
)
: <tr className="table-empty-state">
<th colSpan="5">
<p>No Users to display</p>
</th>
</tr>}
))
) : (
<tr className="table-empty-state">
<th colSpan="5">
<p>No Users to display</p>
</th>
</tr>
)}
</tbody>
</table>
</div>

View File

@ -29,14 +29,14 @@ const UsersTableRow = ({
return (
<tr className={'chronograf-admin-table--user'}>
<td>
{userIsMe
? <strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
: <strong>
{user.name}
</strong>}
{userIsMe ? (
<strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
) : (
<strong>{user.name}</strong>
)}
</td>
<td style={{width: colRole}}>
<span className="chronograf-user--role">
@ -50,12 +50,8 @@ const UsersTableRow = ({
/>
</span>
</td>
<td style={{width: colProvider}}>
{user.provider}
</td>
<td style={{width: colScheme}}>
{user.scheme}
</td>
<td style={{width: colProvider}}>{user.provider}</td>
<td style={{width: colScheme}}>{user.scheme}</td>
<DeleteConfirmTableCell
text="Remove"
onDelete={onDelete}

View File

@ -165,37 +165,39 @@ class AdminInfluxDBPage extends Component {
</div>
</div>
<FancyScrollbar className="page-contents">
{users
? <div className="container-fluid">
<div className="row">
<AdminTabs
users={users}
roles={roles}
source={source}
hasRoles={hasRoles}
permissions={allowed}
onFilterUsers={filterUsers}
onFilterRoles={filterRoles}
onEditUser={this.handleEditUser}
onEditRole={this.handleEditRole}
onSaveUser={this.handleSaveUser}
onSaveRole={this.handleSaveRole}
onDeleteUser={this.handleDeleteUser}
onDeleteRole={this.handleDeleteRole}
onClickCreate={this.handleClickCreate}
onCancelEditUser={this.handleCancelEditUser}
onCancelEditRole={this.handleCancelEditRole}
isEditingUsers={users.some(u => u.isEditing)}
isEditingRoles={roles.some(r => r.isEditing)}
onUpdateRoleUsers={this.handleUpdateRoleUsers}
onUpdateUserRoles={this.handleUpdateUserRoles}
onUpdateUserPassword={this.handleUpdateUserPassword}
onUpdateRolePermissions={this.handleUpdateRolePermissions}
onUpdateUserPermissions={this.handleUpdateUserPermissions}
/>
</div>
{users ? (
<div className="container-fluid">
<div className="row">
<AdminTabs
users={users}
roles={roles}
source={source}
hasRoles={hasRoles}
permissions={allowed}
onFilterUsers={filterUsers}
onFilterRoles={filterRoles}
onEditUser={this.handleEditUser}
onEditRole={this.handleEditRole}
onSaveUser={this.handleSaveUser}
onSaveRole={this.handleSaveRole}
onDeleteUser={this.handleDeleteUser}
onDeleteRole={this.handleDeleteRole}
onClickCreate={this.handleClickCreate}
onCancelEditUser={this.handleCancelEditUser}
onCancelEditRole={this.handleCancelEditRole}
isEditingUsers={users.some(u => u.isEditing)}
isEditingRoles={roles.some(r => r.isEditing)}
onUpdateRoleUsers={this.handleUpdateRoleUsers}
onUpdateUserRoles={this.handleUpdateUserRoles}
onUpdateUserPassword={this.handleUpdateUserPassword}
onUpdateRolePermissions={this.handleUpdateRolePermissions}
onUpdateUserPermissions={this.handleUpdateUserPermissions}
/>
</div>
: <div className="page-spinner" />}
</div>
) : (
<div className="page-spinner" />
)}
</FancyScrollbar>
</div>
)

View File

@ -5,7 +5,7 @@ import {connect} from 'react-redux'
import AdminTabs from 'src/admin/components/chronograf/AdminTabs'
import FancyScrollbar from 'shared/components/FancyScrollbar'
const AdminChronografPage = ({me}) =>
const AdminChronografPage = ({me}) => (
<div className="page">
<div className="page-header">
<div className="page-header__container">
@ -22,6 +22,7 @@ const AdminChronografPage = ({me}) =>
</div>
</FancyScrollbar>
</div>
)
const {shape, string} = PropTypes

View File

@ -7,7 +7,7 @@ import * as configActionCreators from 'src/shared/actions/config'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable'
import {AuthLinks, User, Role, Organization} from 'src/types'
import {AuthLinks, Organization, Role, User} from 'src/types'
interface Props {
notify: () => void
@ -47,12 +47,12 @@ export class AllUsersPage extends PureComponent<Props, State> {
}
}
componentDidMount() {
public componentDidMount() {
const {links, actionsConfig: {getAuthConfigAsync}} = this.props
getAuthConfigAsync(links.config.auth)
}
async componentWillMount() {
public async componentWillMount() {
const {
links,
actionsAdmin: {loadOrganizationsAsync, loadUsersAsync},
@ -68,12 +68,12 @@ export class AllUsersPage extends PureComponent<Props, State> {
this.setState({isLoading: false})
}
handleCreateUser = (user: User) => {
public handleCreateUser = (user: User) => {
const {links, actionsAdmin: {createUserAsync}} = this.props
createUserAsync(links.allUsers, user)
}
handleUpdateUserRoles = (
public handleUpdateUserRoles = (
user: User,
roles: Role[],
successMessage: string
@ -83,7 +83,7 @@ export class AllUsersPage extends PureComponent<Props, State> {
updateUserAsync(user, updatedUser, successMessage)
}
handleUpdateUserSuperAdmin = (user: User, superAdmin: boolean) => {
public handleUpdateUserSuperAdmin = (user: User, superAdmin: boolean) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(
@ -93,12 +93,12 @@ export class AllUsersPage extends PureComponent<Props, State> {
)
}
handleDeleteUser = (user: User) => {
public handleDeleteUser = (user: User) => {
const {actionsAdmin: {deleteUserAsync}} = this.props
deleteUserAsync(user, {isAbsoluteDelete: true})
}
render() {
public render() {
const {
meID,
users,
@ -133,10 +133,10 @@ const mapStateToProps = ({
adminChronograf: {organizations, users},
config: {auth: authConfig},
}) => ({
authConfig,
links,
organizations,
users,
authConfig,
})
const mapDispatchToProps = dispatch => ({

View File

@ -3,8 +3,10 @@ import {proxy} from 'utils/queryUrlGenerator'
export const getAlerts = (source, timeRange, limit) =>
proxy({
source,
query: `SELECT host, value, level, alertName FROM alerts WHERE time >= '${timeRange.lower}' AND time <= '${timeRange.upper}' ORDER BY time desc ${limit
? `LIMIT ${limit}`
: ''}`,
query: `SELECT host, value, level, alertName FROM alerts WHERE time >= '${
timeRange.lower
}' AND time <= '${timeRange.upper}' ORDER BY time desc ${
limit ? `LIMIT ${limit}` : ''
}`,
db: 'chronograf',
})

View File

@ -79,121 +79,125 @@ class AlertsTable extends Component {
this.state.sortDirection
)
const {colName, colLevel, colTime, colHost, colValue} = ALERTS_TABLE
return this.props.alerts.length
? <div className="alert-history-table">
<div className="alert-history-table--thead">
<div
onClick={this.changeSort('name')}
className={this.sortableClasses('name')}
style={{width: colName}}
>
Name
</div>
<div
onClick={this.changeSort('level')}
className={this.sortableClasses('level')}
style={{width: colLevel}}
>
Level
</div>
<div
onClick={this.changeSort('time')}
className={this.sortableClasses('time')}
style={{width: colTime}}
>
Time
</div>
<div
onClick={this.changeSort('host')}
className={this.sortableClasses('host')}
style={{width: colHost}}
>
Host
</div>
<div
onClick={this.changeSort('value')}
className={this.sortableClasses('value')}
style={{width: colValue}}
>
Value
</div>
return this.props.alerts.length ? (
<div className="alert-history-table">
<div className="alert-history-table--thead">
<div
onClick={this.changeSort('name')}
className={this.sortableClasses('name')}
style={{width: colName}}
>
Name
</div>
<div
onClick={this.changeSort('level')}
className={this.sortableClasses('level')}
style={{width: colLevel}}
>
Level
</div>
<div
onClick={this.changeSort('time')}
className={this.sortableClasses('time')}
style={{width: colTime}}
>
Time
</div>
<div
onClick={this.changeSort('host')}
className={this.sortableClasses('host')}
style={{width: colHost}}
>
Host
</div>
<div
onClick={this.changeSort('value')}
className={this.sortableClasses('value')}
style={{width: colValue}}
>
Value
</div>
<InfiniteScroll
className="alert-history-table--tbody"
itemHeight={25}
items={alerts.map(({name, level, time, host, value}) => {
return (
<div className="alert-history-table--tr" key={uuid.v4()}>
<div
className="alert-history-table--td"
style={{width: colName}}
>
{name}
</div>
<div
className={`alert-history-table--td alert-level-${level.toLowerCase()}`}
style={{width: colLevel}}
>
<span
className={classnames(
'table-dot',
{'dot-critical': level === 'CRITICAL'},
{'dot-success': level === 'OK'}
)}
/>
</div>
<div
className="alert-history-table--td"
style={{width: colTime}}
>
{new Date(Number(time)).toISOString()}
</div>
<div
className="alert-history-table--td alert-history-table--host"
style={{width: colHost}}
>
<Link to={`/sources/${id}/hosts/${host}`} title={host}>
{host}
</Link>
</div>
<div
className="alert-history-table--td"
style={{width: colValue}}
>
{value}
</div>
</div>
)
})}
/>
</div>
: this.renderTableEmpty()
<InfiniteScroll
className="alert-history-table--tbody"
itemHeight={25}
items={alerts.map(({name, level, time, host, value}) => {
return (
<div className="alert-history-table--tr" key={uuid.v4()}>
<div
className="alert-history-table--td"
style={{width: colName}}
>
{name}
</div>
<div
className={`alert-history-table--td alert-level-${level.toLowerCase()}`}
style={{width: colLevel}}
>
<span
className={classnames(
'table-dot',
{'dot-critical': level === 'CRITICAL'},
{'dot-success': level === 'OK'}
)}
/>
</div>
<div
className="alert-history-table--td"
style={{width: colTime}}
>
{new Date(Number(time)).toISOString()}
</div>
<div
className="alert-history-table--td alert-history-table--host"
style={{width: colHost}}
>
<Link to={`/sources/${id}/hosts/${host}`} title={host}>
{host}
</Link>
</div>
<div
className="alert-history-table--td"
style={{width: colValue}}
>
{value}
</div>
</div>
)
})}
/>
</div>
) : (
this.renderTableEmpty()
)
}
renderTableEmpty() {
const {source: {id}, shouldNotBeFilterable} = this.props
return shouldNotBeFilterable
? <div className="graph-empty">
<p>
Learn how to configure your first <strong>Rule</strong> in<br />
the <em>Getting Started</em> guide
</p>
</div>
: <div className="generic-empty-state">
<h4 className="no-user-select">There are no Alerts to display</h4>
<br />
<h6 className="no-user-select">
Try changing the Time Range or
<Link
style={{marginLeft: '10px'}}
to={`/sources/${id}/alert-rules/new`}
className="btn btn-primary btn-sm"
>
Create an Alert Rule
</Link>
</h6>
</div>
return shouldNotBeFilterable ? (
<div className="graph-empty">
<p>
Learn how to configure your first <strong>Rule</strong> in<br />
the <em>Getting Started</em> guide
</p>
</div>
) : (
<div className="generic-empty-state">
<h4 className="no-user-select">There are no Alerts to display</h4>
<br />
<h6 className="no-user-select">
Try changing the Time Range or
<Link
style={{marginLeft: '10px'}}
to={`/sources/${id}/alert-rules/new`}
className="btn btn-primary btn-sm"
>
Create an Alert Rule
</Link>
</h6>
</div>
)
}
render() {
@ -205,35 +209,33 @@ class AlertsTable extends Component {
alertsCount,
} = this.props
return shouldNotBeFilterable
? <div className="alerts-widget">
{this.renderTable()}
{limit && alertsCount
? <button
className="btn btn-sm btn-default btn-block"
onClick={onGetMoreAlerts}
disabled={isAlertsMaxedOut}
style={{marginBottom: '20px'}}
>
{isAlertsMaxedOut
? `All ${alertsCount} Alerts displayed`
: 'Load next 30 Alerts'}
</button>
: null}
</div>
: <div className="panel">
<div className="panel-heading">
<h2 className="panel-title">
{this.props.alerts.length} Alerts
</h2>
{this.props.alerts.length
? <SearchBar onSearch={this.filterAlerts} />
: null}
</div>
<div className="panel-body">
{this.renderTable()}
</div>
return shouldNotBeFilterable ? (
<div className="alerts-widget">
{this.renderTable()}
{limit && alertsCount ? (
<button
className="btn btn-sm btn-default btn-block"
onClick={onGetMoreAlerts}
disabled={isAlertsMaxedOut}
style={{marginBottom: '20px'}}
>
{isAlertsMaxedOut
? `All ${alertsCount} Alerts displayed`
: 'Load next 30 Alerts'}
</button>
) : null}
</div>
) : (
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">{this.props.alerts.length} Alerts</h2>
{this.props.alerts.length ? (
<SearchBar onSearch={this.filterAlerts} />
) : null}
</div>
<div className="panel-body">{this.renderTable()}</div>
</div>
)
}
}

View File

@ -30,7 +30,9 @@ class AlertsApp extends Component {
alerts: [],
timeRange: {
upper: moment().format(),
lower: moment().subtract(lowerInSec || oneDayInSec, 'seconds').format(),
lower: moment()
.subtract(lowerInSec || oneDayInSec, 'seconds')
.format(),
},
limit: props.limit || 0, // only used if AlertsApp receives a limit prop
limitMultiplier: 1, // only used if AlertsApp receives a limit prop
@ -118,17 +120,19 @@ class AlertsApp extends Component {
const {source, isWidget, limit} = this.props
const {isAlertsMaxedOut, alerts} = this.state
return this.state.hasKapacitor
? <AlertsTable
source={source}
alerts={this.state.alerts}
shouldNotBeFilterable={isWidget}
limit={limit}
onGetMoreAlerts={this.handleGetMoreAlerts}
isAlertsMaxedOut={isAlertsMaxedOut}
alertsCount={alerts.length}
/>
: <NoKapacitorError source={source} />
return this.state.hasKapacitor ? (
<AlertsTable
source={source}
alerts={this.state.alerts}
shouldNotBeFilterable={isWidget}
limit={limit}
onGetMoreAlerts={this.handleGetMoreAlerts}
isAlertsMaxedOut={isAlertsMaxedOut}
alertsCount={alerts.length}
/>
) : (
<NoKapacitorError source={source} />
)
}
handleApplyTime = timeRange => {
@ -143,33 +147,33 @@ class AlertsApp extends Component {
return <div className="page-spinner" />
}
return isWidget
? this.renderSubComponents()
: <div className="page alert-history-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Alert History</h1>
</div>
<div className="page-header__right">
<SourceIndicator />
<CustomTimeRangeDropdown
onApplyTimeRange={this.handleApplyTime}
timeRange={timeRange}
/>
</div>
return isWidget ? (
this.renderSubComponents()
) : (
<div className="page alert-history-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">Alert History</h1>
</div>
</div>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
{this.renderSubComponents()}
</div>
</div>
<div className="page-header__right">
<SourceIndicator />
<CustomTimeRangeDropdown
onApplyTimeRange={this.handleApplyTime}
timeRange={timeRange}
/>
</div>
</div>
</div>
<div className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">{this.renderSubComponents()}</div>
</div>
</div>
</div>
</div>
)
}
}

View File

@ -20,12 +20,12 @@ const Login = ({authData: {auth}}) => {
<strong>{VERSION}</strong> / Time-Series Data Visualization
</p>
{auth.links &&
auth.links.map(({name, login, label}) =>
auth.links.map(({name, login, label}) => (
<a key={name} className="btn btn-primary" href={login}>
<span className={`icon ${name}`} />
Log in with {label}
</a>
)}
))}
</SplashPage>
</div>
)

View File

@ -65,31 +65,31 @@ class Purgatory extends Component {
<Notifications />
<SplashPage>
<div className="auth--purgatory">
<h3>
{name}
</h3>
<h3>{name}</h3>
<h6>
{subHeading}{' '}
<code>
{scheme}/{provider}
</code>
</h6>
{rolesAndOrgs.length
? <div className="auth--list">
{rolesAndOrgs.map((rag, i) =>
<PurgatoryAuthItem
key={i}
roleAndOrg={rag}
superAdmin={superAdmin}
onClickLogin={handleClickLogin({
router,
links,
meChangeOrganization,
})}
/>
)}
</div>
: <p>You are a Lost Soul</p>}
{rolesAndOrgs.length ? (
<div className="auth--list">
{rolesAndOrgs.map((rag, i) => (
<PurgatoryAuthItem
key={i}
roleAndOrg={rag}
superAdmin={superAdmin}
onClickLogin={handleClickLogin({
router,
links,
meChangeOrganization,
})}
/>
))}
</div>
) : (
<p>You are a Lost Soul</p>
)}
<a href={logoutLink} className="btn btn-sm btn-link auth--logout">
Log out
</a>

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import {isUserAuthorized, VIEWER_ROLE} from 'src/auth/Authorized'
const PurgatoryAuthItem = ({roleAndOrg, onClickLogin, superAdmin}) =>
const PurgatoryAuthItem = ({roleAndOrg, onClickLogin, superAdmin}) => (
<div
className={
roleAndOrg.currentOrganization
@ -12,24 +12,23 @@ const PurgatoryAuthItem = ({roleAndOrg, onClickLogin, superAdmin}) =>
}
>
<div className="auth--list-info">
<div className="auth--list-org">
{roleAndOrg.organization.name}
</div>
<div className="auth--list-role">
{roleAndOrg.role}
</div>
<div className="auth--list-org">{roleAndOrg.organization.name}</div>
<div className="auth--list-role">{roleAndOrg.role}</div>
</div>
{superAdmin || isUserAuthorized(roleAndOrg.role, VIEWER_ROLE)
? <button
className="btn btn-sm btn-primary"
onClick={onClickLogin(roleAndOrg.organization)}
>
Log in
</button>
: <span className="auth--list-blocked">
Contact your Admin<br />for access
</span>}
{superAdmin || isUserAuthorized(roleAndOrg.role, VIEWER_ROLE) ? (
<button
className="btn btn-sm btn-primary"
onClick={onClickLogin(roleAndOrg.organization)}
>
Log in
</button>
) : (
<span className="auth--list-blocked">
Contact your Admin<br />for access
</span>
)}
</div>
)
const {bool, func, shape, string} = PropTypes

View File

@ -91,9 +91,7 @@ class AxesOptions extends Component {
autoHide={false}
>
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">
{menuOption} Controls
</h5>
<h5 className="display-options--header">{menuOption} Controls</h5>
<form autoComplete="off" className="form-group-wrapper">
<div className="form-group col-sm-12">
<label htmlFor="prefix">Title</label>

View File

@ -318,27 +318,29 @@ class CellEditorOverlay extends Component {
isDisplayOptionsTabActive={isDisplayOptionsTabActive}
onClickDisplayOptions={this.handleClickDisplayOptionsTab}
/>
{isDisplayOptionsTabActive
? <DisplayOptions
queryConfigs={queriesWorkingDraft}
onToggleStaticLegend={this.handleToggleStaticLegend}
staticLegend={staticLegend}
onResetFocus={this.handleResetFocus}
/>
: <QueryMaker
source={this.getSource()}
templates={templates}
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
onDeleteQuery={this.handleDeleteQuery}
onAddQuery={this.handleAddQuery}
activeQueryIndex={activeQueryIndex}
activeQuery={this.getActiveQuery()}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
initialGroupByTime={AUTO_GROUP_BY}
/>}
{isDisplayOptionsTabActive ? (
<DisplayOptions
queryConfigs={queriesWorkingDraft}
onToggleStaticLegend={this.handleToggleStaticLegend}
staticLegend={staticLegend}
onResetFocus={this.handleResetFocus}
/>
) : (
<QueryMaker
source={this.getSource()}
templates={templates}
queries={queriesWorkingDraft}
actions={queryActions}
autoRefresh={autoRefresh}
timeRange={timeRange}
onDeleteQuery={this.handleDeleteQuery}
onAddQuery={this.handleAddQuery}
activeQueryIndex={activeQueryIndex}
activeQuery={this.getActiveQuery()}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
initialGroupByTime={AUTO_GROUP_BY}
/>
)}
</CEOBottom>
</ResizeContainer>
</div>
@ -346,10 +348,9 @@ class CellEditorOverlay extends Component {
}
}
const CEOBottom = ({children}) =>
<div className="overlay-technology--editor">
{children}
</div>
const CEOBottom = ({children}) => (
<div className="overlay-technology--editor">{children}</div>
)
const {arrayOf, func, node, number, shape, string} = PropTypes

View File

@ -49,37 +49,39 @@ const Dashboard = ({
setScrollTop={setScrollTop}
>
<div className="dashboard container-fluid full-width">
{inPresentationMode
? null
: <TemplateControlBar
templates={dashboard.templates}
onSelectTemplate={onSelectTemplate}
onOpenTemplateManager={onOpenTemplateManager}
isOpen={showTemplateControlBar}
/>}
{cells.length
? <LayoutRenderer
cells={cells}
onZoom={onZoom}
source={source}
sources={sources}
isEditable={true}
timeRange={timeRange}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime}
onDeleteCell={onDeleteCell}
onPositionChange={onPositionChange}
templates={templatesIncludingDashTime}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
/>
: <div className="dashboard__empty">
<p>This Dashboard has no Cells</p>
<button className="btn btn-primary btn-m" onClick={onAddCell}>
<span className="icon plus" />Add a Cell
</button>
</div>}
{inPresentationMode ? null : (
<TemplateControlBar
templates={dashboard.templates}
onSelectTemplate={onSelectTemplate}
onOpenTemplateManager={onOpenTemplateManager}
isOpen={showTemplateControlBar}
/>
)}
{cells.length ? (
<LayoutRenderer
cells={cells}
onZoom={onZoom}
source={source}
sources={sources}
isEditable={true}
timeRange={timeRange}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime}
onDeleteCell={onDeleteCell}
onPositionChange={onPositionChange}
templates={templatesIncludingDashTime}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
/>
) : (
<div className="dashboard__empty">
<p>This Dashboard has no Cells</p>
<button className="btn btn-primary btn-m" onClick={onAddCell}>
<span className="icon plus" />Add a Cell
</button>
</div>
)}
</div>
</FancyScrollbar>
)

View File

@ -31,90 +31,85 @@ const DashboardHeader = ({
handleClickPresentationButton,
zoomedTimeRange: {zoomedLower, zoomedUpper},
}) =>
isHidden
? null
: <div className="page-header full-width">
<div className="page-header__container">
<div
className={
dashboard
? 'page-header__left page-header__dash-editable'
: 'page-header__left'
}
>
{names && names.length > 1
? <DashboardSwitcher
names={names}
activeDashboard={activeDashboard}
/>
: null}
{dashboard
? <Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={
<h1 className="page-header__title">
{activeDashboard}
</h1>
}
>
<DashboardHeaderEdit
onSave={onSave}
onCancel={onCancel}
activeDashboard={activeDashboard}
onEditDashboard={onEditDashboard}
isEditMode={isEditMode}
/>
</Authorized>
: <h1 className="page-header__title">
{activeDashboard}
</h1>}
</div>
<div className="page-header__right">
<GraphTips />
<SourceIndicator />
{dashboard
? <Authorized requiredRole={EDITOR_ROLE}>
<button
className="btn btn-primary btn-sm"
onClick={onAddCell}
>
<span className="icon plus" />
Add Cell
</button>
</Authorized>
: null}
{dashboard
? <div
className={classnames('btn btn-default btn-sm', {
active: showTemplateControlBar,
})}
onClick={onToggleTempVarControls}
>
<span className="icon cube" />Template Variables
</div>
: null}
<AutoRefreshDropdown
onChoose={handleChooseAutoRefresh}
onManualRefresh={onManualRefresh}
selected={autoRefresh}
iconName="refresh"
isHidden ? null : (
<div className="page-header full-width">
<div className="page-header__container">
<div
className={
dashboard
? 'page-header__left page-header__dash-editable'
: 'page-header__left'
}
>
{names && names.length > 1 ? (
<DashboardSwitcher
names={names}
activeDashboard={activeDashboard}
/>
<TimeRangeDropdown
onChooseTimeRange={handleChooseTimeRange}
selected={{
upper: zoomedUpper || upper,
lower: zoomedLower || lower,
}}
/>
<div
className="btn btn-default btn-sm btn-square"
onClick={handleClickPresentationButton}
) : null}
{dashboard ? (
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={
<h1 className="page-header__title">{activeDashboard}</h1>
}
>
<span className="icon expand-a" />
<DashboardHeaderEdit
onSave={onSave}
onCancel={onCancel}
activeDashboard={activeDashboard}
onEditDashboard={onEditDashboard}
isEditMode={isEditMode}
/>
</Authorized>
) : (
<h1 className="page-header__title">{activeDashboard}</h1>
)}
</div>
<div className="page-header__right">
<GraphTips />
<SourceIndicator />
{dashboard ? (
<Authorized requiredRole={EDITOR_ROLE}>
<button className="btn btn-primary btn-sm" onClick={onAddCell}>
<span className="icon plus" />
Add Cell
</button>
</Authorized>
) : null}
{dashboard ? (
<div
className={classnames('btn btn-default btn-sm', {
active: showTemplateControlBar,
})}
onClick={onToggleTempVarControls}
>
<span className="icon cube" />Template Variables
</div>
) : null}
<AutoRefreshDropdown
onChoose={handleChooseAutoRefresh}
onManualRefresh={onManualRefresh}
selected={autoRefresh}
iconName="refresh"
/>
<TimeRangeDropdown
onChooseTimeRange={handleChooseTimeRange}
selected={{
upper: zoomedUpper || upper,
lower: zoomedLower || lower,
}}
/>
<div
className="btn btn-default btn-sm btn-square"
onClick={handleClickPresentationButton}
>
<span className="icon expand-a" />
</div>
</div>
</div>
</div>
)
const {arrayOf, bool, func, number, shape, string} = PropTypes

View File

@ -46,24 +46,24 @@ class DashboardEditHeader extends Component {
return (
<div className="dashboard-title">
{isEditMode
? <input
maxLength={DASHBOARD_NAME_MAX_LENGTH}
type="text"
className="dashboard-title--input form-control input-sm"
defaultValue={activeDashboard}
autoComplete="off"
autoFocus={true}
spellCheck={false}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
placeholder="Name this Dashboard"
ref={r => (this.inputRef = r)}
/>
: <h1 onClick={onEditDashboard}>
{activeDashboard}
</h1>}
{isEditMode ? (
<input
maxLength={DASHBOARD_NAME_MAX_LENGTH}
type="text"
className="dashboard-title--input form-control input-sm"
defaultValue={activeDashboard}
autoComplete="off"
autoFocus={true}
spellCheck={false}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
placeholder="Name this Dashboard"
ref={r => (this.inputRef = r)}
/>
) : (
<h1 onClick={onEditDashboard}>{activeDashboard}</h1>
)}
</div>
)
}

View File

@ -42,7 +42,7 @@ class DashboardSwitcher extends Component {
<span className="icon dash-f" />
</button>
<ul className="dropdown-menu">
{sorted.map(({name, link}) =>
{sorted.map(({name, link}) => (
<NameLink
key={link}
name={name}
@ -50,14 +50,14 @@ class DashboardSwitcher extends Component {
activeName={activeDashboard}
onClose={this.handleCloseMenu}
/>
)}
))}
</ul>
</div>
)
}
}
const NameLink = ({name, link, activeName, onClose}) =>
const NameLink = ({name, link, activeName, onClose}) => (
<li
className={classnames('dropdown-item', {
active: name === activeName,
@ -67,6 +67,7 @@ const NameLink = ({name, link, activeName, onClose}) =>
{name}
</Link>
</li>
)
const {arrayOf, func, shape, string} = PropTypes

View File

@ -2,7 +2,7 @@ import React from 'react'
import SourceIndicator from 'shared/components/SourceIndicator'
const DashboardsHeader = () =>
const DashboardsHeader = () => (
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
@ -13,5 +13,6 @@ const DashboardsHeader = () =>
</div>
</div>
</div>
)
export default DashboardsHeader

View File

@ -48,9 +48,7 @@ class DashboardsPageContents extends Component {
<div className="col-md-12">
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">
{tableHeader}
</h2>
<h2 className="panel-title">{tableHeader}</h2>
<div className="dashboards-page--actions">
<SearchBar
placeholder="Filter by Name..."

View File

@ -7,7 +7,7 @@ import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
const AuthorizedEmptyState = ({onCreateDashboard}) =>
const AuthorizedEmptyState = ({onCreateDashboard}) => (
<div className="generic-empty-state">
<h4 style={{marginTop: '90px'}}>
Looks like you dont have any dashboards
@ -21,6 +21,7 @@ const AuthorizedEmptyState = ({onCreateDashboard}) =>
<span className="icon plus" /> Create Dashboard
</button>
</div>
)
const unauthorizedEmptyState = (
<div className="generic-empty-state">
@ -34,52 +35,56 @@ const DashboardsTable = ({
onCreateDashboard,
dashboardLink,
}) => {
return dashboards && dashboards.length
? <table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Name</th>
<th>Template Variables</th>
<th />
return dashboards && dashboards.length ? (
<table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Name</th>
<th>Template Variables</th>
<th />
</tr>
</thead>
<tbody>
{_.sortBy(dashboards, d => d.name.toLowerCase()).map(dashboard => (
<tr key={dashboard.id}>
<td>
<Link to={`${dashboardLink}/dashboards/${dashboard.id}`}>
{dashboard.name}
</Link>
</td>
<td>
{dashboard.templates.length ? (
dashboard.templates.map(tv => (
<code className="table--temp-var" key={tv.id}>
{tv.tempVar}
</code>
))
) : (
<span className="empty-string">None</span>
)}
</td>
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={<td />}
>
<DeleteConfirmTableCell
onDelete={onDeleteDashboard}
item={dashboard}
buttonSize="btn-xs"
/>
</Authorized>
</tr>
</thead>
<tbody>
{_.sortBy(dashboards, d => d.name.toLowerCase()).map(dashboard =>
<tr key={dashboard.id}>
<td>
<Link to={`${dashboardLink}/dashboards/${dashboard.id}`}>
{dashboard.name}
</Link>
</td>
<td>
{dashboard.templates.length
? dashboard.templates.map(tv =>
<code className="table--temp-var" key={tv.id}>
{tv.tempVar}
</code>
)
: <span className="empty-string">None</span>}
</td>
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={<td />}
>
<DeleteConfirmTableCell
onDelete={onDeleteDashboard}
item={dashboard}
buttonSize="btn-xs"
/>
</Authorized>
</tr>
)}
</tbody>
</table>
: <Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={unauthorizedEmptyState}
>
<AuthorizedEmptyState onCreateDashboard={onCreateDashboard} />
</Authorized>
))}
</tbody>
</table>
) : (
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={unauthorizedEmptyState}
>
<AuthorizedEmptyState onCreateDashboard={onCreateDashboard} />
</Authorized>
)
}
const {arrayOf, func, shape, string} = PropTypes

View File

@ -9,11 +9,9 @@ const DisplayOptionsInput = ({
labelText,
colWidth,
placeholder,
}) =>
}) => (
<div className={`form-group ${colWidth}`}>
<label htmlFor={name}>
{labelText}
</label>
<label htmlFor={name}>{labelText}</label>
<input
className="form-control input-sm"
type="text"
@ -24,6 +22,7 @@ const DisplayOptionsInput = ({
placeholder={placeholder}
/>
</div>
)
const {func, string} = PropTypes

View File

@ -173,7 +173,7 @@ class GaugeOptions extends Component {
>
<span className="icon plus" /> Add Threshold
</button>
{gaugeColors.map(color =>
{gaugeColors.map(color => (
<Threshold
isMin={color.value === gaugeColors[0].value}
isMax={
@ -189,7 +189,7 @@ class GaugeOptions extends Component {
onDeleteThreshold={this.handleDeleteThreshold}
onSortColors={this.handleSortColors}
/>
)}
))}
</div>
<div className="graph-options-group form-group-wrapper">
<div className="form-group col-xs-6">

View File

@ -2,7 +2,7 @@ import React, {PureComponent} from 'react'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
type Column = {
interface Column {
internalName: string
displayName: string
}
@ -20,19 +20,17 @@ class GraphOptionsCustomizableColumn extends PureComponent<Props, {}> {
this.handleColumnRename = this.handleColumnRename.bind(this)
}
handleColumnRename(rename) {
public handleColumnRename(rename) {
const {onColumnRename, internalName} = this.props
onColumnRename({internalName, displayName: rename})
}
render() {
public render() {
const {internalName, displayName} = this.props
return (
<div className="column-controls--section">
<div className="column-controls--label">
{internalName}
</div>
<div className="column-controls--label">{internalName}</div>
<InputClickToEdit
value={displayName}
wrapperClass="column-controls-input"

View File

@ -3,7 +3,7 @@ import React, {SFC} from 'react'
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
import uuid from 'uuid'
type Column = {
interface Column {
internalName: string
displayName: string
}

View File

@ -0,0 +1,25 @@
import React, {SFC} from 'react'
interface Props {
fixed: boolean
onToggleFixFirstColumn: () => void
}
const GraphOptionsFixFirstColumn: SFC<Props> = ({
fixed,
onToggleFixFirstColumn,
}) => (
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="fixFirstColumnCheckbox"
checked={!!fixed}
onChange={onToggleFixFirstColumn}
/>
<label htmlFor="fixFirstColumnCheckbox">Fix First Column</label>
</div>
</div>
)
export default GraphOptionsFixFirstColumn

View File

@ -1,29 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
const GraphOptionsSortBy = ({sortByOptions, onChooseSortBy}) =>
<div className="form-group col-xs-6">
<label>Sort By</label>
<Dropdown
items={sortByOptions}
selected={sortByOptions[0].text}
buttonColor="btn-default"
buttonSize="btn-sm"
className="dropdown-stretch"
onChoose={onChooseSortBy}
/>
</div>
const {arrayOf, func, shape, string} = PropTypes
GraphOptionsSortBy.propTypes = {
sortByOptions: arrayOf(
shape({
text: string.isRequired,
}).isRequired
),
onChooseSortBy: func,
}
export default GraphOptionsSortBy

View File

@ -0,0 +1,42 @@
import React from 'react'
import Dropdown from 'src/shared/components/Dropdown'
interface Option {
text: string
key: string
}
interface TableColumn {
internalName: string
displayName: string
}
interface Props {
sortByOptions: any[]
onChooseSortBy: (option: Option) => void
selected: TableColumn
}
const GraphOptionsSortBy = ({
sortByOptions,
onChooseSortBy,
selected,
}: Props) => {
const selectedValue = selected.displayName || selected.internalName
return (
<div className="form-group col-xs-6">
<label>Sort By</label>
<Dropdown
items={sortByOptions}
selected={selectedValue}
buttonColor="btn-default"
buttonSize="btn-sm"
className="dropdown-stretch"
onChoose={onChooseSortBy}
/>
</div>
)
}
export default GraphOptionsSortBy

View File

@ -16,25 +16,25 @@ const GraphOptionsTextWrapping = ({
<label>Text Wrapping</label>
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={`${thresholdsListType === THRESHOLD_TYPE_BG
? 'active'
: ''}`}
className={`${
thresholdsListType === THRESHOLD_TYPE_BG ? 'active' : ''
}`}
onClick={onToggleTextWrapping}
>
Truncate
</li>
<li
className={`${thresholdsListType === THRESHOLD_TYPE_TEXT
? 'active'
: ''}`}
className={`${
thresholdsListType === THRESHOLD_TYPE_TEXT ? 'active' : ''
}`}
onClick={onToggleTextWrapping}
>
Wrap
</li>
<li
className={`${thresholdsListType === THRESHOLD_TYPE_BG
? 'active'
: ''}`}
className={`${
thresholdsListType === THRESHOLD_TYPE_BG ? 'active' : ''
}`}
onClick={onToggleTextWrapping}
>
Single Line

View File

@ -1,29 +1,31 @@
import React from 'react'
import PropTypes from 'prop-types'
const VERTICAL = 'VERTICAL'
const HORIZONTAL = 'HORIZONTAL'
const GraphOptionsTimeAxis = ({TimeAxis, onToggleTimeAxis}) =>
const GraphOptionsTimeAxis = ({verticalTimeAxis, onToggleVerticalTimeAxis}) => (
<div className="form-group col-xs-12 col-sm-6">
<label>Time Axis</label>
<ul className="nav nav-tablist nav-tablist-sm">
<li
className={`${TimeAxis === VERTICAL ? 'active' : ''}`}
onClick={onToggleTimeAxis}
className={verticalTimeAxis ? 'active' : ''}
onClick={onToggleVerticalTimeAxis(true)}
>
Vertical
</li>
<li
className={`${TimeAxis === HORIZONTAL ? 'active' : ''}`}
onClick={onToggleTimeAxis}
className={verticalTimeAxis ? '' : 'active'}
onClick={onToggleVerticalTimeAxis(false)}
>
Horizontal
</li>
</ul>
</div>
)
const {func, string} = PropTypes
const {bool, func} = PropTypes
GraphOptionsTimeAxis.propTypes = {TimeAxis: string, onToggleTimeAxis: func}
GraphOptionsTimeAxis.propTypes = {
verticalTimeAxis: bool,
onToggleVerticalTimeAxis: func,
}
export default GraphOptionsTimeAxis

View File

@ -1,11 +1,11 @@
import React, {PureComponent} from 'react'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
import {Dropdown} from 'src/shared/components/Dropdown'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
import {
FORMAT_OPTIONS,
TIME_FORMAT_DEFAULT,
TIME_FORMAT_CUSTOM,
TIME_FORMAT_DEFAULT,
} from 'src/shared/constants/tableGraph'
interface TimeFormatOptions {
@ -26,8 +26,8 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
format: this.props.timeFormat || TIME_FORMAT_DEFAULT,
customFormat: false,
format: this.props.timeFormat || TIME_FORMAT_DEFAULT,
}
}
@ -35,7 +35,7 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
return this.props.onTimeFormatChange
}
handleChooseFormat = (formatOption: TimeFormatOptions) => {
public handleChooseFormat = (formatOption: TimeFormatOptions) => {
if (formatOption.text === TIME_FORMAT_CUSTOM) {
this.setState({customFormat: true})
} else {
@ -44,7 +44,7 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
}
}
render() {
public render() {
const {format, customFormat} = this.state
const {onTimeFormatChange} = this.props
const tipContent =
@ -57,21 +57,22 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
<div className="form-group col-xs-12">
<label>
Time Format
{customFormat &&
{customFormat && (
<QuestionMarkTooltip
tipID="Time Axis Format"
tipContent={tipContent}
/>}
/>
)}
</label>
<Dropdown
items={FORMAT_OPTIONS}
selected={showCustom ? TIME_FORMAT_CUSTOM : format}
buttonColor="btn-default"
buttonSize="btn-xs"
buttonSize="btn-sm"
className="dropdown-stretch"
onChoose={this.handleChooseFormat}
/>
{showCustom &&
{showCustom && (
<div className="column-controls--section">
<InputClickToEdit
wrapperClass="column-controls-input "
@ -81,7 +82,8 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
placeholder="Enter custom format..."
appearAsNormalInput={true}
/>
</div>}
</div>
)}
</div>
)
}

View File

@ -23,7 +23,7 @@ const GraphTypeSelector = ({type, handleChangeCellType}) => {
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">Visualization Type</h5>
<div className="viz-type-selector">
{GRAPH_TYPES.map(graphType =>
{GRAPH_TYPES.map(graphType => (
<div
key={graphType.type}
className={classnames('viz-type-selector--option', {
@ -32,12 +32,10 @@ const GraphTypeSelector = ({type, handleChangeCellType}) => {
>
<div onClick={onChangeCellType(graphType.type)}>
{graphType.graphic}
<p>
{graphType.menuOption}
</p>
<p>{graphType.menuOption}</p>
</div>
</div>
)}
))}
</div>
</div>
</FancyScrollbar>

View File

@ -15,7 +15,7 @@ const OverlayControls = ({
onSetQuerySource,
isDisplayOptionsTabActive,
onClickDisplayOptions,
}) =>
}) => (
<div className="overlay-controls">
<SourceSelector
sources={sources}
@ -51,6 +51,7 @@ const OverlayControls = ({
/>
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes

View File

@ -26,7 +26,7 @@ const QueryMaker = ({
activeQueryIndex,
initialGroupByTime,
setActiveQueryIndex,
}) =>
}) => (
<div className="query-maker query-maker--panel">
<QueryTabList
queries={queries}
@ -36,28 +36,31 @@ const QueryMaker = ({
activeQueryIndex={activeQueryIndex}
setActiveQueryIndex={setActiveQueryIndex}
/>
{activeQuery && activeQuery.id
? <div className="query-maker--tab-contents">
<QueryTextArea
query={buildText(activeQuery)}
config={activeQuery}
onUpdate={rawTextBinder(
source.links,
activeQuery.id,
actions.editRawTextAsync
)}
templates={templates}
/>
<SchemaExplorer
source={source}
actions={actions}
query={activeQuery}
onAddQuery={onAddQuery}
initialGroupByTime={initialGroupByTime}
/>
</div>
: <EmptyQuery onAddQuery={onAddQuery} />}
{activeQuery && activeQuery.id ? (
<div className="query-maker--tab-contents">
<QueryTextArea
query={buildText(activeQuery)}
config={activeQuery}
onUpdate={rawTextBinder(
source.links,
activeQuery.id,
actions.editRawTextAsync
)}
templates={templates}
/>
<SchemaExplorer
source={source}
actions={actions}
query={activeQuery}
onAddQuery={onAddQuery}
initialGroupByTime={initialGroupByTime}
/>
</div>
) : (
<EmptyQuery onAddQuery={onAddQuery} />
)}
</div>
)
const {arrayOf, func, number, shape, string} = PropTypes

View File

@ -215,15 +215,15 @@ class QueryTextArea extends Component {
<QueryStatus status={status} />
</div>
<div className="varmoji-back">
{isTemplating
? <TemplateDrawer
onClickTempVar={this.handleClickTempVar}
templates={filteredTemplates}
selected={selectedTemplate}
onMouseOverTempVar={this.handleMouseOverTempVar}
handleClickOutside={this.handleCloseDrawer}
/>
: null}
{isTemplating ? (
<TemplateDrawer
onClickTempVar={this.handleClickTempVar}
templates={filteredTemplates}
selected={selectedTemplate}
onMouseOverTempVar={this.handleMouseOverTempVar}
handleClickOutside={this.handleCloseDrawer}
/>
) : null}
</div>
</div>
</div>

View File

@ -3,20 +3,22 @@ import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
const SourceSelector = ({sources = [], selected, onSetQuerySource, queries}) =>
sources.length > 1 && queries.length
? <div className="source-selector">
<h3>Source:</h3>
<Dropdown
items={sources}
buttonSize="btn-sm"
menuClass="dropdown-astronaut"
useAutoComplete={true}
selected={selected}
onChoose={onSetQuerySource}
className="dropdown-240"
/>
</div>
: <div className="source-selector" />
sources.length > 1 && queries.length ? (
<div className="source-selector">
<h3>Source:</h3>
<Dropdown
items={sources}
buttonSize="btn-sm"
menuClass="dropdown-astronaut"
useAutoComplete={true}
selected={selected}
onChoose={onSetQuerySource}
className="dropdown-240"
/>
</div>
) : (
<div className="source-selector" />
)
const {array, arrayOf, func, shape, string} = PropTypes

View File

@ -2,23 +2,23 @@ import React from 'react'
import PropTypes from 'prop-types'
import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
export const Tabber = ({labelText, children, tipID, tipContent}) =>
export const Tabber = ({labelText, children, tipID, tipContent}) => (
<div className="form-group col-md-6">
<label>
{labelText}
{tipID
? <QuestionMarkTooltip tipID={tipID} tipContent={tipContent} />
: null}
{tipID ? (
<QuestionMarkTooltip tipID={tipID} tipContent={tipContent} />
) : null}
</label>
<ul className="nav nav-tablist nav-tablist-sm">
{children}
</ul>
<ul className="nav nav-tablist nav-tablist-sm">{children}</ul>
</div>
)
export const Tab = ({isActive, onClickTab, text}) =>
export const Tab = ({isActive, onClickTab, text}) => (
<li className={isActive ? 'active' : ''} onClick={onClickTab}>
{text}
</li>
)
const {bool, func, node, string} = PropTypes

View File

@ -4,39 +4,45 @@ import {bindActionCreators} from 'redux'
import _ from 'lodash'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import GraphOptionsTimeFormat from 'src/dashboards/components/GraphOptionsTimeFormat'
import GraphOptionsTimeAxis from 'src/dashboards/components/GraphOptionsTimeAxis'
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
import GraphOptionsFixFirstColumn from 'src/dashboards/components/GraphOptionsFixFirstColumn'
import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy'
import GraphOptionsTextWrapping from 'src/dashboards/components/GraphOptionsTextWrapping'
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
import GraphOptionsTimeAxis from 'src/dashboards/components/GraphOptionsTimeAxis'
import GraphOptionsTimeFormat from 'src/dashboards/components/GraphOptionsTimeFormat'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ThresholdsList from 'src/shared/components/ThresholdsList'
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph'
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph'
type TableColumn = {
interface Option {
text: string
key: string
}
interface TableColumn {
internalName: string
displayName: string
}
type Options = {
interface Options {
timeFormat: string
verticalTimeAxis: boolean
sortBy: TableColumn
wrapping: string
columnNames: TableColumn[]
fixFirstColumn: boolean
}
type QueryConfig = {
interface QueryConfig {
measurement: string
fields: [
{
alias: string
value: string
}
]
fields: Array<{
alias: string
value: string
}>
}
interface Props {
@ -51,44 +57,68 @@ export class TableOptions extends PureComponent<Props, {}> {
super(props)
}
componentWillMount() {
const {queryConfigs, handleUpdateTableOptions, tableOptions} = this.props
const {columnNames} = tableOptions
const timeColumn =
(columnNames && columnNames.find(c => c.internalName === 'time')) ||
TIME_COLUMN_DEFAULT
const columns = [
timeColumn,
..._.flatten(
queryConfigs.map(qc => {
const {measurement, fields} = qc
return fields.map(f => {
const internalName = `${measurement}.${f.alias}`
const existing = columnNames.find(
c => c.internalName === internalName
)
return existing || {internalName, displayName: ''}
})
})
),
]
handleUpdateTableOptions({...tableOptions, columnNames: columns})
get columnNames() {
const {tableOptions: {columnNames}} = this.props
return columnNames || []
}
handleChooseSortBy = () => {}
get timeColumn() {
return (
this.columnNames.find(c => c.internalName === 'time') ||
TIME_COLUMN_DEFAULT
)
}
handleTimeFormatChange = timeFormat => {
get computedColumnNames() {
const {queryConfigs} = this.props
const queryFields = _.flatten(
queryConfigs.map(({measurement, fields}) => {
return fields.map(({alias}) => {
const internalName = `${measurement}.${alias}`
const existing = this.columnNames.find(
c => c.internalName === internalName
)
return existing || {internalName, displayName: ''}
})
})
)
return [this.timeColumn, ...queryFields]
}
public componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({
...tableOptions,
columnNames: this.computedColumnNames,
})
}
public handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {displayName: option.text, internalName: option.key}
handleUpdateTableOptions({...tableOptions, sortBy})
}
public handleTimeFormatChange = timeFormat => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, timeFormat})
}
handleToggleTimeAxis = () => {}
public handleToggleVerticalTimeAxis = verticalTimeAxis => () => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, verticalTimeAxis})
}
handleToggleTextWrapping = () => {}
public handleToggleFixFirstColumn = () => {
const {handleUpdateTableOptions, tableOptions} = this.props
const fixFirstColumn = !tableOptions.fixFirstColumn
handleUpdateTableOptions({...tableOptions, fixFirstColumn})
}
handleColumnRename = column => {
public handleColumnRename = column => {
const {handleUpdateTableOptions, tableOptions} = this.props
const {columnNames} = tableOptions
const updatedColumns = columnNames.map(
@ -97,19 +127,24 @@ export class TableOptions extends PureComponent<Props, {}> {
handleUpdateTableOptions({...tableOptions, columnNames: updatedColumns})
}
render() {
public handleToggleTextWrapping = () => {}
public render() {
const {
tableOptions: {timeFormat, columnNames: columns},
tableOptions: {
timeFormat,
columnNames: columns,
verticalTimeAxis,
fixFirstColumn,
},
onResetFocus,
tableOptions,
} = this.props
const TimeAxis = 'vertical'
const tableSortByOptions = [
'cpu.mean_usage_system',
'cpu.mean_usage_idle',
'cpu.mean_usage_user',
].map(col => ({text: col}))
const tableSortByOptions = this.computedColumnNames.map(col => ({
key: col.internalName,
text: col.displayName || col.internalName,
}))
return (
<FancyScrollbar
@ -124,10 +159,11 @@ export class TableOptions extends PureComponent<Props, {}> {
onTimeFormatChange={this.handleTimeFormatChange}
/>
<GraphOptionsTimeAxis
TimeAxis={TimeAxis}
onToggleTimeAxis={this.handleToggleTimeAxis}
verticalTimeAxis={verticalTimeAxis}
onToggleVerticalTimeAxis={this.handleToggleVerticalTimeAxis}
/>
<GraphOptionsSortBy
selected={tableOptions.sortBy || TIME_COLUMN_DEFAULT}
sortByOptions={tableSortByOptions}
onChooseSortBy={this.handleChooseSortBy}
/>
@ -135,6 +171,10 @@ export class TableOptions extends PureComponent<Props, {}> {
thresholdsListType="background"
onToggleTextWrapping={this.handleToggleTextWrapping}
/>
<GraphOptionsFixFirstColumn
fixed={fixFirstColumn}
onToggleFixFirstColumn={this.handleToggleFixFirstColumn}
/>
</div>
<GraphOptionsCustomizeColumns
columns={columns}

View File

@ -16,67 +16,67 @@ const TemplateControlBar = ({
templates,
onSelectTemplate,
onOpenTemplateManager,
}) =>
}) => (
<div className={classnames('template-control-bar', {show: isOpen})}>
<div className="template-control--container">
<div className="template-control--controls">
{templates.length
? templates.map(({id, values, tempVar}) => {
const items = values.map(value => ({...value, text: value.value}))
const itemValues = values.map(value => value.value)
const selectedItem = items.find(item => item.selected) || items[0]
const selectedText = selectedItem && selectedItem.text
let customDropdownWidth = 0
if (itemValues.length > 1) {
const longest = itemValues.reduce(
(a, b) => (a.length > b.length ? a : b)
)
const longestLengthPixels =
calculateSize(longest, {
font: 'Monospace',
fontSize: '12px',
}).width + tempVarDropdownPadding
if (
longestLengthPixels > minTempVarDropdownWidth &&
longestLengthPixels < maxTempVarDropdownWidth
) {
customDropdownWidth = longestLengthPixels
} else if (longestLengthPixels > maxTempVarDropdownWidth) {
customDropdownWidth = maxTempVarDropdownWidth
}
}
// TODO: change Dropdown to a MultiSelectDropdown, `selected` to
// the full array, and [item] to all `selected` values when we update
// this component to support multiple values
return (
<div
key={id}
className="template-control--dropdown"
style={
customDropdownWidth > 0
? {minWidth: customDropdownWidth}
: null
}
>
<Dropdown
items={items}
buttonSize="btn-xs"
menuClass="dropdown-astronaut"
useAutoComplete={true}
selected={selectedText || '(No values)'}
onChoose={onSelectTemplate(id)}
/>
<label className="template-control--label">
{tempVar}
</label>
</div>
{templates.length ? (
templates.map(({id, values, tempVar}) => {
const items = values.map(value => ({...value, text: value.value}))
const itemValues = values.map(value => value.value)
const selectedItem = items.find(item => item.selected) || items[0]
const selectedText = selectedItem && selectedItem.text
let customDropdownWidth = 0
if (itemValues.length > 1) {
const longest = itemValues.reduce(
(a, b) => (a.length > b.length ? a : b)
)
})
: <div className="template-control--empty">
This dashboard does not have any Template Variables
</div>}
const longestLengthPixels =
calculateSize(longest, {
font: 'Monospace',
fontSize: '12px',
}).width + tempVarDropdownPadding
if (
longestLengthPixels > minTempVarDropdownWidth &&
longestLengthPixels < maxTempVarDropdownWidth
) {
customDropdownWidth = longestLengthPixels
} else if (longestLengthPixels > maxTempVarDropdownWidth) {
customDropdownWidth = maxTempVarDropdownWidth
}
}
// TODO: change Dropdown to a MultiSelectDropdown, `selected` to
// the full array, and [item] to all `selected` values when we update
// this component to support multiple values
return (
<div
key={id}
className="template-control--dropdown"
style={
customDropdownWidth > 0
? {minWidth: customDropdownWidth}
: null
}
>
<Dropdown
items={items}
buttonSize="btn-xs"
menuClass="dropdown-astronaut"
useAutoComplete={true}
selected={selectedText || '(No values)'}
onChoose={onSelectTemplate(id)}
/>
<label className="template-control--label">{tempVar}</label>
</div>
)
})
) : (
<div className="template-control--empty">
This dashboard does not have any Template Variables
</div>
)}
</div>
<Authorized requiredRole={EDITOR_ROLE}>
<button
@ -89,6 +89,7 @@ const TemplateControlBar = ({
</Authorized>
</div>
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes

View File

@ -78,17 +78,15 @@ class Threshold extends Component {
return (
<div className="threshold-item">
<div className={labelClass}>
{label}
</div>
{canBeDeleted
? <button
className="btn btn-default btn-sm btn-square"
onClick={onDeleteThreshold(threshold)}
>
<span className="icon remove" />
</button>
: null}
<div className={labelClass}>{label}</div>
{canBeDeleted ? (
<button
className="btn btn-default btn-sm btn-square"
onClick={onDeleteThreshold(threshold)}
>
<span className="icon remove" />
</button>
) : null}
<input
value={workingValue}
className={inputClass}

View File

@ -55,21 +55,23 @@ class VisualizationName extends Component {
return (
<div className="graph-heading">
{isEditing
? <input
type="text"
className="form-control input-sm"
defaultValue={name}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
autoFocus={true}
onFocus={this.handleFocus}
placeholder="Name this Cell..."
spellCheck={false}
/>
: <div className={graphNameClass} onClick={this.handleInputClick}>
{name}
</div>}
{isEditing ? (
<input
type="text"
className="form-control input-sm"
defaultValue={name}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
autoFocus={true}
onFocus={this.handleFocus}
placeholder="Name this Cell..."
spellCheck={false}
/>
) : (
<div className={graphNameClass} onClick={this.handleInputClick}>
{name}
</div>
)}
</div>
)
}

View File

@ -19,7 +19,7 @@ const TemplateVariableManager = ({
tempVarAlreadyExists,
onSaveTemplatesSuccess,
onEditTemplateVariables,
}) =>
}) => (
<div className="template-variable-manager">
<div className="template-variable-manager--header">
<div className="page-header__left">
@ -56,6 +56,7 @@ const TemplateVariableManager = ({
/>
</div>
</div>
)
class TemplateVariableManagerWrapper extends Component {
constructor(props) {

View File

@ -45,7 +45,7 @@ const TemplateVariableRow = ({
onSubmit,
onErrorThrown,
onDeleteTempVar,
}) =>
}) => (
<form
className={classnames('template-variable-manager--table-row', {
editing: isEditing,
@ -106,6 +106,7 @@ const TemplateVariableRow = ({
/>
</div>
</form>
)
class RowWrapper extends Component {
constructor(props) {

View File

@ -10,36 +10,39 @@ const TemplateVariableTable = ({
onRunQueryFailure,
onDelete,
tempVarAlreadyExists,
}) =>
}) => (
<div className="template-variable-manager--table">
{templates.length
? <div className="template-variable-manager--table-container">
<div className="template-variable-manager--table-heading">
<div className="tvm--col-1">Variable</div>
<div className="tvm--col-2">Type</div>
<div className="tvm--col-3">Definition / Values</div>
<div className="tvm--col-4" />
</div>
<div className="template-variable-manager--table-rows">
{templates.map(t =>
<TemplateVariableRow
key={t.id}
source={source}
template={t}
onRunQuerySuccess={onRunQuerySuccess}
onRunQueryFailure={onRunQueryFailure}
onDelete={onDelete}
tempVarAlreadyExists={tempVarAlreadyExists}
/>
)}
</div>
{templates.length ? (
<div className="template-variable-manager--table-container">
<div className="template-variable-manager--table-heading">
<div className="tvm--col-1">Variable</div>
<div className="tvm--col-2">Type</div>
<div className="tvm--col-3">Definition / Values</div>
<div className="tvm--col-4" />
</div>
: <div className="generic-empty-state">
<h4 style={{margin: '60px 0'}} className="no-user-select">
You have no Template Variables, why not create one?
</h4>
</div>}
<div className="template-variable-manager--table-rows">
{templates.map(t => (
<TemplateVariableRow
key={t.id}
source={source}
template={t}
onRunQuerySuccess={onRunQuerySuccess}
onRunQueryFailure={onRunQueryFailure}
onDelete={onDelete}
tempVarAlreadyExists={tempVarAlreadyExists}
/>
))}
</div>
</div>
) : (
<div className="generic-empty-state">
<h4 style={{margin: '60px 0'}} className="no-user-select">
You have no Template Variables, why not create one?
</h4>
</div>
)}
</div>
)
const {arrayOf, bool, func, shape, string} = PropTypes

View File

@ -8,26 +8,26 @@ const TableInput = ({
onStartEdit,
autoFocusTarget,
}) => {
return isEditing
? <div name={name} style={{width: '100%'}}>
<input
required={true}
name={name}
autoFocus={name === autoFocusTarget}
className="form-control input-sm tvm-input-edit"
type="text"
defaultValue={
name === 'tempVar'
? defaultValue.replace(/\u003a/g, '') // remove ':'s
: defaultValue
}
/>
</div>
: <div style={{width: '100%'}} onClick={onStartEdit(name)}>
<div className="tvm-input">
{defaultValue}
</div>
</div>
return isEditing ? (
<div name={name} style={{width: '100%'}}>
<input
required={true}
name={name}
autoFocus={name === autoFocusTarget}
className="form-control input-sm tvm-input-edit"
type="text"
defaultValue={
name === 'tempVar'
? defaultValue.replace(/\u003a/g, '') // remove ':'s
: defaultValue
}
/>
</div>
) : (
<div style={{width: '100%'}} onClick={onStartEdit(name)}>
<div className="tvm-input">{defaultValue}</div>
</div>
)
}
const {bool, func, string} = PropTypes

View File

@ -46,15 +46,17 @@ const TemplateQueryBuilder = ({
onErrorThrown={onErrorThrown}
/>
<span className="tvm-query-builder--text">FROM</span>
{selectedDatabase
? <MeasurementDropdown
database={selectedDatabase}
measurement={selectedMeasurement}
onSelectMeasurement={onSelectMeasurement}
onStartEdit={onStartEdit}
onErrorThrown={onErrorThrown}
/>
: <div>No database selected</div>}
{selectedDatabase ? (
<MeasurementDropdown
database={selectedDatabase}
measurement={selectedMeasurement}
onSelectMeasurement={onSelectMeasurement}
onStartEdit={onStartEdit}
onErrorThrown={onErrorThrown}
/>
) : (
<div>No database selected</div>
)}
</div>
)
case 'tagValues':
@ -68,26 +70,30 @@ const TemplateQueryBuilder = ({
onErrorThrown={onErrorThrown}
/>
<span className="tvm-query-builder--text">FROM</span>
{selectedDatabase
? <MeasurementDropdown
database={selectedDatabase}
measurement={selectedMeasurement}
onSelectMeasurement={onSelectMeasurement}
onStartEdit={onStartEdit}
onErrorThrown={onErrorThrown}
/>
: 'Pick a DB'}
{selectedDatabase ? (
<MeasurementDropdown
database={selectedDatabase}
measurement={selectedMeasurement}
onSelectMeasurement={onSelectMeasurement}
onStartEdit={onStartEdit}
onErrorThrown={onErrorThrown}
/>
) : (
'Pick a DB'
)}
<span className="tvm-query-builder--text">WITH KEY =</span>
{selectedMeasurement
? <TagKeyDropdown
database={selectedDatabase}
measurement={selectedMeasurement}
tagKey={selectedTagKey}
onSelectTagKey={onSelectTagKey}
onStartEdit={onStartEdit}
onErrorThrown={onErrorThrown}
/>
: 'Pick a Tag Key'}
{selectedMeasurement ? (
<TagKeyDropdown
database={selectedDatabase}
measurement={selectedMeasurement}
tagKey={selectedTagKey}
onSelectTagKey={onSelectTagKey}
onStartEdit={onStartEdit}
onErrorThrown={onErrorThrown}
/>
) : (
'Pick a Tag Key'
)}
</div>
)
default:

View File

@ -1,3 +1,5 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
export const EMPTY_DASHBOARD = {
id: 0,
name: '',
@ -20,6 +22,7 @@ export const NEW_DEFAULT_DASHBOARD_CELL = {
name: 'Untitled Cell',
type: 'line',
queries: [],
tableOptions: DEFAULT_TABLE_OPTIONS,
}
export const NEW_DASHBOARD = {

View File

@ -356,35 +356,35 @@ class DashboardPage extends Component {
return (
<div className="page">
{isTemplating
? <OverlayTechnologies>
<TemplateVariableManager
source={source}
templates={dashboard.templates}
onClose={this.handleCloseTemplateManager}
onRunQueryFailure={this.handleRunQueryFailure}
onEditTemplateVariables={this.handleEditTemplateVariables}
/>
</OverlayTechnologies>
: null}
{selectedCell
? <CellEditorOverlay
{isTemplating ? (
<OverlayTechnologies>
<TemplateVariableManager
source={source}
sources={sources}
cell={selectedCell}
timeRange={timeRange}
autoRefresh={autoRefresh}
dashboardID={dashboardID}
queryStatus={cellQueryStatus}
onSave={this.handleSaveEditedCell}
onCancel={handleHideCellEditorOverlay}
templates={templatesIncludingDashTime}
editQueryStatus={dashboardActions.editCellQueryStatus}
thresholdsListType={thresholdsListType}
thresholdsListColors={thresholdsListColors}
gaugeColors={gaugeColors}
templates={dashboard.templates}
onClose={this.handleCloseTemplateManager}
onRunQueryFailure={this.handleRunQueryFailure}
onEditTemplateVariables={this.handleEditTemplateVariables}
/>
: null}
</OverlayTechnologies>
) : null}
{selectedCell ? (
<CellEditorOverlay
source={source}
sources={sources}
cell={selectedCell}
timeRange={timeRange}
autoRefresh={autoRefresh}
dashboardID={dashboardID}
queryStatus={cellQueryStatus}
onSave={this.handleSaveEditedCell}
onCancel={handleHideCellEditorOverlay}
templates={templatesIncludingDashTime}
editQueryStatus={dashboardActions.editCellQueryStatus}
thresholdsListType={thresholdsListType}
thresholdsListColors={thresholdsListColors}
gaugeColors={gaugeColors}
/>
) : null}
<DashboardHeader
names={names}
sourceID={sourceID}
@ -407,30 +407,30 @@ class DashboardPage extends Component {
onToggleTempVarControls={this.handleToggleTempVarControls}
handleClickPresentationButton={handleClickPresentationButton}
/>
{dashboard
? <Dashboard
source={source}
sources={sources}
setScrollTop={this.setScrollTop}
inView={this.inView}
dashboard={dashboard}
timeRange={timeRange}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
onZoom={this.handleZoomedTimeRange}
onAddCell={this.handleAddCell}
hoverTime={hoverTime}
onSetHoverTime={this.handleSetHoverTime}
inPresentationMode={inPresentationMode}
onPositionChange={this.handleUpdatePosition}
onSelectTemplate={this.handleSelectTemplate}
onDeleteCell={this.handleDeleteDashboardCell}
showTemplateControlBar={showTemplateControlBar}
onOpenTemplateManager={this.handleOpenTemplateManager}
templatesIncludingDashTime={templatesIncludingDashTime}
onSummonOverlayTechnologies={handleShowCellEditorOverlay}
/>
: null}
{dashboard ? (
<Dashboard
source={source}
sources={sources}
setScrollTop={this.setScrollTop}
inView={this.inView}
dashboard={dashboard}
timeRange={timeRange}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
onZoom={this.handleZoomedTimeRange}
onAddCell={this.handleAddCell}
hoverTime={hoverTime}
onSetHoverTime={this.handleSetHoverTime}
inPresentationMode={inPresentationMode}
onPositionChange={this.handleUpdatePosition}
onSelectTemplate={this.handleSelectTemplate}
onDeleteCell={this.handleDeleteDashboardCell}
showTemplateControlBar={showTemplateControlBar}
onOpenTemplateManager={this.handleOpenTemplateManager}
templatesIncludingDashTime={templatesIncludingDashTime}
onSummonOverlayTechnologies={handleShowCellEditorOverlay}
/>
) : null}
</div>
)
}

View File

@ -1,3 +1,5 @@
import _ from 'lodash'
import {
THRESHOLD_TYPE_TEXT,
DEFAULT_THRESHOLDS_LIST_COLORS,
@ -28,7 +30,11 @@ export default function cellEditorOverlay(state = initialState, action) {
)
const gaugeColors = validateGaugeColors(colors)
const tableOptions = cell.tableOptions || initializeOptions('table')
const tableOptions = _.get(
cell,
'tableOptions',
initializeOptions('table')
)
return {
...state,

View File

@ -1,4 +1,5 @@
import React, {SFC} from 'react'
import moment from 'moment'
export interface CustomCellProps {
@ -10,18 +11,10 @@ const CustomCell: SFC<CustomCellProps> = ({data, columnName}) => {
if (columnName === 'time') {
const date = moment(new Date(data)).format('MM/DD/YY hh:mm:ssA')
return (
<span>
{date}
</span>
)
return <span>{date}</span>
}
return (
<span>
{data}
</span>
)
return <span>{data}</span>
}
export default CustomCell

View File

@ -90,27 +90,27 @@ class FieldListItem extends Component {
<div className="query-builder--checkbox" />
{fieldName}
</span>
{isSelected
? <div
className={classnames('btn btn-xs', {
active: isOpen,
'btn-default': !num,
'btn-primary': num,
})}
onClick={this.toggleFunctionsMenu}
data-test={`query-builder-list-item-function-${fieldName}`}
>
{fieldFuncsLabel}
</div>
: null}
{isSelected ? (
<div
className={classnames('btn btn-xs', {
active: isOpen,
'btn-default': !num,
'btn-primary': num,
})}
onClick={this.toggleFunctionsMenu}
data-test={`query-builder-list-item-function-${fieldName}`}
>
{fieldFuncsLabel}
</div>
) : null}
</div>
{isSelected && isOpen
? <FunctionSelector
onApply={this.handleApplyFunctions}
selectedItems={funcs}
singleSelect={isKapacitorRule}
/>
: null}
{isSelected && isOpen ? (
<FunctionSelector
onApply={this.handleApplyFunctions}
selectedItems={funcs}
singleSelect={isKapacitorRule}
/>
) : null}
</div>
)
}

View File

@ -19,7 +19,7 @@ const GroupByTimeDropdown = ({
selected,
onChooseGroupByTime,
location: {pathname},
}) =>
}) => (
<div className="group-by-time">
<label className="group-by-time--label">Group by:</label>
<Dropdown
@ -34,6 +34,7 @@ const GroupByTimeDropdown = ({
selected={selected || 'Time'}
/>
</div>
)
const {func, string, shape} = PropTypes

View File

@ -8,12 +8,8 @@ const NoDataNodeError = React.createClass({
render() {
return (
<ClusterError>
<PanelHeading>
{errorCopy.noData.head}
</PanelHeading>
<PanelBody>
{errorCopy.noData.body}
</PanelBody>
<PanelHeading>{errorCopy.noData.head}</PanelHeading>
<PanelBody>{errorCopy.noData.body}</PanelBody>
</ClusterError>
)
},

View File

@ -14,7 +14,7 @@ const QueryMaker = ({
timeRange,
activeQuery,
initialGroupByTime,
}) =>
}) => (
<div className="query-maker query-maker--panel">
<div className="query-maker--tab-contents">
<QueryEditor
@ -33,6 +33,7 @@ const QueryMaker = ({
/>
</div>
</div>
)
const {func, shape, string} = PropTypes

View File

@ -31,9 +31,7 @@ const QueryMakerTab = React.createClass({
})}
onClick={this.handleSelect}
>
<label>
{this.props.queryTabText}
</label>
<label>{this.props.queryTabText}</label>
<span
className="query-maker--delete"
onClick={this.handleDelete}

View File

@ -134,60 +134,60 @@ class ChronoTable extends Component {
return (
<div style={{width: '100%', height: '100%', position: 'relative'}}>
{series.length < maximumTabsCount
? <div className="table--tabs">
{series.map((s, i) =>
<TabItem
isActive={i === activeSeriesIndex}
key={i}
name={this.makeTabName(s)}
index={i}
onClickTab={this.handleClickTab}
/>
)}
</div>
: <Dropdown
className="dropdown-160 table--tabs-dropdown"
items={series.map((s, index) => ({
...s,
text: this.makeTabName(s),
index,
}))}
onChoose={this.handleClickDropdown}
selected={this.makeTabName(series[activeSeriesIndex])}
buttonSize="btn-xs"
/>}
{series.length < maximumTabsCount ? (
<div className="table--tabs">
{series.map((s, i) => (
<TabItem
isActive={i === activeSeriesIndex}
key={i}
name={this.makeTabName(s)}
index={i}
onClickTab={this.handleClickTab}
/>
))}
</div>
) : (
<Dropdown
className="dropdown-160 table--tabs-dropdown"
items={series.map((s, index) => ({
...s,
text: this.makeTabName(s),
index,
}))}
onChoose={this.handleClickDropdown}
selected={this.makeTabName(series[activeSeriesIndex])}
buttonSize="btn-xs"
/>
)}
<div className="table--tabs-content">
{(columns && !columns.length) || (values && !values.length)
? <div className="generic-empty-state">This series is empty</div>
: <Table
onColumnResizeEndCallback={this.handleColumnResize}
isColumnResizing={false}
rowHeight={rowHeight}
rowsCount={values.length}
width={containerWidth}
ownerHeight={styleAdjustedHeight}
height={styleAdjustedHeight}
headerHeight={headerHeight}
>
{columns.map((columnName, colIndex) => {
return (
<Column
isResizable={true}
key={columnName}
columnKey={columnName}
header={
<Cell>
{columnName}
</Cell>
}
cell={this.handleCustomCell(columnName, values, colIndex)}
width={columnWidths[columnName] || width}
minWidth={minWidth}
/>
)
})}
</Table>}
{(columns && !columns.length) || (values && !values.length) ? (
<div className="generic-empty-state">This series is empty</div>
) : (
<Table
onColumnResizeEndCallback={this.handleColumnResize}
isColumnResizing={false}
rowHeight={rowHeight}
rowsCount={values.length}
width={containerWidth}
ownerHeight={styleAdjustedHeight}
height={styleAdjustedHeight}
headerHeight={headerHeight}
>
{columns.map((columnName, colIndex) => {
return (
<Column
isResizable={true}
key={columnName}
columnKey={columnName}
header={<Cell>{columnName}</Cell>}
cell={this.handleCustomCell(columnName, values, colIndex)}
width={columnWidths[columnName] || width}
minWidth={minWidth}
/>
)
})}
</Table>
)}
</div>
</div>
)

View File

@ -2,13 +2,14 @@ import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const TableTabItem = ({name, index, onClickTab, isActive}) =>
const TableTabItem = ({name, index, onClickTab, isActive}) => (
<div
className={classnames('table--tab', {active: isActive})}
onClick={onClickTab(index)}
>
{name}
</div>
)
const {bool, func, number, string} = PropTypes

View File

@ -27,32 +27,33 @@ const getCSV = (query, errorThrown) => async () => {
}
}
const VisHeader = ({views, view, onToggleView, query, errorThrown}) =>
const VisHeader = ({views, view, onToggleView, query, errorThrown}) => (
<div className="graph-heading">
{views.length
? <ul className="nav nav-tablist nav-tablist-sm">
{views.map(v =>
<li
key={v}
onClick={onToggleView(v)}
className={classnames({active: view === v})}
data-test={`data-${v}`}
>
{_.upperFirst(v)}
</li>
)}
</ul>
: null}
{query
? <div
className="btn btn-sm btn-default dlcsv"
onClick={getCSV(query, errorThrown)}
>
<span className="icon download dlcsv" />
.csv
</div>
: null}
{views.length ? (
<ul className="nav nav-tablist nav-tablist-sm">
{views.map(v => (
<li
key={v}
onClick={onToggleView(v)}
className={classnames({active: view === v})}
data-test={`data-${v}`}
>
{_.upperFirst(v)}
</li>
))}
</ul>
) : null}
{query ? (
<div
className="btn btn-sm btn-default dlcsv"
onClick={getCSV(query, errorThrown)}
>
<span className="icon download dlcsv" />
.csv
</div>
) : null}
</div>
)
const {arrayOf, func, shape, string} = PropTypes

View File

@ -15,70 +15,75 @@ const WriteDataBody = ({
fileInput,
handleFileOpen,
isUploading,
}) =>
}) => (
<div className="write-data-form--body">
{isManual
? <textarea
className="form-control write-data-form--input"
autoComplete="off"
spellCheck="false"
placeholder="<measurement>,<tag_key>=<tag_value> <field_key>=<field_value>"
onKeyUp={handleKeyUp}
onChange={handleEdit}
autoFocus={true}
data-test="manual-entry-field"
/>
: <div
{isManual ? (
<textarea
className="form-control write-data-form--input"
autoComplete="off"
spellCheck="false"
placeholder="<measurement>,<tag_key>=<tag_value> <field_key>=<field_value>"
onKeyUp={handleKeyUp}
onChange={handleEdit}
autoFocus={true}
data-test="manual-entry-field"
/>
) : (
<div
className={
uploadContent
? 'write-data-form--file'
: 'write-data-form--file write-data-form--file_active'
}
onClick={handleFileOpen}
>
{uploadContent ? (
<h3 className="write-data-form--filepath_selected">{fileName}</h3>
) : (
<h3 className="write-data-form--filepath_empty">
Drop a file here or click to upload
</h3>
)}
<div
className={
uploadContent
? 'write-data-form--file'
: 'write-data-form--file write-data-form--file_active'
? 'write-data-form--graphic write-data-form--graphic_success'
: 'write-data-form--graphic'
}
onClick={handleFileOpen}
>
{uploadContent
? <h3 className="write-data-form--filepath_selected">
{fileName}
</h3>
: <h3 className="write-data-form--filepath_empty">
Drop a file here or click to upload
</h3>}
<div
className={
uploadContent
? 'write-data-form--graphic write-data-form--graphic_success'
: 'write-data-form--graphic'
}
/>
<input
type="file"
onChange={handleFile(false)}
className="write-data-form--upload"
ref={fileInput}
accept="text/*, application/gzip"
/>
{uploadContent &&
<span className="write-data-form--file-submit">
<button className="btn btn-md btn-success" onClick={handleSubmit}>
Write this File
</button>
<button
className="btn btn-md btn-default"
onClick={handleCancelFile}
>
Cancel
</button>
</span>}
</div>}
{isManual &&
/>
<input
type="file"
onChange={handleFile(false)}
className="write-data-form--upload"
ref={fileInput}
accept="text/*, application/gzip"
/>
{uploadContent && (
<span className="write-data-form--file-submit">
<button className="btn btn-md btn-success" onClick={handleSubmit}>
Write this File
</button>
<button
className="btn btn-md btn-default"
onClick={handleCancelFile}
>
Cancel
</button>
</span>
)}
</div>
)}
{isManual && (
<WriteDataFooter
isUploading={isUploading}
isManual={isManual}
inputContent={inputContent}
handleSubmit={handleSubmit}
uploadContent={uploadContent}
/>}
/>
)}
</div>
)
const {func, string, bool} = PropTypes

View File

@ -10,26 +10,28 @@ const WriteDataFooter = ({
uploadContent,
handleSubmit,
isUploading,
}) =>
}) => (
<div className="write-data-form--footer">
{isManual
? <span className="write-data-form--helper">
Need help writing InfluxDB Line Protocol? -&nbsp;
<a
href="https://docs.influxdata.com/influxdb/latest/write_protocols/line_protocol_tutorial/"
target="_blank"
>
See Documentation
</a>
</span>
: <span className="write-data-form--helper">
<a
href="https://docs.influxdata.com/influxdb/v1.2//tools/shell/#import-data-from-a-file-with-import"
target="_blank"
>
File Upload Documentation
</a>
</span>}
{isManual ? (
<span className="write-data-form--helper">
Need help writing InfluxDB Line Protocol? -&nbsp;
<a
href="https://docs.influxdata.com/influxdb/latest/write_protocols/line_protocol_tutorial/"
target="_blank"
>
See Documentation
</a>
</span>
) : (
<span className="write-data-form--helper">
<a
href="https://docs.influxdata.com/influxdb/v1.2//tools/shell/#import-data-from-a-file-with-import"
target="_blank"
>
File Upload Documentation
</a>
</span>
)}
<button
className={isUploading ? `${submitButton} ${spinner}` : submitButton}
onClick={handleSubmit}
@ -43,6 +45,7 @@ const WriteDataFooter = ({
Write
</button>
</div>
)
const {bool, func, string} = PropTypes

View File

@ -9,7 +9,7 @@ const WriteDataHeader = ({
toggleWriteView,
isManual,
onClose,
}) =>
}) => (
<div className="write-data-form--header">
<div className="page-header__left">
<h1 className="page-header__title">Write Data To</h1>
@ -38,6 +38,7 @@ const WriteDataHeader = ({
<span className="page-header__dismiss" onClick={onClose} />
</div>
</div>
)
const {func, string, bool} = PropTypes

View File

@ -95,17 +95,17 @@ class DataExplorer extends Component {
const selectedDatabase = _.get(queryConfigs, ['0', 'database'], null)
return (
<div className="data-explorer">
{showWriteForm
? <OverlayTechnologies>
<WriteDataForm
source={source}
errorThrown={errorThrownAction}
selectedDatabase={selectedDatabase}
onClose={this.handleCloseWriteData}
writeLineProtocol={writeLineProtocol}
/>
</OverlayTechnologies>
: null}
{showWriteForm ? (
<OverlayTechnologies>
<WriteDataForm
source={source}
errorThrown={errorThrownAction}
selectedDatabase={selectedDatabase}
onClose={this.handleCloseWriteData}
writeLineProtocol={writeLineProtocol}
/>
</OverlayTechnologies>
) : null}
<Header
timeRange={timeRange}
autoRefresh={autoRefresh}

View File

@ -16,7 +16,7 @@ const Header = ({
onManualRefresh,
onChooseTimeRange,
onChooseAutoRefresh,
}) =>
}) => (
<div className="page-header full-width">
<div className="page-header__container">
<div className="page-header__left">
@ -47,6 +47,7 @@ const Header = ({
</div>
</div>
</div>
)
Header.propTypes = {
onChooseAutoRefresh: func.isRequired,

View File

@ -37,7 +37,10 @@ const download = (data, strFileName, strMimeType) => {
if (url && url.length < 2048) {
// if no filename and no mime, assume a url was passed as the only argument
fileName = url.split('/').pop().split('?')[0]
fileName = url
.split('/')
.pop()
.split('?')[0]
anchor.href = url // assign href prop to temp anchor
if (anchor.href.indexOf(url) !== -1) {
// if the browser determines that it's a potentially valid url path:

View File

@ -23,9 +23,7 @@ class HostRow extends Component {
return (
<div className="hosts-table--tr">
<div className="hosts-table--td" style={{width: colName}}>
<Link to={`/sources/${source.id}/hosts/${name}`}>
{name}
</Link>
<Link to={`/sources/${source.id}/hosts/${name}`}>{name}</Link>
</div>
<div className="hosts-table--td" style={{width: colStatus}}>
<div

View File

@ -102,61 +102,61 @@ class HostsTable extends Component {
return (
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">
{hostsTitle}
</h2>
<h2 className="panel-title">{hostsTitle}</h2>
<SearchBar
placeholder="Filter by Host..."
onSearch={this.updateSearchTerm}
/>
</div>
<div className="panel-body">
{hostCount > 0 && !hostsError.length
? <div className="hosts-table">
<div className="hosts-table--thead">
<div className="hosts-table--tr">
<div
onClick={this.updateSort('name')}
className={this.sortableClasses('name')}
style={{width: colName}}
>
Host
</div>
<div
onClick={this.updateSort('deltaUptime')}
className={this.sortableClasses('deltaUptime')}
style={{width: colStatus}}
>
Status
</div>
<div
onClick={this.updateSort('cpu')}
className={this.sortableClasses('cpu')}
style={{width: colCPU}}
>
CPU
</div>
<div
onClick={this.updateSort('load')}
className={this.sortableClasses('load')}
style={{width: colLoad}}
>
Load
</div>
<div className="hosts-table--th">Apps</div>
{hostCount > 0 && !hostsError.length ? (
<div className="hosts-table">
<div className="hosts-table--thead">
<div className="hosts-table--tr">
<div
onClick={this.updateSort('name')}
className={this.sortableClasses('name')}
style={{width: colName}}
>
Host
</div>
<div
onClick={this.updateSort('deltaUptime')}
className={this.sortableClasses('deltaUptime')}
style={{width: colStatus}}
>
Status
</div>
<div
onClick={this.updateSort('cpu')}
className={this.sortableClasses('cpu')}
style={{width: colCPU}}
>
CPU
</div>
<div
onClick={this.updateSort('load')}
className={this.sortableClasses('load')}
style={{width: colLoad}}
>
Load
</div>
<div className="hosts-table--th">Apps</div>
</div>
<InfiniteScroll
items={sortedHosts.map(h =>
<HostRow key={h.name} host={h} source={source} />
)}
itemHeight={26}
className="hosts-table--tbody"
/>
</div>
: <div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>No Hosts found</h4>
</div>}
<InfiniteScroll
items={sortedHosts.map(h => (
<HostRow key={h.name} host={h} source={source} />
))}
itemHeight={26}
className="hosts-table--tbody"
/>
</div>
) : (
<div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>No Hosts found</h4>
</div>
)}
</div>
</div>
)

View File

@ -96,7 +96,7 @@ class HostPage extends Component {
const autoflowCells = autoflowLayouts.reduce((allCells, layout) => {
return allCells.concat(
layout.cells.map(cell => {
const x = cellCount * cellWidth % pageWidth
const x = (cellCount * cellWidth) % pageWidth
const y = Math.floor(cellCount * cellWidth / pageWidth) * cellHeight
cellCount += 1
return Object.assign(cell, {

Some files were not shown because too many files have changed in this diff Show More