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

View File

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

View File

@ -550,7 +550,8 @@ func TestServer(t *testing.T) {
"internalName": "", "internalName": "",
"displayName": ""}, "displayName": ""},
"wrapping": "", "wrapping": "",
"columnNames": null "columnNames": null,
"fixFirstColumn": false
}, },
"links": { "links": {
"self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093" "self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093"
@ -799,7 +800,8 @@ func TestServer(t *testing.T) {
"displayName":"" "displayName":""
}, },
"wrapping":"", "wrapping":"",
"columnNames":null "columnNames":null,
"fixFirstColumn":false
}, },
"legend":{ "legend":{
"type": "static", "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", "parser": "babel-eslint",
plugins: [ "plugins": [
'react', "react",
'prettier', "prettier",
'babel', "babel",
'jest', "jest"
], ],
extends: [ "extends": [
"prettier", "prettier",
"prettier/react" "prettier/react"
], ],
env: { "env": {
browser: true, "browser": true,
mocha: true, "mocha": true,
"jest": true, "jest": true,
node: true, "node": true,
es6: true, "es6": true
}, },
globals: { "globals": {
expect: true, "expect": true
}, },
parserOptions: { "parserOptions": {
ecmaFeatures: { "ecmaFeatures": {
arrowFunctions: true, "arrowFunctions": true,
binaryLiterals: true, "binaryLiterals": true,
blockBindings: true, "blockBindings": true,
classes: true, "classes": true,
defaultParams: false, "defaultParams": false,
destructuring: true, "destructuring": true,
forOf: false, "forOf": false,
generators: false, "generators": false,
modules: true, "modules": true,
objectLiteralComputedProperties: true, "objectLiteralComputedProperties": true,
objectLiteralDuplicateProperties: false, "objectLiteralDuplicateProperties": false,
objectLiteralShorthandMethods: true, "objectLiteralShorthandMethods": true,
objectLiteralShorthandProperties: true, "objectLiteralShorthandProperties": true,
octalLiterals: false, "octalLiterals": false,
regexUFlag: false, "regexUFlag": false,
regexYFlag: false, "regexYFlag": false,
restParams: true, "restParams": true,
spread: true, "spread": true,
superInFunctions: false, "superInFunctions": false,
templateStrings: true, "templateStrings": true,
unicodeCodePointEscapes: false, "unicodeCodePointEscapes": false,
globalReturn: false, "globalReturn": false,
jsx: true, "jsx": true
}, }
}, },
rules: { "rules": {
'quotes': [1, 'single'], "func-style": 0,
'func-style': 0, "func-names": 0,
'func-names': 0, "arrow-parens": 0,
'arrow-parens': 0, "no-cond-assign": 2,
'comma-dangle': [2, 'always-multiline'], "no-console": [
'no-cond-assign': 2, "error",
'no-console': ['error', {allow: ['error', 'warn']}], {
'no-constant-condition': 2, "allow": [
'no-control-regex': 2, "error",
'no-debugger': 2, "warn"
'no-dupe-args': 2, ]
'no-dupe-keys': 2, }
'no-duplicate-case': 2, ],
'no-empty-character-class': 2, "no-constant-condition": 2,
'no-empty': 2, "no-control-regex": 2,
'no-ex-assign': 2, "no-debugger": 2,
'no-extra-boolean-cast': 2, "no-dupe-args": 2,
'no-extra-parens': 0, "no-dupe-keys": 2,
'no-extra-semi': 2, "no-duplicate-case": 2,
'no-func-assign': 2, "no-empty-character-class": 2,
'no-inner-declarations': [2, 'both'], "no-empty": 2,
'no-invalid-regexp': 2, "no-ex-assign": 2,
'no-irregular-whitespace': 2, "no-extra-boolean-cast": 2,
'no-negated-in-lhs': 2, "no-extra-parens": 0,
'no-obj-calls': 2, "no-func-assign": 2,
'no-regex-spaces': 2, "no-inner-declarations": [
'no-sparse-arrays': 2, 2,
'no-unexpected-multiline': 2, "both"
'no-unreachable': 2, ],
'use-isnan': 2, "no-invalid-regexp": 2,
'valid-jsdoc': 0, "no-irregular-whitespace": 2,
'valid-typeof': 2, "no-negated-in-lhs": 2,
"no-obj-calls": 2,
'accessor-pairs': 2, "no-regex-spaces": 2,
'block-scoped-var': 2, "no-sparse-arrays": 2,
'complexity': 0, // TODO: revisit "no-unreachable": 2,
'consistent-return': 0, "use-isnan": 2,
'curly': 2, "valid-jsdoc": 0,
'default-case': 0, // TODO: revisit "valid-typeof": 2,
'dot-location': [2, 'property'], "accessor-pairs": 2,
'dot-notation': 2, "block-scoped-var": 2,
'eqeqeq': 2, "complexity": 0,
'no-alert': 2, "consistent-return": 0,
'no-caller': 2, "curly": 2,
'no-case-declarations': 2, "default-case": 0,
'no-div-regex': 2, "dot-notation": 2,
'no-else-return': 2, "eqeqeq": 2,
'no-labels': 2, "no-alert": 2,
'no-empty-pattern': 2, "no-caller": 2,
'no-eq-null': 2, "no-case-declarations": 2,
'no-eval': 2, "no-div-regex": 2,
'no-extend-native': 2, "no-else-return": 2,
'no-extra-bind': 2, "no-labels": 2,
'no-fallthrough': 2, "no-empty-pattern": 2,
'no-floating-decimal': 2, "no-eq-null": 2,
'no-implicit-coercion': 0, "no-eval": 2,
'no-implied-eval': 2, "no-extend-native": 2,
'no-iterator': 2, "no-extra-bind": 2,
'no-lone-blocks': 2, "no-fallthrough": 2,
'no-loop-func': 2, "no-implicit-coercion": 0,
'no-magic-numbers': [0, {ignore: [-1, 0, 1, 2]}], "no-implied-eval": 2,
'no-multi-spaces': 2, "no-iterator": 2,
'no-multi-str': 2, "no-lone-blocks": 2,
'no-native-reassign': 2, "no-loop-func": 2,
'no-new-func': 2, "no-magic-numbers": [
'no-new-wrappers': 2, 0,
'no-new': 2, {
'no-octal-escape': 2, "ignore": [
'no-octal': 2, -1,
'no-proto': 2, 0,
'no-redeclare': 2, 1,
'no-script-url': 2, 2
'no-self-compare': 2, ]
'no-sequences': 2, }
'no-throw-literal': 2, ],
'no-unused-expressions': 2, "no-multi-str": 2,
'no-useless-call': 2, "no-native-reassign": 2,
'no-useless-concat': 2, "no-new-func": 2,
'no-void': 2, "no-new-wrappers": 2,
'no-warning-comments': 0, "no-new": 2,
'no-with': 2, "no-octal-escape": 2,
'radix': 2, "no-octal": 2,
'vars-on-top': 2, "no-proto": 2,
"no-redeclare": 2,
'strict': [2, 'never'], "no-script-url": 2,
"no-self-compare": 2,
'init-declarations': 0, "no-sequences": 2,
'no-catch-shadow': 2, "no-throw-literal": 2,
'no-delete-var': 2, "no-unused-expressions": 2,
'no-label-var': 2, "no-useless-call": 2,
'no-shadow-restricted-names': 2, "no-useless-concat": 2,
'no-shadow': 2, "no-void": 2,
'no-undef-init': 2, "no-warning-comments": 0,
'no-undef': 2, "no-with": 2,
'no-unused-vars': [2, {args: 'after-used', 'argsIgnorePattern': '^_'}], "radix": 2,
'no-use-before-define': [2, 'nofunc'], "vars-on-top": 2,
"strict": [
'array-bracket-spacing': [2, 'never'], 2,
'block-spacing': [2, 'always'], "never"
'brace-style': [2, '1tbs'], ],
'camelcase': [2, {properties: 'never'}], "init-declarations": 0,
'comma-spacing': [2, {before: false, after: true}], "no-catch-shadow": 2,
'comma-style': [2, 'last'], "no-delete-var": 2,
'computed-property-spacing': [2, 'never'], "no-label-var": 2,
'consistent-this': [2, 'self'], "no-shadow-restricted-names": 2,
'eol-last': 0, // TODO: revisit "no-shadow": 2,
'id-length': 0, "no-undef-init": 2,
'id-match': 0, "no-undef": 2,
'indent': [0, 2, {SwitchCase: 1}], "no-unused-vars": [
'key-spacing': [2, {beforeColon: false, afterColon: true}], 2,
'linebreak-style': [2, 'unix'], {
'lines-around-comment': 0, "args": "after-used",
'max-depth': 0, "argsIgnorePattern": "^_"
'max-len': 0, }
'max-nested-callbacks': 0, ],
'max-params': 0, "no-use-before-define": [
'max-statements': 0, 2,
'new-cap': 0, "nofunc"
'new-parens': 2, ],
'newline-after-var': 0, "camelcase": [
'no-array-constructor': 2, 2,
'no-negated-condition': 2, {
'no-inline-comments': 0, "properties": "never"
'no-lonely-if': 2, }
'no-mixed-spaces-and-tabs': 2, ],
'no-multiple-empty-lines': 2, "consistent-this": [
'no-nested-ternary': 2, 2,
'no-new-object': 2, "self"
'no-plusplus': [2, {allowForLoopAfterthoughts: true}], ],
'no-spaced-func': 2, "eol-last": 0,
'no-ternary': 0, "id-length": 0,
'no-trailing-spaces': 2, "id-match": 0,
'no-underscore-dangle': 0, "indent": [
'no-unneeded-ternary': 2, 0,
'object-curly-spacing': [2, 'never'], 2,
'one-var': 0, {
'operator-assignment': [2, 'always'], "SwitchCase": 1
'padded-blocks': [2, 'never'], }
'quote-props': [2, 'as-needed', {keywords: false, numbers: false }], ],
'require-jsdoc': 0, "linebreak-style": [
'semi-spacing': [2, {before: false, after: true}], 2,
'semi': [2, 'never'], "unix"
'sort-vars': 0, ],
'keyword-spacing': 'error', "lines-around-comment": 0,
'space-before-blocks': [2, 'always'], "max-depth": 0,
'space-before-function-paren': [2, 'never'], "max-len": 0,
'space-in-parens': [2, 'never'], "max-nested-callbacks": 0,
'space-infix-ops': 2, "max-params": 0,
'space-unary-ops': 2, "max-statements": 0,
'spaced-comment': [2, 'always'], "new-cap": 0,
'wrap-regex': 0, "newline-after-var": 0,
'arrow-body-style': 0, "no-array-constructor": 2,
'arrow-spacing': [2, {before: true, after: true}], "no-negated-condition": 2,
'no-confusing-arrow': 0, "no-inline-comments": 0,
'no-class-assign': 2, "no-lonely-if": 2,
'no-const-assign': 2, "no-nested-ternary": 2,
'no-dupe-class-members': 2, "no-new-object": 2,
'no-this-before-super': 2, "no-plusplus": [
'no-var': 2, 2,
'object-shorthand': [2, 'always'], {
'prefer-arrow-callback': 0, "allowForLoopAfterthoughts": true
'prefer-const': 2, }
'prefer-template': 2, ],
"no-ternary": 0,
// React "no-underscore-dangle": 0,
'jsx-quotes': [1, "prefer-double"], "no-unneeded-ternary": 2,
'react/display-name': 0, "one-var": 0,
'react/jsx-no-bind': [2, {ignoreRefs: true}], "operator-assignment": [
'react/jsx-boolean-value': [2, 'always'], 2,
'react/jsx-curly-spacing': [2, 'never'], "always"
'react/jsx-equals-spacing': [2, 'never'], ],
'react/jsx-key': 2, "require-jsdoc": 0,
'react/jsx-no-duplicate-props': 2, "sort-vars": 0,
'react/jsx-no-undef': 2, "spaced-comment": [
'react/jsx-sort-props': 0, 2,
'react/jsx-sort-prop-types': 0, "always"
'react/jsx-uses-react': 2, ],
'react/jsx-uses-vars': 2, "wrap-regex": 0,
'react/no-danger': 2, "arrow-body-style": 0,
'react/no-did-mount-set-state': 0, "no-confusing-arrow": 0,
'react/no-did-update-set-state': 2, "no-class-assign": 2,
'react/no-direct-mutation-state': 2, "no-const-assign": 2,
'react/no-is-mounted': 2, "no-dupe-class-members": 2,
'react/no-multi-comp': 0, "no-this-before-super": 2,
'react/no-set-state': 0, "no-var": 2,
'react/no-string-refs': 0, // TODO: 2 "object-shorthand": [
'react/no-unknown-property': 2, 2,
'react/prop-types': 2, "always"
'react/prefer-es6-class': [0, 'never'], ],
'react/react-in-jsx-scope': 2, "prefer-arrow-callback": 0,
'react/require-extension': 0, "prefer-const": 2,
'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) "prefer-template": 2,
'react/sort-comp': 0, // TODO: 2 "react/display-name": 0,
"react/jsx-no-bind": [
// Prettier 2,
'prettier/prettier': ['error', { {
'singleQuote': true, "ignoreRefs": true
'trailingComma': 'es5', }
'bracketSpacing': false, ],
'semi': false, "react/jsx-boolean-value": [
}], 2,
"always"
// jest ],
'jest/no-disabled-tests': "warn", "react/jsx-key": 2,
'jest/no-focused-tests': "error", "react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,
// Babel "react/jsx-sort-props": 0,
'babel/no-invalid-this': 1 "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', runner: 'jest-runner-eslint',
displayName: 'lint', displayName: 'eslint',
testMatch: ['<rootDir>/test/**/*.test.js'], 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-config-prettier": "^2.9.0",
"eslint-loader": "^2.0.0", "eslint-loader": "^2.0.0",
"eslint-plugin-jest": "^21.12.2", "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-plugin-react": "6.6.0",
"eslint-watch": "^3.1.2", "eslint-watch": "^3.1.2",
"express": "^4.14.0", "express": "^4.14.0",
@ -77,6 +77,7 @@
"imports-loader": "^0.6.5", "imports-loader": "^0.6.5",
"jest": "^22.4.2", "jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0", "jest-runner-eslint": "^0.4.0",
"jest-runner-tslint": "^1.0.3",
"jsdom": "^9.0.0", "jsdom": "^9.0.0",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"node-sass": "^4.5.3", "node-sass": "^4.5.3",
@ -86,7 +87,7 @@
"postcss-loader": "^0.8.0", "postcss-loader": "^0.8.0",
"postcss-reporter": "^1.3.1", "postcss-reporter": "^1.3.1",
"precss": "^1.4.0", "precss": "^1.4.0",
"prettier": "1.5.3", "prettier": "^1.11.1",
"react-addons-test-utils": "^15.0.2", "react-addons-test-utils": "^15.0.2",
"react-test-renderer": "^15.6.1", "react-test-renderer": "^15.6.1",
"resolve-url-loader": "^2.2.1", "resolve-url-loader": "^2.2.1",
@ -96,6 +97,11 @@
"ts-jest": "^22.4.1", "ts-jest": "^22.4.1",
"ts-loader": "^3.5.0", "ts-loader": "^3.5.0",
"tslib": "^1.9.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", "typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^1.2.2", "uglifyjs-webpack-plugin": "^1.2.2",
"webpack": "^3.11.0", "webpack": "^3.11.0",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -133,33 +133,33 @@ class AllUsersTable extends Component {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{users.length {users.length ? (
? users.map(user => users.map(user => (
<AllUsersTableRow <AllUsersTableRow
user={user} user={user}
key={uuid.v4()} 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
organizations={organizations} organizations={organizations}
onBlur={this.handleBlurCreateUserRow} onAddToOrganization={this.handleAddToOrganization}
onCreateUser={onCreateUser} 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> </tbody>
</table> </table>
</div> </div>

View File

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

View File

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

View File

@ -39,10 +39,9 @@ class OrganizationsTable extends Component {
} = this.props } = this.props
const {isCreatingOrganization} = this.state const {isCreatingOrganization} = this.state
const tableTitle = `${organizations.length} Organization${organizations.length === const tableTitle = `${organizations.length} Organization${
1 organizations.length === 1 ? '' : 's'
? '' }`
: 's'}`
if (!organizations.length) { if (!organizations.length) {
return ( return (
@ -56,9 +55,7 @@ class OrganizationsTable extends Component {
return ( return (
<div className="panel panel-solid"> <div className="panel panel-solid">
<div className="panel-heading"> <div className="panel-heading">
<h2 className="panel-title"> <h2 className="panel-title">{tableTitle}</h2>
{tableTitle}
</h2>
<button <button
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"
onClick={this.handleClickCreateOrganization} onClick={this.handleClickCreateOrganization}
@ -76,13 +73,13 @@ class OrganizationsTable extends Component {
</div> </div>
<div className="fancytable--th orgs-table--delete" /> <div className="fancytable--th orgs-table--delete" />
</div> </div>
{isCreatingOrganization {isCreatingOrganization ? (
? <OrganizationsTableRowNew <OrganizationsTableRowNew
onCreateOrganization={this.handleCreateOrganization} onCreateOrganization={this.handleCreateOrganization}
onCancelCreateOrganization={this.handleCancelCreateOrganization} onCancelCreateOrganization={this.handleCancelCreateOrganization}
/> />
: null} ) : null}
{organizations.map(org => {organizations.map(org => (
<OrganizationsTableRow <OrganizationsTableRow
key={uuid.v4()} key={uuid.v4()}
organization={org} organization={org}
@ -91,7 +88,7 @@ class OrganizationsTable extends Component {
onChooseDefaultRole={onChooseDefaultRole} onChooseDefaultRole={onChooseDefaultRole}
currentOrganization={currentOrganization} currentOrganization={currentOrganization}
/> />
)} ))}
</div> </div>
</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' import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
const OrganizationsTableRowDeleteButton = ({organization, onClickDelete}) => const OrganizationsTableRowDeleteButton = ({organization, onClickDelete}) =>
organization.id === DEFAULT_ORG_ID organization.id === DEFAULT_ORG_ID ? (
? <button <button
className="btn btn-sm btn-default btn-square orgs-table--delete" className="btn btn-sm btn-default btn-square orgs-table--delete"
disabled={true} disabled={true}
> >
<span className="icon trash" /> <span className="icon trash" />
</button> </button>
: <button ) : (
className="btn btn-sm btn-default btn-square" <button
onClick={onClickDelete} className="btn btn-sm btn-default btn-square"
> onClick={onClickDelete}
<span className="icon trash" /> >
</button> <span className="icon trash" />
</button>
)
class OrganizationsTableRow extends Component { class OrganizationsTableRow extends Component {
constructor(props) { constructor(props) {
@ -81,16 +83,18 @@ class OrganizationsTableRow extends Component {
return ( return (
<div className="fancytable--row"> <div className="fancytable--row">
<div className="fancytable--td orgs-table--active"> <div className="fancytable--td orgs-table--active">
{organization.id === currentOrganization.id {organization.id === currentOrganization.id ? (
? <button className="btn btn-sm btn-success"> <button className="btn btn-sm btn-success">
<span className="icon checkmark" /> Current <span className="icon checkmark" /> Current
</button> </button>
: <button ) : (
className="btn btn-sm btn-default" <button
onClick={this.handleChangeCurrentOrganization} className="btn btn-sm btn-default"
> onClick={this.handleChangeCurrentOrganization}
<span className="icon shuffle" /> Switch to >
</button>} <span className="icon shuffle" /> Switch to
</button>
)}
</div> </div>
<InputClickToEdit <InputClickToEdit
value={organization.name} value={organization.name}
@ -105,19 +109,21 @@ class OrganizationsTableRow extends Component {
className="dropdown-stretch" className="dropdown-stretch"
/> />
</div> </div>
{isDeleting {isDeleting ? (
? <ConfirmButtons <ConfirmButtons
item={organization} item={organization}
onCancel={this.handleDismissDeleteConfirmation} onCancel={this.handleDismissDeleteConfirmation}
onConfirm={this.handleDeleteOrg} onConfirm={this.handleDeleteOrg}
onClickOutside={this.handleDismissDeleteConfirmation} onClickOutside={this.handleDismissDeleteConfirmation}
confirmLeft={true} confirmLeft={true}
confirmTitle="Delete" confirmTitle="Delete"
/> />
: <OrganizationsTableRowDeleteButton ) : (
organization={organization} <OrganizationsTableRowDeleteButton
onClickDelete={this.handleDeleteClick} organization={organization}
/>} onClickDelete={this.handleDeleteClick}
/>
)}
</div> </div>
) )
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import {connect} from 'react-redux'
import AdminTabs from 'src/admin/components/chronograf/AdminTabs' import AdminTabs from 'src/admin/components/chronograf/AdminTabs'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'shared/components/FancyScrollbar'
const AdminChronografPage = ({me}) => const AdminChronografPage = ({me}) => (
<div className="page"> <div className="page">
<div className="page-header"> <div className="page-header">
<div className="page-header__container"> <div className="page-header__container">
@ -22,6 +22,7 @@ const AdminChronografPage = ({me}) =>
</div> </div>
</FancyScrollbar> </FancyScrollbar>
</div> </div>
)
const {shape, string} = PropTypes 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 {notify as notifyAction} from 'src/shared/actions/notifications'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable' 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 { interface Props {
notify: () => void notify: () => void
@ -47,12 +47,12 @@ export class AllUsersPage extends PureComponent<Props, State> {
} }
} }
componentDidMount() { public componentDidMount() {
const {links, actionsConfig: {getAuthConfigAsync}} = this.props const {links, actionsConfig: {getAuthConfigAsync}} = this.props
getAuthConfigAsync(links.config.auth) getAuthConfigAsync(links.config.auth)
} }
async componentWillMount() { public async componentWillMount() {
const { const {
links, links,
actionsAdmin: {loadOrganizationsAsync, loadUsersAsync}, actionsAdmin: {loadOrganizationsAsync, loadUsersAsync},
@ -68,12 +68,12 @@ export class AllUsersPage extends PureComponent<Props, State> {
this.setState({isLoading: false}) this.setState({isLoading: false})
} }
handleCreateUser = (user: User) => { public handleCreateUser = (user: User) => {
const {links, actionsAdmin: {createUserAsync}} = this.props const {links, actionsAdmin: {createUserAsync}} = this.props
createUserAsync(links.allUsers, user) createUserAsync(links.allUsers, user)
} }
handleUpdateUserRoles = ( public handleUpdateUserRoles = (
user: User, user: User,
roles: Role[], roles: Role[],
successMessage: string successMessage: string
@ -83,7 +83,7 @@ export class AllUsersPage extends PureComponent<Props, State> {
updateUserAsync(user, updatedUser, successMessage) updateUserAsync(user, updatedUser, successMessage)
} }
handleUpdateUserSuperAdmin = (user: User, superAdmin: boolean) => { public handleUpdateUserSuperAdmin = (user: User, superAdmin: boolean) => {
const {actionsAdmin: {updateUserAsync}} = this.props const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin} const updatedUser = {...user, superAdmin}
updateUserAsync( updateUserAsync(
@ -93,12 +93,12 @@ export class AllUsersPage extends PureComponent<Props, State> {
) )
} }
handleDeleteUser = (user: User) => { public handleDeleteUser = (user: User) => {
const {actionsAdmin: {deleteUserAsync}} = this.props const {actionsAdmin: {deleteUserAsync}} = this.props
deleteUserAsync(user, {isAbsoluteDelete: true}) deleteUserAsync(user, {isAbsoluteDelete: true})
} }
render() { public render() {
const { const {
meID, meID,
users, users,
@ -133,10 +133,10 @@ const mapStateToProps = ({
adminChronograf: {organizations, users}, adminChronograf: {organizations, users},
config: {auth: authConfig}, config: {auth: authConfig},
}) => ({ }) => ({
authConfig,
links, links,
organizations, organizations,
users, users,
authConfig,
}) })
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

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

View File

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

View File

@ -30,7 +30,9 @@ class AlertsApp extends Component {
alerts: [], alerts: [],
timeRange: { timeRange: {
upper: moment().format(), 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 limit: props.limit || 0, // only used if AlertsApp receives a limit prop
limitMultiplier: 1, // 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 {source, isWidget, limit} = this.props
const {isAlertsMaxedOut, alerts} = this.state const {isAlertsMaxedOut, alerts} = this.state
return this.state.hasKapacitor return this.state.hasKapacitor ? (
? <AlertsTable <AlertsTable
source={source} source={source}
alerts={this.state.alerts} alerts={this.state.alerts}
shouldNotBeFilterable={isWidget} shouldNotBeFilterable={isWidget}
limit={limit} limit={limit}
onGetMoreAlerts={this.handleGetMoreAlerts} onGetMoreAlerts={this.handleGetMoreAlerts}
isAlertsMaxedOut={isAlertsMaxedOut} isAlertsMaxedOut={isAlertsMaxedOut}
alertsCount={alerts.length} alertsCount={alerts.length}
/> />
: <NoKapacitorError source={source} /> ) : (
<NoKapacitorError source={source} />
)
} }
handleApplyTime = timeRange => { handleApplyTime = timeRange => {
@ -143,33 +147,33 @@ class AlertsApp extends Component {
return <div className="page-spinner" /> return <div className="page-spinner" />
} }
return isWidget return isWidget ? (
? this.renderSubComponents() this.renderSubComponents()
: <div className="page alert-history-page"> ) : (
<div className="page-header"> <div className="page alert-history-page">
<div className="page-header__container"> <div className="page-header">
<div className="page-header__left"> <div className="page-header__container">
<h1 className="page-header__title">Alert History</h1> <div className="page-header__left">
</div> <h1 className="page-header__title">Alert History</h1>
<div className="page-header__right">
<SourceIndicator />
<CustomTimeRangeDropdown
onApplyTimeRange={this.handleApplyTime}
timeRange={timeRange}
/>
</div>
</div> </div>
</div> <div className="page-header__right">
<div className="page-contents"> <SourceIndicator />
<div className="container-fluid"> <CustomTimeRangeDropdown
<div className="row"> onApplyTimeRange={this.handleApplyTime}
<div className="col-md-12"> timeRange={timeRange}
{this.renderSubComponents()} />
</div>
</div>
</div> </div>
</div> </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 <strong>{VERSION}</strong> / Time-Series Data Visualization
</p> </p>
{auth.links && {auth.links &&
auth.links.map(({name, login, label}) => auth.links.map(({name, login, label}) => (
<a key={name} className="btn btn-primary" href={login}> <a key={name} className="btn btn-primary" href={login}>
<span className={`icon ${name}`} /> <span className={`icon ${name}`} />
Log in with {label} Log in with {label}
</a> </a>
)} ))}
</SplashPage> </SplashPage>
</div> </div>
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,90 +31,85 @@ const DashboardHeader = ({
handleClickPresentationButton, handleClickPresentationButton,
zoomedTimeRange: {zoomedLower, zoomedUpper}, zoomedTimeRange: {zoomedLower, zoomedUpper},
}) => }) =>
isHidden isHidden ? null : (
? null <div className="page-header full-width">
: <div className="page-header full-width"> <div className="page-header__container">
<div className="page-header__container"> <div
<div className={
className={ dashboard
dashboard ? 'page-header__left page-header__dash-editable'
? 'page-header__left page-header__dash-editable' : 'page-header__left'
: 'page-header__left' }
} >
> {names && names.length > 1 ? (
{names && names.length > 1 <DashboardSwitcher
? <DashboardSwitcher names={names}
names={names} activeDashboard={activeDashboard}
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"
/> />
<TimeRangeDropdown ) : null}
onChooseTimeRange={handleChooseTimeRange} {dashboard ? (
selected={{ <Authorized
upper: zoomedUpper || upper, requiredRole={EDITOR_ROLE}
lower: zoomedLower || lower, replaceWithIfNotAuthorized={
}} <h1 className="page-header__title">{activeDashboard}</h1>
/> }
<div
className="btn btn-default btn-sm btn-square"
onClick={handleClickPresentationButton}
> >
<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> </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>
</div> </div>
</div>
)
const {arrayOf, bool, func, number, shape, string} = PropTypes const {arrayOf, bool, func, number, shape, string} = PropTypes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -173,7 +173,7 @@ class GaugeOptions extends Component {
> >
<span className="icon plus" /> Add Threshold <span className="icon plus" /> Add Threshold
</button> </button>
{gaugeColors.map(color => {gaugeColors.map(color => (
<Threshold <Threshold
isMin={color.value === gaugeColors[0].value} isMin={color.value === gaugeColors[0].value}
isMax={ isMax={
@ -189,7 +189,7 @@ class GaugeOptions extends Component {
onDeleteThreshold={this.handleDeleteThreshold} onDeleteThreshold={this.handleDeleteThreshold}
onSortColors={this.handleSortColors} onSortColors={this.handleSortColors}
/> />
)} ))}
</div> </div>
<div className="graph-options-group form-group-wrapper"> <div className="graph-options-group form-group-wrapper">
<div className="form-group col-xs-6"> <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' import InputClickToEdit from 'src/shared/components/InputClickToEdit'
type Column = { interface Column {
internalName: string internalName: string
displayName: string displayName: string
} }
@ -20,19 +20,17 @@ class GraphOptionsCustomizableColumn extends PureComponent<Props, {}> {
this.handleColumnRename = this.handleColumnRename.bind(this) this.handleColumnRename = this.handleColumnRename.bind(this)
} }
handleColumnRename(rename) { public handleColumnRename(rename) {
const {onColumnRename, internalName} = this.props const {onColumnRename, internalName} = this.props
onColumnRename({internalName, displayName: rename}) onColumnRename({internalName, displayName: rename})
} }
render() { public render() {
const {internalName, displayName} = this.props const {internalName, displayName} = this.props
return ( return (
<div className="column-controls--section"> <div className="column-controls--section">
<div className="column-controls--label"> <div className="column-controls--label">{internalName}</div>
{internalName}
</div>
<InputClickToEdit <InputClickToEdit
value={displayName} value={displayName}
wrapperClass="column-controls-input" wrapperClass="column-controls-input"

View File

@ -3,7 +3,7 @@ import React, {SFC} from 'react'
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn' import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
import uuid from 'uuid' import uuid from 'uuid'
type Column = { interface Column {
internalName: string internalName: string
displayName: 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> <label>Text Wrapping</label>
<ul className="nav nav-tablist nav-tablist-sm"> <ul className="nav nav-tablist nav-tablist-sm">
<li <li
className={`${thresholdsListType === THRESHOLD_TYPE_BG className={`${
? 'active' thresholdsListType === THRESHOLD_TYPE_BG ? 'active' : ''
: ''}`} }`}
onClick={onToggleTextWrapping} onClick={onToggleTextWrapping}
> >
Truncate Truncate
</li> </li>
<li <li
className={`${thresholdsListType === THRESHOLD_TYPE_TEXT className={`${
? 'active' thresholdsListType === THRESHOLD_TYPE_TEXT ? 'active' : ''
: ''}`} }`}
onClick={onToggleTextWrapping} onClick={onToggleTextWrapping}
> >
Wrap Wrap
</li> </li>
<li <li
className={`${thresholdsListType === THRESHOLD_TYPE_BG className={`${
? 'active' thresholdsListType === THRESHOLD_TYPE_BG ? 'active' : ''
: ''}`} }`}
onClick={onToggleTextWrapping} onClick={onToggleTextWrapping}
> >
Single Line Single Line

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,67 +16,67 @@ const TemplateControlBar = ({
templates, templates,
onSelectTemplate, onSelectTemplate,
onOpenTemplateManager, onOpenTemplateManager,
}) => }) => (
<div className={classnames('template-control-bar', {show: isOpen})}> <div className={classnames('template-control-bar', {show: isOpen})}>
<div className="template-control--container"> <div className="template-control--container">
<div className="template-control--controls"> <div className="template-control--controls">
{templates.length {templates.length ? (
? templates.map(({id, values, tempVar}) => { templates.map(({id, values, tempVar}) => {
const items = values.map(value => ({...value, text: value.value})) const items = values.map(value => ({...value, text: value.value}))
const itemValues = values.map(value => value.value) const itemValues = values.map(value => value.value)
const selectedItem = items.find(item => item.selected) || items[0] const selectedItem = items.find(item => item.selected) || items[0]
const selectedText = selectedItem && selectedItem.text const selectedText = selectedItem && selectedItem.text
let customDropdownWidth = 0 let customDropdownWidth = 0
if (itemValues.length > 1) { if (itemValues.length > 1) {
const longest = itemValues.reduce( const longest = itemValues.reduce(
(a, b) => (a.length > b.length ? a : b) (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>
) )
}) const longestLengthPixels =
: <div className="template-control--empty"> calculateSize(longest, {
This dashboard does not have any Template Variables font: 'Monospace',
</div>} 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> </div>
<Authorized requiredRole={EDITOR_ROLE}> <Authorized requiredRole={EDITOR_ROLE}>
<button <button
@ -89,6 +89,7 @@ const TemplateControlBar = ({
</Authorized> </Authorized>
</div> </div>
</div> </div>
)
const {arrayOf, bool, func, shape, string} = PropTypes const {arrayOf, bool, func, shape, string} = PropTypes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,13 +2,14 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
const TableTabItem = ({name, index, onClickTab, isActive}) => const TableTabItem = ({name, index, onClickTab, isActive}) => (
<div <div
className={classnames('table--tab', {active: isActive})} className={classnames('table--tab', {active: isActive})}
onClick={onClickTab(index)} onClick={onClickTab(index)}
> >
{name} {name}
</div> </div>
)
const {bool, func, number, string} = PropTypes 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"> <div className="graph-heading">
{views.length {views.length ? (
? <ul className="nav nav-tablist nav-tablist-sm"> <ul className="nav nav-tablist nav-tablist-sm">
{views.map(v => {views.map(v => (
<li <li
key={v} key={v}
onClick={onToggleView(v)} onClick={onToggleView(v)}
className={classnames({active: view === v})} className={classnames({active: view === v})}
data-test={`data-${v}`} data-test={`data-${v}`}
> >
{_.upperFirst(v)} {_.upperFirst(v)}
</li> </li>
)} ))}
</ul> </ul>
: null} ) : null}
{query {query ? (
? <div <div
className="btn btn-sm btn-default dlcsv" className="btn btn-sm btn-default dlcsv"
onClick={getCSV(query, errorThrown)} onClick={getCSV(query, errorThrown)}
> >
<span className="icon download dlcsv" /> <span className="icon download dlcsv" />
.csv .csv
</div> </div>
: null} ) : null}
</div> </div>
)
const {arrayOf, func, shape, string} = PropTypes const {arrayOf, func, shape, string} = PropTypes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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