Merge branch 'master' of github.com:influxdata/chronograf

pull/10616/head
Andrew Watkins 2018-07-11 10:03:53 -07:00
commit 4d498eba75
31 changed files with 4309 additions and 466 deletions

View File

@ -6,6 +6,7 @@
1. [#3559](https://github.com/influxdata/chronograf/pull/3559): Add ability to export and import dashboards
1. [#3556](https://github.com/influxdata/chronograf/pull/3556): Add ability to override template variables and time ranges via URL query
1. [#3814](https://github.com/influxdata/chronograf/pull/3814): Add pprof routes to chronograf server
1. [#3806](https://github.com/influxdata/chronograf/pull/3806): Add API to get/update Log Viewer UI config
### UI Improvements

View File

@ -33,15 +33,16 @@ type Client struct {
Now func() time.Time
LayoutIDs chronograf.ID
BuildStore *BuildStore
SourcesStore *SourcesStore
ServersStore *ServersStore
LayoutsStore *LayoutsStore
DashboardsStore *DashboardsStore
UsersStore *UsersStore
OrganizationsStore *OrganizationsStore
ConfigStore *ConfigStore
MappingsStore *MappingsStore
BuildStore *BuildStore
SourcesStore *SourcesStore
ServersStore *ServersStore
LayoutsStore *LayoutsStore
DashboardsStore *DashboardsStore
UsersStore *UsersStore
OrganizationsStore *OrganizationsStore
ConfigStore *ConfigStore
MappingsStore *MappingsStore
OrganizationConfigStore *OrganizationConfigStore
}
// NewClient initializes all stores
@ -62,6 +63,7 @@ func NewClient() *Client {
c.OrganizationsStore = &OrganizationsStore{client: c}
c.ConfigStore = &ConfigStore{client: c}
c.MappingsStore = &MappingsStore{client: c}
c.OrganizationConfigStore = &OrganizationConfigStore{client: c}
return c
}
@ -161,6 +163,10 @@ func (c *Client) initialize(ctx context.Context) error {
if _, err := tx.CreateBucketIfNotExists(MappingsBucket); err != nil {
return err
}
// Always create OrganizationConfig bucket.
if _, err := tx.CreateBucketIfNotExists(OrganizationConfigBucket); err != nil {
return err
}
return nil
}); err != nil {
return err
@ -197,6 +203,9 @@ func (c *Client) migrate(ctx context.Context, build chronograf.BuildInfo) error
if err := c.MappingsStore.Migrate(ctx); err != nil {
return err
}
if err := c.OrganizationConfigStore.Migrate(ctx); err != nil {
return err
}
MigrateAll(c)
}

View File

@ -747,6 +747,81 @@ func UnmarshalConfigPB(data []byte, c *Config) error {
return proto.Unmarshal(data, c)
}
// MarshalOrganizationConfig encodes a config to binary protobuf format.
func MarshalOrganizationConfig(c *chronograf.OrganizationConfig) ([]byte, error) {
columns := make([]*LogViewerColumn, len(c.LogViewer.Columns))
for i, column := range c.LogViewer.Columns {
encodings := make([]*ColumnEncoding, len(column.Encodings))
for j, e := range column.Encodings {
encodings[j] = &ColumnEncoding{
Type: e.Type,
Value: e.Value,
Name: e.Name,
}
}
columns[i] = &LogViewerColumn{
Name: column.Name,
Position: column.Position,
Encodings: encodings,
}
}
return MarshalOrganizationConfigPB(&OrganizationConfig{
OrganizationID: c.OrganizationID,
LogViewer: &LogViewerConfig{
Columns: columns,
},
})
}
// MarshalOrganizationConfigPB encodes a config to binary protobuf format.
func MarshalOrganizationConfigPB(c *OrganizationConfig) ([]byte, error) {
return proto.Marshal(c)
}
// UnmarshalOrganizationConfig decodes a config from binary protobuf data.
func UnmarshalOrganizationConfig(data []byte, c *chronograf.OrganizationConfig) error {
var pb OrganizationConfig
if err := UnmarshalOrganizationConfigPB(data, &pb); err != nil {
return err
}
if pb.LogViewer == nil {
return fmt.Errorf("Log Viewer config is nil")
}
c.OrganizationID = pb.OrganizationID
columns := make([]chronograf.LogViewerColumn, len(pb.LogViewer.Columns))
for i, c := range pb.LogViewer.Columns {
columns[i].Name = c.Name
columns[i].Position = c.Position
encodings := make([]chronograf.ColumnEncoding, len(c.Encodings))
for j, e := range c.Encodings {
encodings[j].Type = e.Type
encodings[j].Value = e.Value
encodings[j].Name = e.Name
}
columns[i].Encodings = encodings
}
c.LogViewer.Columns = columns
return nil
}
// UnmarshalOrganizationConfigPB decodes a config from binary protobuf data.
func UnmarshalOrganizationConfigPB(data []byte, c *OrganizationConfig) error {
return proto.Unmarshal(data, c)
}
// MarshalMapping encodes a mapping to binary protobuf format.
func MarshalMapping(m *chronograf.Mapping) ([]byte, error) {

View File

@ -33,6 +33,10 @@ It has these top-level messages:
Organization
Config
AuthConfig
OrganizationConfig
LogViewerConfig
LogViewerColumn
ColumnEncoding
BuildInfo
*/
package internal
@ -1340,6 +1344,110 @@ func (m *AuthConfig) GetSuperAdminNewUsers() bool {
return false
}
type OrganizationConfig struct {
OrganizationID string `protobuf:"bytes,1,opt,name=OrganizationID,proto3" json:"OrganizationID,omitempty"`
LogViewer *LogViewerConfig `protobuf:"bytes,2,opt,name=LogViewer" json:"LogViewer,omitempty"`
}
func (m *OrganizationConfig) Reset() { *m = OrganizationConfig{} }
func (m *OrganizationConfig) String() string { return proto.CompactTextString(m) }
func (*OrganizationConfig) ProtoMessage() {}
func (*OrganizationConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{25} }
func (m *OrganizationConfig) GetOrganizationID() string {
if m != nil {
return m.OrganizationID
}
return ""
}
func (m *OrganizationConfig) GetLogViewer() *LogViewerConfig {
if m != nil {
return m.LogViewer
}
return nil
}
type LogViewerConfig struct {
Columns []*LogViewerColumn `protobuf:"bytes,1,rep,name=Columns" json:"Columns,omitempty"`
}
func (m *LogViewerConfig) Reset() { *m = LogViewerConfig{} }
func (m *LogViewerConfig) String() string { return proto.CompactTextString(m) }
func (*LogViewerConfig) ProtoMessage() {}
func (*LogViewerConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{26} }
func (m *LogViewerConfig) GetColumns() []*LogViewerColumn {
if m != nil {
return m.Columns
}
return nil
}
type LogViewerColumn struct {
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
Position int32 `protobuf:"varint,2,opt,name=Position,proto3" json:"Position,omitempty"`
Encodings []*ColumnEncoding `protobuf:"bytes,3,rep,name=Encodings" json:"Encodings,omitempty"`
}
func (m *LogViewerColumn) Reset() { *m = LogViewerColumn{} }
func (m *LogViewerColumn) String() string { return proto.CompactTextString(m) }
func (*LogViewerColumn) ProtoMessage() {}
func (*LogViewerColumn) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{27} }
func (m *LogViewerColumn) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *LogViewerColumn) GetPosition() int32 {
if m != nil {
return m.Position
}
return 0
}
func (m *LogViewerColumn) GetEncodings() []*ColumnEncoding {
if m != nil {
return m.Encodings
}
return nil
}
type ColumnEncoding struct {
Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"`
Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"`
Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"`
}
func (m *ColumnEncoding) Reset() { *m = ColumnEncoding{} }
func (m *ColumnEncoding) String() string { return proto.CompactTextString(m) }
func (*ColumnEncoding) ProtoMessage() {}
func (*ColumnEncoding) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{28} }
func (m *ColumnEncoding) GetType() string {
if m != nil {
return m.Type
}
return ""
}
func (m *ColumnEncoding) GetValue() string {
if m != nil {
return m.Value
}
return ""
}
func (m *ColumnEncoding) GetName() string {
if m != nil {
return m.Name
}
return ""
}
type BuildInfo struct {
Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"`
Commit string `protobuf:"bytes,2,opt,name=Commit,proto3" json:"Commit,omitempty"`
@ -1348,7 +1456,7 @@ type BuildInfo struct {
func (m *BuildInfo) Reset() { *m = BuildInfo{} }
func (m *BuildInfo) String() string { return proto.CompactTextString(m) }
func (*BuildInfo) ProtoMessage() {}
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{25} }
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{29} }
func (m *BuildInfo) GetVersion() string {
if m != nil {
@ -1390,117 +1498,128 @@ func init() {
proto.RegisterType((*Organization)(nil), "internal.Organization")
proto.RegisterType((*Config)(nil), "internal.Config")
proto.RegisterType((*AuthConfig)(nil), "internal.AuthConfig")
proto.RegisterType((*OrganizationConfig)(nil), "internal.OrganizationConfig")
proto.RegisterType((*LogViewerConfig)(nil), "internal.LogViewerConfig")
proto.RegisterType((*LogViewerColumn)(nil), "internal.LogViewerColumn")
proto.RegisterType((*ColumnEncoding)(nil), "internal.ColumnEncoding")
proto.RegisterType((*BuildInfo)(nil), "internal.BuildInfo")
}
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 1691 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdd, 0x8f, 0xe3, 0x48,
0x11, 0x97, 0x13, 0x3b, 0x89, 0x2b, 0x99, 0xb9, 0x91, 0x59, 0xdd, 0x99, 0x03, 0xa1, 0x60, 0xf1,
0x31, 0x7c, 0xdc, 0x72, 0x9a, 0x13, 0x12, 0x3a, 0xdd, 0x9d, 0x34, 0x1f, 0xb7, 0xcb, 0xec, 0xe7,
0x6c, 0x67, 0x76, 0x78, 0x42, 0xa7, 0x4e, 0xdc, 0x49, 0x5a, 0xe7, 0xd8, 0xa6, 0xdd, 0x9e, 0x19,
0xf3, 0xcc, 0xdf, 0x81, 0x84, 0x04, 0xef, 0x08, 0xf1, 0x88, 0xc4, 0x3b, 0x7f, 0x00, 0xff, 0x0a,
0xaf, 0xa8, 0xfa, 0xc3, 0x69, 0xcf, 0x64, 0x57, 0x8b, 0x84, 0xee, 0xad, 0x7f, 0x55, 0x95, 0xea,
0xea, 0xea, 0xaa, 0x5f, 0x97, 0x03, 0xfb, 0x3c, 0x97, 0x4c, 0xe4, 0x34, 0x7b, 0x58, 0x8a, 0x42,
0x16, 0xd1, 0xc8, 0xe2, 0xe4, 0x0f, 0x7d, 0x18, 0xcc, 0x8a, 0x5a, 0x2c, 0x58, 0xb4, 0x0f, 0xbd,
0xf3, 0xb3, 0xd8, 0x9b, 0x7a, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x8b, 0x22, 0xf0, 0x5f, 0xd0, 0x0d,
0x8b, 0x7b, 0x53, 0xef, 0x30, 0x24, 0x6a, 0x8d, 0xb2, 0xcb, 0xa6, 0x64, 0x71, 0x5f, 0xcb, 0x70,
0x1d, 0x7d, 0x08, 0xa3, 0xd7, 0x15, 0x7a, 0xdb, 0xb0, 0xd8, 0x57, 0xf2, 0x16, 0xa3, 0xee, 0x82,
0x56, 0xd5, 0x4d, 0x21, 0xd2, 0x38, 0xd0, 0x3a, 0x8b, 0xa3, 0x03, 0xe8, 0xbf, 0x26, 0xcf, 0xe2,
0x81, 0x12, 0xe3, 0x32, 0x8a, 0x61, 0x78, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x3c, 0x9c, 0x7a, 0x87,
0x23, 0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0x47, 0xda, 0x8f, 0xc5, 0xd1,
0x43, 0x88, 0xce, 0xf3, 0x8a, 0x2d, 0x6a, 0xc1, 0x66, 0x5f, 0xf3, 0xf2, 0x8a, 0x09, 0xbe, 0x6c,
0xe2, 0x50, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x8c,
0x12, 0x98, 0xcc, 0xd6, 0x54, 0xb0, 0x74, 0xc6, 0x16, 0x82, 0xc9, 0x78, 0xac, 0xd4, 0x1d, 0x19,
0xda, 0xbc, 0x14, 0x2b, 0x9a, 0xf3, 0xdf, 0x53, 0xc9, 0x8b, 0x3c, 0x9e, 0x68, 0x1b, 0x57, 0x86,
0x59, 0x22, 0x45, 0xc6, 0xe2, 0x3d, 0x9d, 0x25, 0x5c, 0x47, 0xdf, 0x85, 0xd0, 0x1c, 0x86, 0x5c,
0xc4, 0xfb, 0x4a, 0xb1, 0x15, 0x24, 0x7f, 0xf7, 0x20, 0x3c, 0xa3, 0xd5, 0x7a, 0x5e, 0x50, 0x91,
0xbe, 0xd3, 0x4d, 0x7c, 0x04, 0xc1, 0x82, 0x65, 0x59, 0x15, 0xf7, 0xa7, 0xfd, 0xc3, 0xf1, 0xd1,
0x07, 0x0f, 0xdb, 0x2b, 0x6e, 0xfd, 0x9c, 0xb2, 0x2c, 0x23, 0xda, 0x2a, 0xfa, 0x18, 0x42, 0xc9,
0x36, 0x65, 0x46, 0x25, 0xab, 0x62, 0x5f, 0xfd, 0x24, 0xda, 0xfe, 0xe4, 0xd2, 0xa8, 0xc8, 0xd6,
0xe8, 0xde, 0x41, 0x83, 0xfb, 0x07, 0x4d, 0xfe, 0xed, 0xc3, 0x5e, 0x67, 0xbb, 0x68, 0x02, 0xde,
0xad, 0x8a, 0x3c, 0x20, 0xde, 0x2d, 0xa2, 0x46, 0x45, 0x1d, 0x10, 0xaf, 0x41, 0x74, 0xa3, 0x2a,
0x27, 0x20, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x97, 0x80, 0x78, 0xeb, 0xe8, 0x27, 0x30, 0xfc, 0x5d,
0xcd, 0x04, 0x67, 0x55, 0x1c, 0xa8, 0xe8, 0xde, 0xdb, 0x46, 0xf7, 0xaa, 0x66, 0xa2, 0x21, 0x56,
0x8f, 0xd9, 0x50, 0xb5, 0xa6, 0x0b, 0x47, 0xad, 0x51, 0x26, 0xb1, 0x2e, 0x87, 0x5a, 0x86, 0x6b,
0x93, 0x45, 0x5d, 0x2d, 0x98, 0xc5, 0x5f, 0x82, 0x4f, 0x6f, 0x59, 0x15, 0x87, 0xca, 0xff, 0xf7,
0xdf, 0x90, 0xb0, 0x87, 0xc7, 0xb7, 0xac, 0xfa, 0x32, 0x97, 0xa2, 0x21, 0xca, 0x3c, 0xfa, 0x31,
0x0c, 0x16, 0x45, 0x56, 0x88, 0x2a, 0x86, 0xbb, 0x81, 0x9d, 0xa2, 0x9c, 0x18, 0x75, 0x74, 0x08,
0x83, 0x8c, 0xad, 0x58, 0x9e, 0xaa, 0xba, 0x19, 0x1f, 0x1d, 0x6c, 0x0d, 0x9f, 0x29, 0x39, 0x31,
0xfa, 0xe8, 0x53, 0x98, 0x48, 0x3a, 0xcf, 0xd8, 0xcb, 0x12, 0xb3, 0x58, 0xa9, 0x1a, 0x1a, 0x1f,
0xbd, 0xef, 0xdc, 0x87, 0xa3, 0x25, 0x1d, 0xdb, 0xe8, 0x33, 0x98, 0x2c, 0x39, 0xcb, 0x52, 0xfb,
0xdb, 0x3d, 0x15, 0x54, 0xbc, 0xfd, 0x2d, 0x61, 0x39, 0xdd, 0xe0, 0x2f, 0x1e, 0xa1, 0x19, 0xe9,
0x58, 0x47, 0xdf, 0x03, 0x90, 0x7c, 0xc3, 0x1e, 0x15, 0x62, 0x43, 0xa5, 0x29, 0x43, 0x47, 0x12,
0x7d, 0x0e, 0x7b, 0x29, 0x5b, 0xf0, 0x0d, 0xcd, 0x2e, 0x32, 0xba, 0x60, 0x55, 0xfc, 0x9e, 0x0a,
0xcd, 0xad, 0x2e, 0x57, 0x4d, 0xba, 0xd6, 0x1f, 0x3e, 0x86, 0xb0, 0x4d, 0x1f, 0xf6, 0xf7, 0xd7,
0xac, 0x51, 0xc5, 0x10, 0x12, 0x5c, 0x46, 0x3f, 0x80, 0xe0, 0x9a, 0x66, 0xb5, 0x2e, 0xe4, 0xf1,
0xd1, 0xfe, 0xd6, 0xeb, 0xf1, 0x2d, 0xaf, 0x88, 0x56, 0x7e, 0xda, 0xfb, 0x95, 0x97, 0x3c, 0x86,
0xbd, 0xce, 0x46, 0x18, 0x38, 0xaf, 0xbe, 0xcc, 0x97, 0x85, 0x58, 0xb0, 0x54, 0xf9, 0x1c, 0x11,
0x47, 0x12, 0xbd, 0x0f, 0x83, 0x94, 0xaf, 0xb8, 0xac, 0x4c, 0xb9, 0x19, 0x94, 0xfc, 0xc3, 0x83,
0x89, 0x9b, 0xcd, 0xe8, 0xa7, 0x70, 0x70, 0xcd, 0x84, 0xe4, 0x0b, 0x9a, 0x5d, 0xf2, 0x0d, 0xc3,
0x8d, 0xd5, 0x4f, 0x46, 0xe4, 0x9e, 0x3c, 0xfa, 0x18, 0x06, 0x55, 0x21, 0xe4, 0x49, 0xa3, 0xaa,
0xf6, 0x6d, 0x59, 0x36, 0x76, 0xc8, 0x53, 0x37, 0x82, 0x96, 0x25, 0xcf, 0x57, 0x96, 0x0b, 0x2d,
0x8e, 0x7e, 0x04, 0xfb, 0x4b, 0x7e, 0xfb, 0x88, 0x8b, 0x4a, 0x9e, 0x16, 0x59, 0xbd, 0xc9, 0x55,
0x05, 0x8f, 0xc8, 0x1d, 0xe9, 0x13, 0x7f, 0xe4, 0x1d, 0xf4, 0x9e, 0xf8, 0xa3, 0xe0, 0x60, 0x90,
0x94, 0xb0, 0xdf, 0xdd, 0x09, 0xdb, 0xd2, 0x06, 0xa1, 0x38, 0x41, 0xa7, 0xb7, 0x23, 0x8b, 0xa6,
0x30, 0x4e, 0x79, 0x55, 0x66, 0xb4, 0x71, 0x68, 0xc3, 0x15, 0x21, 0x07, 0x5e, 0xf3, 0x8a, 0xcf,
0x33, 0x4d, 0xe5, 0x23, 0x62, 0x61, 0xb2, 0x82, 0x40, 0x95, 0xb5, 0x43, 0x42, 0xa1, 0x25, 0x21,
0x45, 0xfd, 0x3d, 0x87, 0xfa, 0x0f, 0xa0, 0xff, 0x6b, 0x76, 0x6b, 0x5e, 0x03, 0x5c, 0xb6, 0x54,
0xe5, 0x3b, 0x54, 0xf5, 0x00, 0x82, 0x2b, 0x75, 0xed, 0x9a, 0x42, 0x34, 0x48, 0xbe, 0x80, 0x81,
0x6e, 0x8b, 0xd6, 0xb3, 0xe7, 0x78, 0x9e, 0xc2, 0xf8, 0xa5, 0xe0, 0x2c, 0x97, 0x9a, 0x7c, 0xcc,
0x11, 0x1c, 0x51, 0xf2, 0x37, 0x0f, 0x7c, 0x75, 0x4b, 0x09, 0x4c, 0x32, 0xb6, 0xa2, 0x8b, 0xe6,
0xa4, 0xa8, 0xf3, 0xb4, 0x8a, 0xbd, 0x69, 0xff, 0xb0, 0x4f, 0x3a, 0x32, 0x2c, 0x8f, 0xb9, 0xd6,
0xf6, 0xa6, 0xfd, 0xc3, 0x90, 0x18, 0x84, 0xa1, 0x65, 0x74, 0xce, 0x32, 0x73, 0x04, 0x0d, 0xd0,
0xba, 0x14, 0x6c, 0xc9, 0x6f, 0xcd, 0x31, 0x0c, 0x42, 0x79, 0x55, 0x2f, 0x51, 0xae, 0x4f, 0x62,
0x10, 0x1e, 0x60, 0x4e, 0xab, 0x96, 0x91, 0x70, 0x8d, 0x9e, 0xab, 0x05, 0xcd, 0x2c, 0x25, 0x69,
0x90, 0xfc, 0xd3, 0xc3, 0x87, 0x4c, 0x53, 0xec, 0xbd, 0x0c, 0x7f, 0x1b, 0x46, 0x48, 0xbf, 0x5f,
0x5d, 0x53, 0x61, 0x0e, 0x3c, 0x44, 0x7c, 0x45, 0x45, 0xf4, 0x0b, 0x18, 0xa8, 0xe6, 0xd8, 0x41,
0xf7, 0xd6, 0x9d, 0xca, 0x2a, 0x31, 0x66, 0x2d, 0x21, 0xfa, 0x0e, 0x21, 0xb6, 0x87, 0x0d, 0xdc,
0xc3, 0x7e, 0x04, 0x01, 0x32, 0x6b, 0xa3, 0xa2, 0xdf, 0xe9, 0x59, 0xf3, 0xaf, 0xb6, 0x4a, 0x56,
0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xd0, 0x34, 0x36, 0x36, 0x47,
0xc5, 0x32, 0xb6, 0x90, 0x2c, 0x35, 0x55, 0xd7, 0x62, 0x4b, 0x16, 0x7e, 0x4b, 0x16, 0xc9, 0x9f,
0xbc, 0xed, 0x4e, 0x2a, 0x02, 0x2c, 0xda, 0x45, 0xb1, 0xd9, 0xd0, 0x3c, 0x35, 0x9b, 0x59, 0x88,
0x99, 0x4c, 0xe7, 0x66, 0xb3, 0x5e, 0x3a, 0x47, 0x2c, 0x4a, 0x73, 0xa7, 0x3d, 0x51, 0x62, 0x35,
0x6d, 0x18, 0xad, 0x6a, 0xc1, 0x36, 0x2c, 0x97, 0x66, 0x17, 0x57, 0x14, 0x7d, 0x00, 0x43, 0x49,
0x57, 0x5f, 0x61, 0x0c, 0xe6, 0x6e, 0x25, 0x5d, 0x3d, 0x65, 0x4d, 0xf4, 0x1d, 0x08, 0x15, 0x83,
0x2a, 0x95, 0xbe, 0xe0, 0x91, 0x12, 0x3c, 0x65, 0x4d, 0xf2, 0xd7, 0x1e, 0x0c, 0x66, 0x4c, 0x5c,
0x33, 0xf1, 0x4e, 0x6f, 0xb6, 0x3b, 0x29, 0xf5, 0xdf, 0x32, 0x29, 0xf9, 0xbb, 0x27, 0xa5, 0x60,
0x3b, 0x29, 0x3d, 0x80, 0x60, 0x26, 0x16, 0xe7, 0x67, 0x2a, 0xa2, 0x3e, 0xd1, 0x00, 0xeb, 0xf3,
0x78, 0x21, 0xf9, 0x35, 0x33, 0xe3, 0x93, 0x41, 0xf7, 0x9e, 0xf2, 0xd1, 0x8e, 0x99, 0xe5, 0x7f,
0x9d, 0xa2, 0x6c, 0xd3, 0x82, 0xd3, 0xb4, 0x09, 0x4c, 0x70, 0x94, 0x4a, 0xa9, 0xa4, 0x4f, 0x66,
0x2f, 0x5f, 0xd8, 0xf9, 0xc9, 0x95, 0x25, 0x7f, 0xf4, 0x60, 0xf0, 0x8c, 0x36, 0x45, 0x2d, 0xef,
0xd5, 0xff, 0x14, 0xc6, 0xc7, 0x65, 0x99, 0xf1, 0x45, 0xa7, 0xe7, 0x1d, 0x11, 0x5a, 0x3c, 0x77,
0xee, 0x51, 0xe7, 0xd0, 0x15, 0xe1, 0x13, 0x73, 0xaa, 0xc6, 0x22, 0x3d, 0xe3, 0x38, 0x4f, 0x8c,
0x9e, 0x86, 0x94, 0x12, 0x93, 0x7d, 0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xab, 0x23, 0xd2,
0xe2, 0xe4, 0x5f, 0x3d, 0xf0, 0xbf, 0xa9, 0x51, 0x66, 0x02, 0x1e, 0x37, 0x45, 0xe5, 0xf1, 0x76,
0xb0, 0x19, 0x3a, 0x83, 0x4d, 0x0c, 0xc3, 0x46, 0xd0, 0x7c, 0xc5, 0xaa, 0x78, 0xa4, 0x78, 0xcd,
0x42, 0xa5, 0x51, 0x1d, 0xac, 0x27, 0x9a, 0x90, 0x58, 0xd8, 0x76, 0x24, 0x38, 0x1d, 0xf9, 0x73,
0x33, 0xfc, 0x8c, 0xef, 0x8e, 0x0b, 0xbb, 0x66, 0x9e, 0xff, 0xdf, 0x3b, 0xfe, 0x1f, 0x0f, 0x82,
0xb6, 0x79, 0x4f, 0xbb, 0xcd, 0x7b, 0xba, 0x6d, 0xde, 0xb3, 0x13, 0xdb, 0xbc, 0x67, 0x27, 0x88,
0xc9, 0x85, 0x6d, 0x5e, 0x72, 0x81, 0x97, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0x5b, 0x0d,
0x49, 0x8b, 0xb1, 0xe2, 0x7f, 0xb3, 0x66, 0xc2, 0xa4, 0x3a, 0x24, 0x06, 0x61, 0x7f, 0x3c, 0x53,
0x54, 0xa7, 0x93, 0xab, 0x41, 0xf4, 0x43, 0x08, 0x08, 0x26, 0x4f, 0x65, 0xb8, 0x73, 0x2f, 0x4a,
0x4c, 0xb4, 0x16, 0x9d, 0xea, 0x4f, 0x22, 0xd3, 0x28, 0xf6, 0x03, 0xe9, 0x67, 0x30, 0x98, 0xad,
0xf9, 0x52, 0xda, 0x11, 0xf2, 0x5b, 0x0e, 0x55, 0xf2, 0x0d, 0x53, 0x3a, 0x62, 0x4c, 0x92, 0x57,
0x10, 0xb6, 0xc2, 0x6d, 0x38, 0x9e, 0x1b, 0x4e, 0x04, 0xfe, 0xeb, 0x9c, 0x4b, 0x4b, 0x11, 0xb8,
0xc6, 0xc3, 0xbe, 0xaa, 0x69, 0x2e, 0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x9c, 0x7c, 0x62, 0xc2, 0x47,
0x77, 0xaf, 0xcb, 0x92, 0x09, 0x43, 0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0xdf, 0x8e, 0x3e,
0xd1, 0x20, 0xf9, 0x2d, 0x84, 0xc7, 0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0xa6, 0xab, 0x46,
0x35, 0x11, 0xe0, 0x7a, 0x4b, 0x2d, 0xfd, 0x3b, 0xd4, 0xf2, 0x94, 0x96, 0xf4, 0xfc, 0x4c, 0xd5,
0x79, 0x9f, 0x18, 0x94, 0xfc, 0xd9, 0x03, 0x1f, 0x39, 0xcc, 0x71, 0xed, 0xbf, 0x8d, 0xff, 0x2e,
0x44, 0x71, 0xcd, 0x53, 0x26, 0xec, 0xe1, 0x2c, 0x56, 0x49, 0x5f, 0xac, 0x59, 0x3b, 0x3a, 0x18,
0x84, 0xb5, 0x86, 0xdf, 0x4f, 0xb6, 0x97, 0x9c, 0x5a, 0x43, 0x31, 0xd1, 0x4a, 0x1c, 0x0f, 0x67,
0x75, 0xc9, 0xc4, 0x71, 0xba, 0xe1, 0x76, 0xae, 0x72, 0x24, 0xc9, 0x17, 0xfa, 0x8b, 0xec, 0x1e,
0x13, 0x7a, 0xbb, 0xbf, 0xde, 0xee, 0x46, 0x9e, 0xfc, 0xc5, 0x83, 0xe1, 0x73, 0x33, 0xc7, 0xb9,
0xa7, 0xf0, 0xde, 0x78, 0x8a, 0x5e, 0xe7, 0x14, 0x47, 0xf0, 0xc0, 0xda, 0x74, 0xf6, 0xd7, 0x59,
0xd8, 0xa9, 0x33, 0x19, 0xf5, 0xdb, 0xcb, 0x7a, 0x97, 0x0f, 0xb2, 0xcb, 0xae, 0xcd, 0xae, 0x0b,
0xbf, 0x77, 0x2b, 0x53, 0x18, 0xdb, 0x0f, 0xd1, 0x22, 0xb3, 0x0f, 0x93, 0x2b, 0x4a, 0x8e, 0x60,
0x70, 0x5a, 0xe4, 0x4b, 0xbe, 0x8a, 0x0e, 0xc1, 0x3f, 0xae, 0xe5, 0x5a, 0x79, 0x1c, 0x1f, 0x3d,
0x70, 0x1a, 0xbf, 0x96, 0x6b, 0x6d, 0x43, 0x94, 0x45, 0xf2, 0x19, 0xc0, 0x56, 0x86, 0xaf, 0xcb,
0xf6, 0x36, 0x5e, 0xb0, 0x1b, 0x2c, 0x99, 0xca, 0x8c, 0xf1, 0x3b, 0x34, 0xc9, 0xe7, 0x10, 0x9e,
0xd4, 0x3c, 0x4b, 0xcf, 0xf3, 0x65, 0x81, 0xd4, 0x71, 0xc5, 0x44, 0xb5, 0xbd, 0x2f, 0x0b, 0x31,
0xdd, 0xc8, 0x22, 0x6d, 0x0f, 0x19, 0x34, 0x1f, 0xa8, 0xbf, 0x39, 0x3e, 0xf9, 0x6f, 0x00, 0x00,
0x00, 0xff, 0xff, 0x5f, 0x2f, 0x55, 0x31, 0xf8, 0x10, 0x00, 0x00,
// 1807 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x4b, 0x6f, 0xdc, 0xc8,
0x11, 0x06, 0x67, 0x86, 0xa3, 0x61, 0x8d, 0x24, 0x0b, 0x1d, 0x63, 0x97, 0xbb, 0x09, 0x82, 0x09,
0x91, 0x6c, 0x94, 0xc7, 0x3a, 0x0b, 0x19, 0x79, 0x60, 0xb1, 0xbb, 0x80, 0x1e, 0xb6, 0x23, 0x5b,
0xb6, 0xe5, 0x96, 0xac, 0x9c, 0x82, 0x45, 0x8b, 0xec, 0x99, 0x69, 0x98, 0x43, 0x32, 0x4d, 0x52,
0x12, 0x73, 0xce, 0xef, 0x08, 0x10, 0x20, 0xb9, 0x07, 0x41, 0x8e, 0x01, 0x72, 0xcf, 0x0f, 0xc8,
0x5f, 0xc9, 0x35, 0xa8, 0x7e, 0x90, 0x4d, 0x69, 0x6c, 0x38, 0x40, 0xb0, 0xb7, 0xfe, 0xaa, 0x6a,
0xaa, 0xab, 0xab, 0xab, 0xbe, 0x2e, 0x0e, 0x6c, 0x8b, 0xac, 0xe2, 0x32, 0x63, 0xe9, 0x83, 0x42,
0xe6, 0x55, 0x4e, 0x26, 0x16, 0x47, 0x7f, 0x18, 0xc2, 0xf8, 0x2c, 0xaf, 0x65, 0xcc, 0xc9, 0x36,
0x0c, 0x8e, 0x8f, 0x42, 0x6f, 0xe6, 0xed, 0x0e, 0xe9, 0xe0, 0xf8, 0x88, 0x10, 0x18, 0xbd, 0x60,
0x2b, 0x1e, 0x0e, 0x66, 0xde, 0x6e, 0x40, 0xd5, 0x1a, 0x65, 0xe7, 0x4d, 0xc1, 0xc3, 0xa1, 0x96,
0xe1, 0x9a, 0x7c, 0x0c, 0x93, 0xd7, 0x25, 0x7a, 0x5b, 0xf1, 0x70, 0xa4, 0xe4, 0x2d, 0x46, 0xdd,
0x29, 0x2b, 0xcb, 0xeb, 0x5c, 0x26, 0xa1, 0xaf, 0x75, 0x16, 0x93, 0x1d, 0x18, 0xbe, 0xa6, 0x27,
0xe1, 0x58, 0x89, 0x71, 0x49, 0x42, 0xd8, 0x38, 0xe2, 0x73, 0x56, 0xa7, 0x55, 0xb8, 0x31, 0xf3,
0x76, 0x27, 0xd4, 0x42, 0xf4, 0x73, 0xce, 0x53, 0xbe, 0x90, 0x6c, 0x1e, 0x4e, 0xb4, 0x1f, 0x8b,
0xc9, 0x03, 0x20, 0xc7, 0x59, 0xc9, 0xe3, 0x5a, 0xf2, 0xb3, 0x37, 0xa2, 0xb8, 0xe0, 0x52, 0xcc,
0x9b, 0x30, 0x50, 0x0e, 0xd6, 0x68, 0x70, 0x97, 0xe7, 0xbc, 0x62, 0xb8, 0x37, 0x28, 0x57, 0x16,
0x92, 0x08, 0x36, 0xcf, 0x96, 0x4c, 0xf2, 0xe4, 0x8c, 0xc7, 0x92, 0x57, 0xe1, 0x54, 0xa9, 0x7b,
0x32, 0xb4, 0x79, 0x29, 0x17, 0x2c, 0x13, 0xbf, 0x67, 0x95, 0xc8, 0xb3, 0x70, 0x53, 0xdb, 0xb8,
0x32, 0xcc, 0x12, 0xcd, 0x53, 0x1e, 0x6e, 0xe9, 0x2c, 0xe1, 0x9a, 0x7c, 0x07, 0x02, 0x73, 0x18,
0x7a, 0x1a, 0x6e, 0x2b, 0x45, 0x27, 0x88, 0xfe, 0xee, 0x41, 0x70, 0xc4, 0xca, 0xe5, 0x65, 0xce,
0x64, 0xf2, 0x5e, 0x37, 0xf1, 0x29, 0xf8, 0x31, 0x4f, 0xd3, 0x32, 0x1c, 0xce, 0x86, 0xbb, 0xd3,
0xbd, 0x0f, 0x1f, 0xb4, 0x57, 0xdc, 0xfa, 0x39, 0xe4, 0x69, 0x4a, 0xb5, 0x15, 0xf9, 0x0c, 0x82,
0x8a, 0xaf, 0x8a, 0x94, 0x55, 0xbc, 0x0c, 0x47, 0xea, 0x27, 0xa4, 0xfb, 0xc9, 0xb9, 0x51, 0xd1,
0xce, 0xe8, 0xce, 0x41, 0xfd, 0xbb, 0x07, 0x8d, 0xfe, 0x3d, 0x82, 0xad, 0xde, 0x76, 0x64, 0x13,
0xbc, 0x1b, 0x15, 0xb9, 0x4f, 0xbd, 0x1b, 0x44, 0x8d, 0x8a, 0xda, 0xa7, 0x5e, 0x83, 0xe8, 0x5a,
0x55, 0x8e, 0x4f, 0xbd, 0x6b, 0x44, 0x4b, 0x55, 0x2f, 0x3e, 0xf5, 0x96, 0xe4, 0x47, 0xb0, 0xf1,
0xbb, 0x9a, 0x4b, 0xc1, 0xcb, 0xd0, 0x57, 0xd1, 0xdd, 0xeb, 0xa2, 0x7b, 0x55, 0x73, 0xd9, 0x50,
0xab, 0xc7, 0x6c, 0xa8, 0x5a, 0xd3, 0x85, 0xa3, 0xd6, 0x28, 0xab, 0xb0, 0x2e, 0x37, 0xb4, 0x0c,
0xd7, 0x26, 0x8b, 0xba, 0x5a, 0x30, 0x8b, 0x3f, 0x87, 0x11, 0xbb, 0xe1, 0x65, 0x18, 0x28, 0xff,
0xdf, 0x7b, 0x4b, 0xc2, 0x1e, 0xec, 0xdf, 0xf0, 0xf2, 0x51, 0x56, 0xc9, 0x86, 0x2a, 0x73, 0xf2,
0x43, 0x18, 0xc7, 0x79, 0x9a, 0xcb, 0x32, 0x84, 0xdb, 0x81, 0x1d, 0xa2, 0x9c, 0x1a, 0x35, 0xd9,
0x85, 0x71, 0xca, 0x17, 0x3c, 0x4b, 0x54, 0xdd, 0x4c, 0xf7, 0x76, 0x3a, 0xc3, 0x13, 0x25, 0xa7,
0x46, 0x4f, 0x3e, 0x87, 0xcd, 0x8a, 0x5d, 0xa6, 0xfc, 0x65, 0x81, 0x59, 0x2c, 0x55, 0x0d, 0x4d,
0xf7, 0x3e, 0x70, 0xee, 0xc3, 0xd1, 0xd2, 0x9e, 0x2d, 0xf9, 0x02, 0x36, 0xe7, 0x82, 0xa7, 0x89,
0xfd, 0xed, 0x96, 0x0a, 0x2a, 0xec, 0x7e, 0x4b, 0x79, 0xc6, 0x56, 0xf8, 0x8b, 0xc7, 0x68, 0x46,
0x7b, 0xd6, 0xe4, 0xbb, 0x00, 0x95, 0x58, 0xf1, 0xc7, 0xb9, 0x5c, 0xb1, 0xca, 0x94, 0xa1, 0x23,
0x21, 0x5f, 0xc2, 0x56, 0xc2, 0x63, 0xb1, 0x62, 0xe9, 0x69, 0xca, 0x62, 0x5e, 0x86, 0xf7, 0x54,
0x68, 0x6e, 0x75, 0xb9, 0x6a, 0xda, 0xb7, 0xfe, 0xf8, 0x09, 0x04, 0x6d, 0xfa, 0xb0, 0xbf, 0xdf,
0xf0, 0x46, 0x15, 0x43, 0x40, 0x71, 0x49, 0xbe, 0x0f, 0xfe, 0x15, 0x4b, 0x6b, 0x5d, 0xc8, 0xd3,
0xbd, 0xed, 0xce, 0xeb, 0xfe, 0x8d, 0x28, 0xa9, 0x56, 0x7e, 0x3e, 0xf8, 0x95, 0x17, 0x3d, 0x81,
0xad, 0xde, 0x46, 0x18, 0xb8, 0x28, 0x1f, 0x65, 0xf3, 0x5c, 0xc6, 0x3c, 0x51, 0x3e, 0x27, 0xd4,
0x91, 0x90, 0x0f, 0x60, 0x9c, 0x88, 0x85, 0xa8, 0x4a, 0x53, 0x6e, 0x06, 0x45, 0xff, 0xf0, 0x60,
0xd3, 0xcd, 0x26, 0xf9, 0x31, 0xec, 0x5c, 0x71, 0x59, 0x89, 0x98, 0xa5, 0xe7, 0x62, 0xc5, 0x71,
0x63, 0xf5, 0x93, 0x09, 0xbd, 0x23, 0x27, 0x9f, 0xc1, 0xb8, 0xcc, 0x65, 0x75, 0xd0, 0xa8, 0xaa,
0x7d, 0x57, 0x96, 0x8d, 0x1d, 0xf2, 0xd4, 0xb5, 0x64, 0x45, 0x21, 0xb2, 0x85, 0xe5, 0x42, 0x8b,
0xc9, 0x27, 0xb0, 0x3d, 0x17, 0x37, 0x8f, 0x85, 0x2c, 0xab, 0xc3, 0x3c, 0xad, 0x57, 0x99, 0xaa,
0xe0, 0x09, 0xbd, 0x25, 0x7d, 0x3a, 0x9a, 0x78, 0x3b, 0x83, 0xa7, 0xa3, 0x89, 0xbf, 0x33, 0x8e,
0x0a, 0xd8, 0xee, 0xef, 0x84, 0x6d, 0x69, 0x83, 0x50, 0x9c, 0xa0, 0xd3, 0xdb, 0x93, 0x91, 0x19,
0x4c, 0x13, 0x51, 0x16, 0x29, 0x6b, 0x1c, 0xda, 0x70, 0x45, 0xc8, 0x81, 0x57, 0xa2, 0x14, 0x97,
0xa9, 0xa6, 0xf2, 0x09, 0xb5, 0x30, 0x5a, 0x80, 0xaf, 0xca, 0xda, 0x21, 0xa1, 0xc0, 0x92, 0x90,
0xa2, 0xfe, 0x81, 0x43, 0xfd, 0x3b, 0x30, 0xfc, 0x35, 0xbf, 0x31, 0xaf, 0x01, 0x2e, 0x5b, 0xaa,
0x1a, 0x39, 0x54, 0x75, 0x1f, 0xfc, 0x0b, 0x75, 0xed, 0x9a, 0x42, 0x34, 0x88, 0xbe, 0x82, 0xb1,
0x6e, 0x8b, 0xd6, 0xb3, 0xe7, 0x78, 0x9e, 0xc1, 0xf4, 0xa5, 0x14, 0x3c, 0xab, 0x34, 0xf9, 0x98,
0x23, 0x38, 0xa2, 0xe8, 0x6f, 0x1e, 0x8c, 0xd4, 0x2d, 0x45, 0xb0, 0x99, 0xf2, 0x05, 0x8b, 0x9b,
0x83, 0xbc, 0xce, 0x92, 0x32, 0xf4, 0x66, 0xc3, 0xdd, 0x21, 0xed, 0xc9, 0xb0, 0x3c, 0x2e, 0xb5,
0x76, 0x30, 0x1b, 0xee, 0x06, 0xd4, 0x20, 0x0c, 0x2d, 0x65, 0x97, 0x3c, 0x35, 0x47, 0xd0, 0x00,
0xad, 0x0b, 0xc9, 0xe7, 0xe2, 0xc6, 0x1c, 0xc3, 0x20, 0x94, 0x97, 0xf5, 0x1c, 0xe5, 0xfa, 0x24,
0x06, 0xe1, 0x01, 0x2e, 0x59, 0xd9, 0x32, 0x12, 0xae, 0xd1, 0x73, 0x19, 0xb3, 0xd4, 0x52, 0x92,
0x06, 0xd1, 0x3f, 0x3d, 0x7c, 0xc8, 0x34, 0xc5, 0xde, 0xc9, 0xf0, 0x47, 0x30, 0x41, 0xfa, 0xfd,
0xfa, 0x8a, 0x49, 0x73, 0xe0, 0x0d, 0xc4, 0x17, 0x4c, 0x92, 0x9f, 0xc1, 0x58, 0x35, 0xc7, 0x1a,
0xba, 0xb7, 0xee, 0x54, 0x56, 0xa9, 0x31, 0x6b, 0x09, 0x71, 0xe4, 0x10, 0x62, 0x7b, 0x58, 0xdf,
0x3d, 0xec, 0xa7, 0xe0, 0x23, 0xb3, 0x36, 0x2a, 0xfa, 0xb5, 0x9e, 0x35, 0xff, 0x6a, 0xab, 0x68,
0x01, 0x5b, 0xbd, 0x1d, 0xdb, 0x9d, 0xbc, 0xfe, 0x4e, 0x5d, 0xa3, 0x07, 0xa6, 0xb1, 0xb1, 0x39,
0x4a, 0x9e, 0xf2, 0xb8, 0xe2, 0x89, 0xa9, 0xba, 0x16, 0x5b, 0xb2, 0x18, 0xb5, 0x64, 0x11, 0xfd,
0xc9, 0xeb, 0x76, 0x52, 0x11, 0x60, 0xd1, 0xc6, 0xf9, 0x6a, 0xc5, 0xb2, 0xc4, 0x6c, 0x66, 0x21,
0x66, 0x32, 0xb9, 0x34, 0x9b, 0x0d, 0x92, 0x4b, 0xc4, 0xb2, 0x30, 0x77, 0x3a, 0x90, 0x05, 0x56,
0xd3, 0x8a, 0xb3, 0xb2, 0x96, 0x7c, 0xc5, 0xb3, 0xca, 0xec, 0xe2, 0x8a, 0xc8, 0x87, 0xb0, 0x51,
0xb1, 0xc5, 0xd7, 0x18, 0x83, 0xb9, 0xdb, 0x8a, 0x2d, 0x9e, 0xf1, 0x86, 0x7c, 0x1b, 0x02, 0xc5,
0xa0, 0x4a, 0xa5, 0x2f, 0x78, 0xa2, 0x04, 0xcf, 0x78, 0x13, 0xfd, 0x75, 0x00, 0xe3, 0x33, 0x2e,
0xaf, 0xb8, 0x7c, 0xaf, 0x37, 0xdb, 0x9d, 0x94, 0x86, 0xef, 0x98, 0x94, 0x46, 0xeb, 0x27, 0x25,
0xbf, 0x9b, 0x94, 0xee, 0x83, 0x7f, 0x26, 0xe3, 0xe3, 0x23, 0x15, 0xd1, 0x90, 0x6a, 0x80, 0xf5,
0xb9, 0x1f, 0x57, 0xe2, 0x8a, 0x9b, 0xf1, 0xc9, 0xa0, 0x3b, 0x4f, 0xf9, 0x64, 0xcd, 0xcc, 0xf2,
0xbf, 0x4e, 0x51, 0xb6, 0x69, 0xc1, 0x69, 0xda, 0x08, 0x36, 0x71, 0x94, 0x4a, 0x58, 0xc5, 0x9e,
0x9e, 0xbd, 0x7c, 0x61, 0xe7, 0x27, 0x57, 0x16, 0xfd, 0xd1, 0x83, 0xf1, 0x09, 0x6b, 0xf2, 0xba,
0xba, 0x53, 0xff, 0x33, 0x98, 0xee, 0x17, 0x45, 0x2a, 0xe2, 0x5e, 0xcf, 0x3b, 0x22, 0xb4, 0x78,
0xee, 0xdc, 0xa3, 0xce, 0xa1, 0x2b, 0xc2, 0x27, 0xe6, 0x50, 0x8d, 0x45, 0x7a, 0xc6, 0x71, 0x9e,
0x18, 0x3d, 0x0d, 0x29, 0x25, 0x26, 0x7b, 0xbf, 0xae, 0xf2, 0x79, 0x9a, 0x5f, 0xab, 0xac, 0x4e,
0x68, 0x8b, 0xa3, 0x7f, 0x0d, 0x60, 0xf4, 0x4d, 0x8d, 0x32, 0x9b, 0xe0, 0x09, 0x53, 0x54, 0x9e,
0x68, 0x07, 0x9b, 0x0d, 0x67, 0xb0, 0x09, 0x61, 0xa3, 0x91, 0x2c, 0x5b, 0xf0, 0x32, 0x9c, 0x28,
0x5e, 0xb3, 0x50, 0x69, 0x54, 0x07, 0xeb, 0x89, 0x26, 0xa0, 0x16, 0xb6, 0x1d, 0x09, 0x4e, 0x47,
0xfe, 0xd4, 0x0c, 0x3f, 0xd3, 0xdb, 0xe3, 0xc2, 0xba, 0x99, 0xe7, 0xff, 0xf7, 0x8e, 0xff, 0xc7,
0x03, 0xbf, 0x6d, 0xde, 0xc3, 0x7e, 0xf3, 0x1e, 0x76, 0xcd, 0x7b, 0x74, 0x60, 0x9b, 0xf7, 0xe8,
0x00, 0x31, 0x3d, 0xb5, 0xcd, 0x4b, 0x4f, 0xf1, 0xb2, 0x9e, 0xc8, 0xbc, 0x2e, 0x0e, 0x1a, 0x7d,
0xab, 0x01, 0x6d, 0x31, 0x56, 0xfc, 0x6f, 0x96, 0x5c, 0x9a, 0x54, 0x07, 0xd4, 0x20, 0xec, 0x8f,
0x13, 0x45, 0x75, 0x3a, 0xb9, 0x1a, 0x90, 0x1f, 0x80, 0x4f, 0x31, 0x79, 0x2a, 0xc3, 0xbd, 0x7b,
0x51, 0x62, 0xaa, 0xb5, 0xe8, 0x54, 0x7f, 0x12, 0x99, 0x46, 0xb1, 0x1f, 0x48, 0x3f, 0x81, 0xf1,
0xd9, 0x52, 0xcc, 0x2b, 0x3b, 0x42, 0x7e, 0xcb, 0xa1, 0x4a, 0xb1, 0xe2, 0x4a, 0x47, 0x8d, 0x49,
0xf4, 0x0a, 0x82, 0x56, 0xd8, 0x85, 0xe3, 0xb9, 0xe1, 0x10, 0x18, 0xbd, 0xce, 0x44, 0x65, 0x29,
0x02, 0xd7, 0x78, 0xd8, 0x57, 0x35, 0xcb, 0x2a, 0x51, 0x35, 0x96, 0x22, 0x2c, 0x8e, 0x1e, 0x9a,
0xf0, 0xd1, 0xdd, 0xeb, 0xa2, 0xe0, 0xd2, 0xd0, 0x8d, 0x06, 0x6a, 0x93, 0xfc, 0x9a, 0xeb, 0xb7,
0x63, 0x48, 0x35, 0x88, 0x7e, 0x0b, 0xc1, 0x7e, 0xca, 0x65, 0x45, 0xeb, 0x94, 0xaf, 0x7b, 0xd3,
0x55, 0xa3, 0x9a, 0x08, 0x70, 0xdd, 0x51, 0xcb, 0xf0, 0x16, 0xb5, 0x3c, 0x63, 0x05, 0x3b, 0x3e,
0x52, 0x75, 0x3e, 0xa4, 0x06, 0x45, 0x7f, 0xf6, 0x60, 0x84, 0x1c, 0xe6, 0xb8, 0x1e, 0xbd, 0x8b,
0xff, 0x4e, 0x65, 0x7e, 0x25, 0x12, 0x2e, 0xed, 0xe1, 0x2c, 0x56, 0x49, 0x8f, 0x97, 0xbc, 0x1d,
0x1d, 0x0c, 0xc2, 0x5a, 0xc3, 0xef, 0x27, 0xdb, 0x4b, 0x4e, 0xad, 0xa1, 0x98, 0x6a, 0x25, 0x8e,
0x87, 0x67, 0x75, 0xc1, 0xe5, 0x7e, 0xb2, 0x12, 0x76, 0xae, 0x72, 0x24, 0xd1, 0x57, 0xfa, 0x8b,
0xec, 0x0e, 0x13, 0x7a, 0xeb, 0xbf, 0xde, 0x6e, 0x47, 0x1e, 0xfd, 0xc5, 0x83, 0x8d, 0xe7, 0x66,
0x8e, 0x73, 0x4f, 0xe1, 0xbd, 0xf5, 0x14, 0x83, 0xde, 0x29, 0xf6, 0xe0, 0xbe, 0xb5, 0xe9, 0xed,
0xaf, 0xb3, 0xb0, 0x56, 0x67, 0x32, 0x3a, 0x6a, 0x2f, 0xeb, 0x7d, 0x3e, 0xc8, 0xce, 0xfb, 0x36,
0xeb, 0x2e, 0xfc, 0xce, 0xad, 0xcc, 0x60, 0x6a, 0x3f, 0x44, 0xf3, 0xd4, 0x3e, 0x4c, 0xae, 0x28,
0xda, 0x83, 0xf1, 0x61, 0x9e, 0xcd, 0xc5, 0x82, 0xec, 0xc2, 0x68, 0xbf, 0xae, 0x96, 0xca, 0xe3,
0x74, 0xef, 0xbe, 0xd3, 0xf8, 0x75, 0xb5, 0xd4, 0x36, 0x54, 0x59, 0x44, 0x5f, 0x00, 0x74, 0x32,
0x7c, 0x5d, 0xba, 0xdb, 0x78, 0xc1, 0xaf, 0xb1, 0x64, 0x4a, 0x33, 0xc6, 0xaf, 0xd1, 0x44, 0x35,
0x10, 0xf7, 0x1c, 0xc6, 0xcb, 0x27, 0xb0, 0xed, 0x4a, 0xdb, 0x93, 0xdd, 0x92, 0x92, 0x5f, 0x42,
0x70, 0x92, 0x2f, 0x2e, 0x04, 0xb7, 0xdd, 0x30, 0xdd, 0xfb, 0xc8, 0xf9, 0x18, 0xb3, 0x2a, 0x13,
0x6f, 0x67, 0x1b, 0x3d, 0x86, 0x7b, 0xb7, 0xb4, 0xe4, 0x21, 0xf2, 0x16, 0xce, 0xe5, 0x7a, 0xb0,
0x7c, 0x9b, 0x27, 0xb4, 0xa0, 0xd6, 0x32, 0x6a, 0x7a, 0x7e, 0x50, 0xd6, 0x66, 0xde, 0xbb, 0xd5,
0x0f, 0x79, 0x29, 0xda, 0xd7, 0xce, 0xa7, 0x2d, 0x26, 0xbf, 0x80, 0xe0, 0x51, 0x16, 0xe7, 0x89,
0xc8, 0x16, 0x76, 0xe8, 0x0b, 0x7b, 0x5f, 0x9e, 0xf5, 0x2a, 0xb3, 0x06, 0xb4, 0x33, 0x8d, 0x5e,
0xc0, 0x76, 0x5f, 0xb9, 0x76, 0xbc, 0x6e, 0x47, 0xf2, 0x81, 0x33, 0x92, 0xb7, 0x31, 0x0e, 0x9d,
0xca, 0xff, 0x12, 0x82, 0x83, 0x5a, 0xa4, 0xc9, 0x71, 0x36, 0xcf, 0x91, 0xc4, 0x2f, 0xb8, 0x2c,
0xbb, 0xce, 0xb1, 0x10, 0x0b, 0x1f, 0xf9, 0xbc, 0x65, 0x33, 0x83, 0x2e, 0xc7, 0xea, 0x0f, 0xa7,
0x87, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x58, 0xd6, 0x93, 0x3e, 0x82, 0x12, 0x00, 0x00,
}

View File

@ -214,6 +214,27 @@ message AuthConfig {
bool SuperAdminNewUsers = 1; // SuperAdminNewUsers configuration option that specifies which users will auto become super admin
}
message OrganizationConfig {
string OrganizationID = 1; // OrganizationID is the ID of the organization this config belogs to
LogViewerConfig LogViewer = 2; // LogViewer is the organization configuration for log viewer
}
message LogViewerConfig {
repeated LogViewerColumn Columns = 1; // Columns is the array of columns in the log viewer
}
message LogViewerColumn {
string Name = 1; // Name is the unique identifier of the log viewer column
int32 Position = 2; // Position is the position of the column in the log viewer's array of columns
repeated ColumnEncoding Encodings = 3; // Encodings is the array of encoded properties associated with a log viewer column
}
message ColumnEncoding {
string Type = 1; // Type is the purpose of the encoding, for example: severity color
string Value = 2; // Value is what the encoding corresponds to
string Name = 3; // Name is the optional encoding name
}
message BuildInfo {
string Version = 1; // Version is a descriptive git SHA identifier
string Commit = 2; // Commit is an abbreviated SHA

236
bolt/org_config.go Normal file
View File

@ -0,0 +1,236 @@
package bolt
import (
"context"
"fmt"
"github.com/boltdb/bolt"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/bolt/internal"
)
// Ensure OrganizationConfigStore implements chronograf.OrganizationConfigStore.
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
// OrganizationConfigBucket is used to store chronograf organization configurations
var OrganizationConfigBucket = []byte("OrganizationConfigV1")
// OrganizationConfigStore uses bolt to store and retrieve organization configurations
type OrganizationConfigStore struct {
client *Client
}
func (s *OrganizationConfigStore) Migrate(ctx context.Context) error {
return nil
}
// Get retrieves an OrganizationConfig from the store
func (s *OrganizationConfigStore) Get(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
var c chronograf.OrganizationConfig
err := s.client.db.View(func(tx *bolt.Tx) error {
return s.get(ctx, tx, orgID, &c)
})
if err != nil {
return nil, err
}
return &c, nil
}
func (s *OrganizationConfigStore) get(ctx context.Context, tx *bolt.Tx, orgID string, c *chronograf.OrganizationConfig) error {
v := tx.Bucket(OrganizationConfigBucket).Get([]byte(orgID))
if len(v) == 0 {
return chronograf.ErrOrganizationConfigNotFound
}
return internal.UnmarshalOrganizationConfig(v, c)
}
// FindOrCreate gets an OrganizationConfig from the store or creates one if none exists for this organization
func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
var c chronograf.OrganizationConfig
err := s.client.db.Update(func(tx *bolt.Tx) error {
err := s.get(ctx, tx, orgID, &c)
if err == chronograf.ErrOrganizationConfigNotFound {
c = newOrganizationConfig(orgID)
return s.put(ctx, tx, &c)
}
return err
})
if err != nil {
return nil, err
}
return &c, nil
}
// Put replaces the OrganizationConfig in the store
func (s *OrganizationConfigStore) Put(ctx context.Context, c *chronograf.OrganizationConfig) error {
return s.client.db.Update(func(tx *bolt.Tx) error {
return s.put(ctx, tx, c)
})
}
func (s *OrganizationConfigStore) put(ctx context.Context, tx *bolt.Tx, c *chronograf.OrganizationConfig) error {
if c == nil {
return fmt.Errorf("config provided was nil")
}
if v, err := internal.MarshalOrganizationConfig(c); err != nil {
return err
} else if err := tx.Bucket(OrganizationConfigBucket).Put([]byte(c.OrganizationID), v); err != nil {
return err
}
return nil
}
func newOrganizationConfig(orgID string) chronograf.OrganizationConfig {
return chronograf.OrganizationConfig{
OrganizationID: orgID,
LogViewer: chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "time",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "severity",
Position: 1,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
{
Type: "label",
Value: "text",
},
{
Type: "color",
Name: "emerg",
Value: "ruby",
},
{
Type: "color",
Name: "alert",
Value: "fire",
},
{
Type: "color",
Name: "crit",
Value: "curacao",
},
{
Type: "color",
Name: "err",
Value: "tiger",
},
{
Type: "color",
Name: "warning",
Value: "pineapple",
},
{
Type: "color",
Name: "notice",
Value: "rainforest",
},
{
Type: "color",
Name: "info",
Value: "star",
},
{
Type: "color",
Name: "debug",
Value: "wolf",
},
},
},
{
Name: "timestamp",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Proc ID",
},
},
},
{
Name: "appname",
Position: 6,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
}
}

1160
bolt/org_config_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,15 @@
"name": "MySQL Reads/Second",
"queries": [
{
"query": "SELECT non_negative_derivative(last(\"commands_select\"), 1s) AS selects_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": [
"\"server\""
],
"query":
"SELECT non_negative_derivative(last(\"commands_select\"), 1s) AS selects_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": ["\"server\""],
"wheres": []
},
{
"query":
"SELECT non_negative_derivative(last(\"com_select\"), 1s) AS selects_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": ["\"server\""],
"wheres": []
}
]
@ -30,10 +35,15 @@
"name": "MySQL Writes/Second",
"queries": [
{
"query": "SELECT non_negative_derivative(last(\"commands_insert\"), 1s) AS inserts_per_second, non_negative_derivative(last(\"commands_update\"), 1s) AS updates_per_second, non_negative_derivative(last(\"commands_delete\"), 1s) AS deletes_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": [
"\"server\""
],
"query":
"SELECT non_negative_derivative(last(\"commands_insert\"), 1s) AS inserts_per_second, non_negative_derivative(last(\"commands_update\"), 1s) AS updates_per_second, non_negative_derivative(last(\"commands_delete\"), 1s) AS deletes_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": ["\"server\""],
"wheres": []
},
{
"query":
"SELECT non_negative_derivative(last(\"com_insert\"), 1s) AS inserts_per_second, non_negative_derivative(last(\"com_update\"), 1s) AS updates_per_second, non_negative_derivative(last(\"com_delete\"), 1s) AS deletes_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": ["\"server\""],
"wheres": []
}
]
@ -47,10 +57,9 @@
"name": "MySQL Connections/Second",
"queries": [
{
"query": "SELECT non_negative_derivative(last(\"threads_connected\"), 1s) AS cxn_per_second, non_negative_derivative(last(\"threads_running\"), 1s) AS threads_running_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": [
"\"server\""
],
"query":
"SELECT non_negative_derivative(last(\"threads_connected\"), 1s) AS cxn_per_second, non_negative_derivative(last(\"threads_running\"), 1s) AS threads_running_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": ["\"server\""],
"wheres": []
}
]
@ -64,10 +73,9 @@
"name": "MySQL Connections Errors/Second",
"queries": [
{
"query": "SELECT non_negative_derivative(last(\"connection_errors_max_connections\"), 1s) AS cxn_errors_per_second, non_negative_derivative(last(\"connection_errors_internal\"), 1s) AS internal_cxn_errors_per_second, non_negative_derivative(last(\"aborted_connects\"), 1s) AS cxn_aborted_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": [
"\"server\""
],
"query":
"SELECT non_negative_derivative(last(\"connection_errors_max_connections\"), 1s) AS cxn_errors_per_second, non_negative_derivative(last(\"connection_errors_internal\"), 1s) AS internal_cxn_errors_per_second, non_negative_derivative(last(\"aborted_connects\"), 1s) AS cxn_aborted_per_second FROM \":db:\".\":rp:\".\"mysql\"",
"groupbys": ["\"server\""],
"wheres": []
}
]

View File

@ -15,7 +15,7 @@
{
"query": "select max(\"percent_packet_loss\") as \"packet_loss\" from ping",
"groupbys": [
"\"server\""
"\"url\""
],
"wheres": []
}
@ -32,7 +32,7 @@
{
"query": "select mean(\"average_response_ms\") as \"average\", mean(\"minimum_response_ms\") as \"min\", mean(\"maximum_response_ms\") as \"max\" from ping",
"groupbys": [
"\"server\""
"\"url\""
],
"wheres": []
}

View File

@ -38,6 +38,7 @@ const (
ErrInvalidCellOptionsText = Error("invalid text wrapping option. Valid wrappings are 'truncate', 'wrap', and 'single line'")
ErrInvalidCellOptionsSort = Error("cell options sortby cannot be empty'")
ErrInvalidCellOptionsColumns = Error("cell options columns cannot be empty'")
ErrOrganizationConfigNotFound = Error("could not find organization config")
)
// Error is a domain error encountered while processing chronograf requests
@ -736,22 +737,18 @@ type OrganizationsStore interface {
DefaultOrganization(ctx context.Context) (*Organization, error)
}
// AuthConfig is the global application config section for auth parameters
type AuthConfig struct {
// SuperAdminNewUsers should be true by default to give a seamless upgrade to
// 1.4.0 for legacy users. It means that all new users will by default receive
// SuperAdmin status. If a SuperAdmin wants to change this behavior, they
// can toggle it off via the Chronograf UI, in which case newly authenticating
// users will simply receive whatever role they would otherwise receive.
SuperAdminNewUsers bool `json:"superAdminNewUsers"`
}
// Config is the global application Config for parameters that can be set via
// API, with different sections, such as Auth
type Config struct {
Auth AuthConfig `json:"auth"`
}
// AuthConfig is the global application config section for auth parameters
type AuthConfig struct {
// SuperAdminNewUsers configuration option that specifies which users will auto become super admin
SuperAdminNewUsers bool `json:"superAdminNewUsers"`
}
// ConfigStore is the storage and retrieval of global application Config
type ConfigStore interface {
// Initialize creates the initial configuration
@ -762,6 +759,40 @@ type ConfigStore interface {
Update(context.Context, *Config) error
}
// OrganizationConfig is the organization config for parameters that can
// be set via API, with different sections, such as LogViewer
type OrganizationConfig struct {
OrganizationID string `json:"organization"`
LogViewer LogViewerConfig `json:"logViewer"`
}
// LogViewerConfig is the configuration settings for the Log Viewer UI
type LogViewerConfig struct {
Columns []LogViewerColumn `json:"columns"`
}
// LogViewerColumn is a specific column of the Log Viewer UI
type LogViewerColumn struct {
Name string `json:"name"`
Position int32 `json:"position"`
Encodings []ColumnEncoding `json:"encodings"`
}
// ColumnEncoding is the settings for a specific column of the Log Viewer UI
type ColumnEncoding struct {
Type string `json:"type"`
Value string `json:"value"`
Name string `json:"name,omitempty"`
}
// OrganizationConfigStore is the storage and retrieval of organization Configs
type OrganizationConfigStore interface {
// FindOrCreate gets an existing OrganizationConfig and creates one if none exists
FindOrCreate(ctx context.Context, orgID string) (*OrganizationConfig, error)
// Put replaces the whole organization config in the OrganizationConfigStore
Put(context.Context, *OrganizationConfig) error
}
// BuildInfo is sent to the usage client to track versions and commits
type BuildInfo struct {
Version string

View File

@ -2657,6 +2657,705 @@ func TestServer(t *testing.T) {
`,
},
},
{
name: "GET /org_config",
subName: "default org",
fields: fields{
Users: []chronograf.User{
{
ID: 1, // This is artificial, but should be reflective of the users actual ID
Name: "billibob",
Provider: "github",
Scheme: "oauth2",
SuperAdmin: true,
Roles: []chronograf.Role{
{
Name: "admin",
Organization: "default",
},
},
},
},
},
args: args{
server: &server.Server{
GithubClientID: "not empty",
GithubClientSecret: "not empty",
},
method: "GET",
path: "/chronograf/v1/org_config",
principal: oauth2.Principal{
Organization: "default",
Subject: "billibob",
Issuer: "github",
},
},
wants: wants{
statusCode: 200,
body: `
{
"links": {
"self": "\/chronograf\/v1\/org_config",
"logViewer": "\/chronograf\/v1\/org_config\/logviewer"
},
"organization": "default",
"logViewer": {
"columns": [
{
"name": "time",
"position": 0,
"encodings": [
{
"type": "visibility",
"value": "hidden"
}
]
},
{
"name": "severity",
"position": 1,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "label",
"value": "icon"
},
{
"type": "label",
"value": "text"
},
{
"type": "color",
"value": "ruby",
"name": "emerg"
},
{
"type": "color",
"value": "fire",
"name": "alert"
},
{
"type": "color",
"value": "curacao",
"name": "crit"
},
{
"type": "color",
"value": "tiger",
"name": "err"
},
{
"type": "color",
"value": "pineapple",
"name": "warning"
},
{
"type": "color",
"value": "rainforest",
"name": "notice"
},
{
"type": "color",
"value": "star",
"name": "info"
},
{
"type": "color",
"value": "wolf",
"name": "debug"
}
]
},
{
"name": "timestamp",
"position": 2,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "message",
"position": 3,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "facility",
"position": 4,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "procid",
"position": 5,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "displayName",
"value": "Proc ID"
}
]
},
{
"name": "appname",
"position": 6,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "displayName",
"value": "Application"
}
]
},
{
"name": "host",
"position": 7,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
}
]
}
}
`,
},
},
{
name: "GET /org_config/logviewer",
subName: "default org",
fields: fields{
Users: []chronograf.User{
{
ID: 1, // This is artificial, but should be reflective of the users actual ID
Name: "billibob",
Provider: "github",
Scheme: "oauth2",
SuperAdmin: true,
Roles: []chronograf.Role{
{
Name: "admin",
Organization: "default",
},
},
},
},
},
args: args{
server: &server.Server{
GithubClientID: "not empty",
GithubClientSecret: "not empty",
},
method: "GET",
path: "/chronograf/v1/org_config/logviewer",
principal: oauth2.Principal{
Organization: "default",
Subject: "billibob",
Issuer: "github",
},
},
wants: wants{
statusCode: 200,
body: `
{
"links": {
"self": "\/chronograf\/v1\/org_config/logviewer"
},
"columns": [
{
"name": "time",
"position": 0,
"encodings": [
{
"type": "visibility",
"value": "hidden"
}
]
},
{
"name": "severity",
"position": 1,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "label",
"value": "icon"
},
{
"type": "label",
"value": "text"
},
{
"type": "color",
"value": "ruby",
"name": "emerg"
},
{
"type": "color",
"value": "fire",
"name": "alert"
},
{
"type": "color",
"value": "curacao",
"name": "crit"
},
{
"type": "color",
"value": "tiger",
"name": "err"
},
{
"type": "color",
"value": "pineapple",
"name": "warning"
},
{
"type": "color",
"value": "rainforest",
"name": "notice"
},
{
"type": "color",
"value": "star",
"name": "info"
},
{
"type": "color",
"value": "wolf",
"name": "debug"
}
]
},
{
"name": "timestamp",
"position": 2,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "message",
"position": 3,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "facility",
"position": 4,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "procid",
"position": 5,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "displayName",
"value": "Proc ID"
}
]
},
{
"name": "appname",
"position": 6,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "displayName",
"value": "Application"
}
]
},
{
"name": "host",
"position": 7,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
}
]
}
`,
},
},
{
name: "PUT /org_config/logviewer",
subName: "default org",
fields: fields{
Config: &chronograf.Config{
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: true,
},
},
Organizations: []chronograf.Organization{
{
ID: "1",
Name: "cool",
DefaultRole: roles.ViewerRoleName,
},
},
Users: []chronograf.User{
{
ID: 1, // This is artificial, but should be reflective of the users actual ID
Name: "billibob",
Provider: "github",
Scheme: "oauth2",
SuperAdmin: true,
Roles: []chronograf.Role{
{
Name: "admin",
Organization: "default",
},
},
},
},
},
args: args{
server: &server.Server{
GithubClientID: "not empty",
GithubClientSecret: "not empty",
},
method: "PUT",
path: "/chronograf/v1/org_config/logviewer",
payload: &chronograf.LogViewerConfig{
Columns: []chronograf.LogViewerColumn{
{
Name: "time",
Position: 0,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "severity",
Position: 1,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
{
Type: "color",
Name: "emerg",
Value: "ruby",
},
{
Type: "color",
Name: "alert",
Value: "fire",
},
{
Type: "color",
Name: "crit",
Value: "curacao",
},
{
Type: "color",
Name: "err",
Value: "tiger",
},
{
Type: "color",
Name: "warning",
Value: "pineapple",
},
{
Type: "color",
Name: "notice",
Value: "wolf",
},
{
Type: "color",
Name: "info",
Value: "wolf",
},
{
Type: "color",
Name: "debug",
Value: "wolf",
},
},
},
{
Name: "timestamp",
Position: 3,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 2,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
{
Type: "displayName",
Value: "ProcID!",
},
},
},
{
Name: "appname",
Position: 6,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encodings: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
principal: oauth2.Principal{
Organization: "default",
Subject: "billibob",
Issuer: "github",
},
},
wants: wants{
statusCode: 200,
body: `
{
"links": {
"self": "\/chronograf\/v1\/org_config\/logviewer"
},
"columns": [
{
"name": "time",
"position": 0,
"encodings": [
{
"type": "visibility",
"value": "hidden"
}
]
},
{
"name": "severity",
"position": 1,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "label",
"value": "icon"
},
{
"type": "color",
"value": "ruby",
"name": "emerg"
},
{
"type": "color",
"value": "fire",
"name": "alert"
},
{
"type": "color",
"value": "curacao",
"name": "crit"
},
{
"type": "color",
"value": "tiger",
"name": "err"
},
{
"type": "color",
"value": "pineapple",
"name": "warning"
},
{
"type": "color",
"value": "wolf",
"name": "notice"
},
{
"type": "color",
"value": "wolf",
"name": "info"
},
{
"type": "color",
"value": "wolf",
"name": "debug"
}
]
},
{
"name": "timestamp",
"position": 3,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "message",
"position": 2,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "facility",
"position": 4,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
},
{
"name": "procid",
"position": 5,
"encodings": [
{
"type": "visibility",
"value": "hidden"
},
{
"type": "displayName",
"value": "ProcID!"
}
]
},
{
"name": "appname",
"position": 6,
"encodings": [
{
"type": "visibility",
"value": "visible"
},
{
"type": "displayName",
"value": "Application"
}
]
},
{
"name": "host",
"position": 7,
"encodings": [
{
"type": "visibility",
"value": "visible"
}
]
}
]
}
`,
},
},
{
name: "GET /",
subName: "signed into default org",
@ -2717,7 +3416,8 @@ func TestServer(t *testing.T) {
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth"
"auth": "/chronograf/v1/config/auth",
"logViewer": "/chronograf/v1/config/logviewer"
},
"auth": [
{
@ -2794,38 +3494,39 @@ func TestServer(t *testing.T) {
statusCode: 200,
body: `
{
"layouts": "/chronograf/v1/layouts",
"users": "/chronograf/v1/organizations/1/users",
"allUsers": "/chronograf/v1/users",
"organizations": "/chronograf/v1/organizations",
"mappings": "/chronograf/v1/mappings",
"sources": "/chronograf/v1/sources",
"me": "/chronograf/v1/me",
"environment": "/chronograf/v1/env",
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth"
},
"auth": [
{
"name": "github",
"label": "Github",
"login": "/oauth/github/login",
"logout": "/oauth/github/logout",
"callback": "/oauth/github/callback"
}
],
"logout": "/oauth/logout",
"external": {
"statusFeed": ""
},
"flux": {
"ast": "/chronograf/v1/flux/ast",
"self": "/chronograf/v1/flux",
"suggestions": "/chronograf/v1/flux/suggestions"
}
}
"layouts": "/chronograf/v1/layouts",
"users": "/chronograf/v1/organizations/1/users",
"allUsers": "/chronograf/v1/users",
"organizations": "/chronograf/v1/organizations",
"mappings": "/chronograf/v1/mappings",
"sources": "/chronograf/v1/sources",
"me": "/chronograf/v1/me",
"environment": "/chronograf/v1/env",
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth",
"logViewer": "/chronograf/v1/config/logviewer"
},
"auth": [
{
"name": "github",
"label": "Github",
"login": "/oauth/github/login",
"logout": "/oauth/github/logout",
"callback": "/oauth/github/callback"
}
],
"logout": "/oauth/logout",
"external": {
"statusFeed": ""
},
"flux": {
"ast": "/chronograf/v1/flux/ast",
"self": "/chronograf/v1/flux",
"suggestions": "/chronograf/v1/flux/suggestions"
}
}
`,
},
},

22
mocks/org_config.go Normal file
View File

@ -0,0 +1,22 @@
package mocks
import (
"context"
"github.com/influxdata/chronograf"
)
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
type OrganizationConfigStore struct {
FindOrCreateF func(ctx context.Context, id string) (*chronograf.OrganizationConfig, error)
PutF func(ctx context.Context, c *chronograf.OrganizationConfig) error
}
func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, id string) (*chronograf.OrganizationConfig, error) {
return s.FindOrCreateF(ctx, id)
}
func (s *OrganizationConfigStore) Put(ctx context.Context, c *chronograf.OrganizationConfig) error {
return s.PutF(ctx, c)
}

View File

@ -8,14 +8,15 @@ import (
// Store is a server.DataStore
type Store struct {
SourcesStore chronograf.SourcesStore
MappingsStore chronograf.MappingsStore
ServersStore chronograf.ServersStore
LayoutsStore chronograf.LayoutsStore
UsersStore chronograf.UsersStore
DashboardsStore chronograf.DashboardsStore
OrganizationsStore chronograf.OrganizationsStore
ConfigStore chronograf.ConfigStore
SourcesStore chronograf.SourcesStore
MappingsStore chronograf.MappingsStore
ServersStore chronograf.ServersStore
LayoutsStore chronograf.LayoutsStore
UsersStore chronograf.UsersStore
DashboardsStore chronograf.DashboardsStore
OrganizationsStore chronograf.OrganizationsStore
ConfigStore chronograf.ConfigStore
OrganizationConfigStore chronograf.OrganizationConfigStore
}
func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
@ -48,3 +49,7 @@ func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
func (s *Store) Config(ctx context.Context) chronograf.ConfigStore {
return s.ConfigStore
}
func (s *Store) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore {
return s.OrganizationConfigStore
}

21
noop/org_config.go Normal file
View File

@ -0,0 +1,21 @@
package noop
import (
"context"
"fmt"
"github.com/influxdata/chronograf"
)
// ensure OrganizationConfigStore implements chronograf.OrganizationConfigStore
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
type OrganizationConfigStore struct{}
func (s *OrganizationConfigStore) FindOrCreate(context.Context, string) (*chronograf.OrganizationConfig, error) {
return nil, chronograf.ErrOrganizationConfigNotFound
}
func (s *OrganizationConfigStore) Put(context.Context, *chronograf.OrganizationConfig) error {
return fmt.Errorf("cannot replace config")
}

View File

@ -0,0 +1,51 @@
package organizations
import (
"context"
"github.com/influxdata/chronograf"
)
// ensure that OrganizationConfig implements chronograf.OrganizationConfigStore
var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{}
// OrganizationConfigStore facade on a OrganizationConfig that filters OrganizationConfigs by organization.
type OrganizationConfigStore struct {
store chronograf.OrganizationConfigStore
organization string
}
// NewOrganizationConfigStore creates a new OrganizationConfigStore from an existing
// chronograf.OrganizationConfigStore and an organization string
func NewOrganizationConfigStore(s chronograf.OrganizationConfigStore, orgID string) *OrganizationConfigStore {
return &OrganizationConfigStore{
store: s,
organization: orgID,
}
}
// FindOrCreate gets an organization's config or creates one if none exists
func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) {
var err = validOrganization(ctx)
if err != nil {
return nil, err
}
oc, err := s.store.FindOrCreate(ctx, orgID)
if err != nil {
return nil, err
}
return oc, nil
}
// Put the OrganizationConfig in OrganizationConfigStore.
func (s *OrganizationConfigStore) Put(ctx context.Context, c *chronograf.OrganizationConfig) error {
err := validOrganization(ctx)
if err != nil {
return err
}
return s.store.Put(ctx, c)
}

View File

@ -95,13 +95,8 @@ func AuthorizedUser(
next http.HandlerFunc,
) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !useAuth {
ctx := r.Context()
// If there is no auth, then give the user raw access to the DataStore
r = r.WithContext(serverContext(ctx))
next(w, r)
return
}
ctx := r.Context()
serverCtx := serverContext(ctx)
log := logger.
WithField("component", "role_auth").
@ -109,8 +104,24 @@ func AuthorizedUser(
WithField("method", r.Method).
WithField("url", r.URL)
ctx := r.Context()
serverCtx := serverContext(ctx)
defaultOrg, err := store.Organizations(serverCtx).DefaultOrganization(serverCtx)
if err != nil {
log.Error(fmt.Sprintf("Failed to retrieve the default organization: %v", err))
Error(w, http.StatusForbidden, "User is not authorized", logger)
return
}
if !useAuth {
// If there is no auth, then set the organization id to be the default org id on context
// so that calls like hasOrganizationContext as used in Organization Config service
// method OrganizationConfig can successfully get the organization id
ctx = context.WithValue(ctx, organizations.ContextKey, defaultOrg.ID)
// And if there is no auth, then give the user raw access to the DataStore
r = r.WithContext(serverContext(ctx))
next(w, r)
return
}
p, err := getValidPrincipal(ctx)
if err != nil {
@ -127,12 +138,6 @@ func AuthorizedUser(
// This is as if the user was logged into the default organization
if p.Organization == "" {
defaultOrg, err := store.Organizations(serverCtx).DefaultOrganization(serverCtx)
if err != nil {
log.Error(fmt.Sprintf("Failed to retrieve the default organization: %v", err))
Error(w, http.StatusForbidden, "User is not authorized", logger)
return
}
p.Organization = defaultOrg.ID
}

View File

@ -63,7 +63,6 @@ func TestAuthorizedToken(t *testing.T) {
}
}
}
func TestAuthorizedUser(t *testing.T) {
type fields struct {
UsersStore chronograf.UsersStore
@ -102,7 +101,7 @@ func TestAuthorizedUser(t *testing.T) {
args: args{
useAuth: false,
},
hasOrganizationContext: false,
hasOrganizationContext: true,
hasSuperAdminContext: false,
hasRoleContext: false,
hasServerContext: true,
@ -1047,6 +1046,11 @@ func TestAuthorizedUser(t *testing.T) {
Name: "The ShillBillThrilliettas",
}, nil
},
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
return &chronograf.Organization{
ID: "0",
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},

View File

@ -2,22 +2,26 @@ package server
import (
"encoding/json"
"fmt"
"net/http"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf"
)
type configLinks struct {
Self string `json:"self"` // Self link mapping to this resource
Auth string `json:"auth"` // Auth link to the auth config endpoint
}
type configResponse struct {
Links selfLinks `json:"links"`
Links configLinks `json:"links"`
chronograf.Config
}
func newConfigResponse(config chronograf.Config) *configResponse {
return &configResponse{
Links: selfLinks{
Links: configLinks{
Self: "/chronograf/v1/config",
Auth: "/chronograf/v1/config/auth",
},
Config: config,
}
@ -52,11 +56,12 @@ func (s *Service) Config(w http.ResponseWriter, r *http.Request) {
return
}
res := newConfigResponse(*config)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// ConfigSection retrieves the section of the global application configuration
func (s *Service) ConfigSection(w http.ResponseWriter, r *http.Request) {
// AuthConfig retrieves the auth section of the global application configuration
func (s *Service) AuthConfig(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
config, err := s.Store.Config(ctx).Get(ctx)
@ -70,50 +75,33 @@ func (s *Service) ConfigSection(w http.ResponseWriter, r *http.Request) {
return
}
section := httprouter.GetParamFromContext(ctx, "section")
var res interface{}
switch section {
case "auth":
res = newAuthConfigResponse(*config)
default:
Error(w, http.StatusBadRequest, fmt.Sprintf("received unknown section %q", section), s.Logger)
return
}
res := newAuthConfigResponse(*config)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// ReplaceConfigSection replaces a section of the global application configuration
func (s *Service) ReplaceConfigSection(w http.ResponseWriter, r *http.Request) {
// ReplaceAuthConfig replaces the auth section of the global application configuration
func (s *Service) ReplaceAuthConfig(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var authConfig chronograf.AuthConfig
if err := json.NewDecoder(r.Body).Decode(&authConfig); err != nil {
invalidJSON(w, s.Logger)
return
}
config, err := s.Store.Config(ctx).Get(ctx)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
if config == nil {
Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger)
return
}
config.Auth = authConfig
section := httprouter.GetParamFromContext(ctx, "section")
var res interface{}
switch section {
case "auth":
var authConfig chronograf.AuthConfig
if err := json.NewDecoder(r.Body).Decode(&authConfig); err != nil {
invalidJSON(w, s.Logger)
return
}
config.Auth = authConfig
res = newAuthConfigResponse(*config)
default:
Error(w, http.StatusBadRequest, fmt.Sprintf("received unknown section %q", section), s.Logger)
return
}
res := newAuthConfigResponse(*config)
if err := s.Store.Config(ctx).Update(ctx, config); err != nil {
unknownErrorWithMessage(w, err, s.Logger)
return

View File

@ -7,7 +7,6 @@ import (
"net/http/httptest"
"testing"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/log"
"github.com/influxdata/chronograf/mocks"
@ -42,7 +41,7 @@ func TestConfig(t *testing.T) {
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"auth": {"superAdminNewUsers": false}, "links": {"self": "/chronograf/v1/config"}}`,
body: `{"links":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":{"superAdminNewUsers":false}}`,
},
},
}
@ -78,13 +77,10 @@ func TestConfig(t *testing.T) {
}
}
func TestConfigSection(t *testing.T) {
func TestAuthConfig(t *testing.T) {
type fields struct {
ConfigStore chronograf.ConfigStore
}
type args struct {
section string
}
type wants struct {
statusCode int
contentType string
@ -94,7 +90,6 @@ func TestConfigSection(t *testing.T) {
tests := []struct {
name string
fields fields
args args
wants wants
}{
{
@ -108,35 +103,12 @@ func TestConfigSection(t *testing.T) {
},
},
},
args: args{
section: "auth",
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"superAdminNewUsers": false, "links": {"self": "/chronograf/v1/config/auth"}}`,
},
},
{
name: "Get unknown configuration",
fields: fields{
ConfigStore: &mocks.ConfigStore{
Config: &chronograf.Config{
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
},
},
},
args: args{
section: "unknown",
},
wants: wants{
statusCode: 400,
contentType: "application/json",
body: `{"code":400,"message":"received unknown section \"unknown\""}`,
},
},
}
for _, tt := range tests {
@ -150,16 +122,8 @@ func TestConfigSection(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://any.url", nil)
r = r.WithContext(httprouter.WithParams(
r.Context(),
httprouter.Params{
{
Key: "section",
Value: tt.args.section,
},
}))
s.ConfigSection(w, r)
s.AuthConfig(w, r)
resp := w.Result()
content := resp.Header.Get("Content-Type")
@ -178,12 +142,11 @@ func TestConfigSection(t *testing.T) {
}
}
func TestReplaceConfigSection(t *testing.T) {
func TestReplaceAuthConfig(t *testing.T) {
type fields struct {
ConfigStore chronograf.ConfigStore
}
type args struct {
section string
payload interface{} // expects JSON serializable struct
}
type wants struct {
@ -210,7 +173,6 @@ func TestReplaceConfigSection(t *testing.T) {
},
},
args: args{
section: "auth",
payload: chronograf.AuthConfig{
SuperAdminNewUsers: true,
},
@ -221,31 +183,6 @@ func TestReplaceConfigSection(t *testing.T) {
body: `{"superAdminNewUsers": true, "links": {"self": "/chronograf/v1/config/auth"}}`,
},
},
{
name: "Set unknown configuration",
fields: fields{
ConfigStore: &mocks.ConfigStore{
Config: &chronograf.Config{
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
},
},
},
args: args{
section: "unknown",
payload: struct {
Data string `json:"data"`
}{
Data: "stuff",
},
},
wants: wants{
statusCode: 400,
contentType: "application/json",
body: `{"code":400,"message":"received unknown section \"unknown\""}`,
},
},
}
for _, tt := range tests {
@ -259,18 +196,10 @@ func TestReplaceConfigSection(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://any.url", nil)
r = r.WithContext(httprouter.WithParams(
r.Context(),
httprouter.Params{
{
Key: "section",
Value: tt.args.section,
},
}))
buf, _ := json.Marshal(tt.args.payload)
r.Body = ioutil.NopCloser(bytes.NewReader(buf))
s.ReplaceConfigSection(w, r)
s.ReplaceAuthConfig(w, r)
resp := w.Result()
content := resp.Header.Get("Content-Type")

View File

@ -12,8 +12,9 @@ type getFluxLinksResponse struct {
}
type getConfigLinksResponse struct {
Self string `json:"self"` // Location of the whole global application configuration
Auth string `json:"auth"` // Location of the auth section of the global application configuration
Self string `json:"self"` // Location of the whole global application configuration
Auth string `json:"auth"` // Location of the auth section of the global application configuration
LogViewer string `json:"logViewer"` // Location of the log viewer section of the global application configuration
}
type getExternalLinksResponse struct {

View File

@ -313,8 +313,13 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
// Global application config for Chronograf
router.GET("/chronograf/v1/config", EnsureSuperAdmin(service.Config))
router.GET("/chronograf/v1/config/:section", EnsureSuperAdmin(service.ConfigSection))
router.PUT("/chronograf/v1/config/:section", EnsureSuperAdmin(service.ReplaceConfigSection))
router.GET("/chronograf/v1/config/auth", EnsureSuperAdmin(service.AuthConfig))
router.PUT("/chronograf/v1/config/auth", EnsureSuperAdmin(service.ReplaceAuthConfig))
// Organization config settings for Chronograf
router.GET("/chronograf/v1/org_config", EnsureViewer(service.OrganizationConfig))
router.GET("/chronograf/v1/org_config/logviewer", EnsureViewer(service.OrganizationLogViewerConfig))
router.PUT("/chronograf/v1/org_config/logviewer", EnsureEditor(service.ReplaceOrganizationLogViewerConfig))
router.GET("/chronograf/v1/env", EnsureViewer(service.Environment))

180
server/org_config.go Normal file
View File

@ -0,0 +1,180 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"github.com/influxdata/chronograf"
)
type organizationConfigLinks struct {
Self string `json:"self"` // Self link mapping to this resource
LogViewer string `json:"logViewer"` // LogViewer link to the organization log viewer config endpoint
}
type organizationConfigResponse struct {
Links organizationConfigLinks `json:"links"`
chronograf.OrganizationConfig
}
func newOrganizationConfigResponse(c chronograf.OrganizationConfig) *organizationConfigResponse {
return &organizationConfigResponse{
Links: organizationConfigLinks{
Self: "/chronograf/v1/org_config",
LogViewer: "/chronograf/v1/org_config/logviewer",
},
OrganizationConfig: c,
}
}
type logViewerConfigResponse struct {
Links selfLinks `json:"links"`
chronograf.LogViewerConfig
}
func newLogViewerConfigResponse(c chronograf.LogViewerConfig) *logViewerConfigResponse {
return &logViewerConfigResponse{
Links: selfLinks{
Self: "/chronograf/v1/org_config/logviewer",
},
LogViewerConfig: c,
}
}
// OrganizationConfig retrieves the organization-wide config settings
func (s *Service) OrganizationConfig(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
orgID, ok := hasOrganizationContext(ctx)
if !ok {
Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger)
return
}
config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
res := newOrganizationConfigResponse(*config)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// OrganizationLogViewerConfig retrieves the log viewer UI section of the organization config
// This uses a FindOrCreate function to ensure that any new organizations have
// default organization config values, without having to associate organization creation with
// organization config creation.
func (s *Service) OrganizationLogViewerConfig(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
orgID, ok := hasOrganizationContext(ctx)
if !ok {
Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger)
return
}
config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
res := newLogViewerConfigResponse(config.LogViewer)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// ReplaceOrganizationLogViewerConfig replaces the log viewer UI section of the organization config
func (s *Service) ReplaceOrganizationLogViewerConfig(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
orgID, ok := hasOrganizationContext(ctx)
if !ok {
Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger)
return
}
var logViewerConfig chronograf.LogViewerConfig
if err := json.NewDecoder(r.Body).Decode(&logViewerConfig); err != nil {
invalidJSON(w, s.Logger)
return
}
if err := validLogViewerConfig(logViewerConfig); err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID)
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
}
config.LogViewer = logViewerConfig
if err := s.Store.OrganizationConfig(ctx).Put(ctx, config); err != nil {
unknownErrorWithMessage(w, err, s.Logger)
return
}
res := newLogViewerConfigResponse(config.LogViewer)
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// validLogViewerConfig ensures that the request body log viewer UI config is valid
// to be valid, it must: not be empty, have at least one column, not have multiple
// columns with the same name or position value, each column must have a visbility
// of either "visible" or "hidden" and if a column is of type severity, it must have
// at least one severity format of type icon, text, or both
func validLogViewerConfig(c chronograf.LogViewerConfig) error {
if len(c.Columns) == 0 {
return fmt.Errorf("Invalid log viewer config: must have at least 1 column")
}
nameMatcher := map[string]bool{}
positionMatcher := map[int32]bool{}
for _, clm := range c.Columns {
iconCount := 0
textCount := 0
visibility := 0
// check that each column has a unique value for the name and position properties
if _, ok := nameMatcher[clm.Name]; ok {
return fmt.Errorf("Invalid log viewer config: Duplicate column name %s", clm.Name)
}
nameMatcher[clm.Name] = true
if _, ok := positionMatcher[clm.Position]; ok {
return fmt.Errorf("Invalid log viewer config: Multiple columns with same position value")
}
positionMatcher[clm.Position] = true
for _, e := range clm.Encodings {
if e.Type == "visibility" {
visibility++
if !(e.Value == "visible" || e.Value == "hidden") {
return fmt.Errorf("Invalid log viewer config: invalid visibility in column %s", clm.Name)
}
}
if clm.Name == "severity" {
if e.Value == "icon" {
iconCount++
} else if e.Value == "text" {
textCount++
}
}
}
if visibility != 1 {
return fmt.Errorf("Invalid log viewer config: missing visibility encoding in column %s", clm.Name)
}
if clm.Name == "severity" {
if iconCount+textCount == 0 || iconCount > 1 || textCount > 1 {
return fmt.Errorf("Invalid log viewer config: invalid number of severity format encodings in column %s", clm.Name)
}
}
}
return nil
}

1077
server/org_config_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -87,8 +87,9 @@ func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Mappings: "/chronograf/v1/mappings",
Dashboards: "/chronograf/v1/dashboards",
Config: getConfigLinksResponse{
Self: "/chronograf/v1/config",
Auth: "/chronograf/v1/config/auth",
Self: "/chronograf/v1/config",
Auth: "/chronograf/v1/config/auth",
LogViewer: "/chronograf/v1/config/logviewer",
},
Auth: make([]AuthRoute, len(a.AuthRoutes)), // We want to return at least an empty array, rather than null
ExternalLinks: getExternalLinksResponse{

View File

@ -29,7 +29,7 @@ func TestAllRoutes(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutes not able to unmarshal JSON response")
}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth","logViewer":"/chronograf/v1/config/logviewer"},"auth":[],"external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
if want != string(body) {
t.Errorf("TestAllRoutes\nwanted\n*%s*\ngot\n*%s*", want, string(body))
@ -67,7 +67,7 @@ func TestAllRoutesWithAuth(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutesWithAuth not able to unmarshal JSON response")
}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth","logViewer":"/chronograf/v1/config/logviewer"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
if want != string(body) {
t.Errorf("TestAllRoutesWithAuth\nwanted\n*%s*\ngot\n*%s*", want, string(body))
@ -100,7 +100,7 @@ func TestAllRoutesWithExternalLinks(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutesWithExternalLinks not able to unmarshal JSON response")
}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth","logViewer":"/chronograf/v1/config/logviewer"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
if want != string(body) {
t.Errorf("TestAllRoutesWithExternalLinks\nwanted\n*%s*\ngot\n*%s*", want, string(body))

View File

@ -486,14 +486,15 @@ func openService(ctx context.Context, buildInfo chronograf.BuildInfo, boltPath s
return Service{
TimeSeriesClient: &InfluxClient{},
Store: &Store{
LayoutsStore: layouts,
DashboardsStore: dashboards,
SourcesStore: sources,
ServersStore: kapacitors,
OrganizationsStore: organizations,
UsersStore: db.UsersStore,
ConfigStore: db.ConfigStore,
MappingsStore: db.MappingsStore,
LayoutsStore: layouts,
DashboardsStore: dashboards,
SourcesStore: sources,
ServersStore: kapacitors,
OrganizationsStore: organizations,
UsersStore: db.UsersStore,
ConfigStore: db.ConfigStore,
MappingsStore: db.MappingsStore,
OrganizationConfigStore: db.OrganizationConfigStore,
},
Logger: logger,
UseAuth: useAuth,

View File

@ -91,6 +91,7 @@ type DataStore interface {
Mappings(ctx context.Context) chronograf.MappingsStore
Dashboards(ctx context.Context) chronograf.DashboardsStore
Config(ctx context.Context) chronograf.ConfigStore
OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore
}
// ensure that Store implements a DataStore
@ -98,18 +99,19 @@ var _ DataStore = &Store{}
// Store implements the DataStore interface
type Store struct {
SourcesStore chronograf.SourcesStore
ServersStore chronograf.ServersStore
LayoutsStore chronograf.LayoutsStore
UsersStore chronograf.UsersStore
DashboardsStore chronograf.DashboardsStore
MappingsStore chronograf.MappingsStore
OrganizationsStore chronograf.OrganizationsStore
ConfigStore chronograf.ConfigStore
SourcesStore chronograf.SourcesStore
ServersStore chronograf.ServersStore
LayoutsStore chronograf.LayoutsStore
UsersStore chronograf.UsersStore
DashboardsStore chronograf.DashboardsStore
MappingsStore chronograf.MappingsStore
OrganizationsStore chronograf.OrganizationsStore
ConfigStore chronograf.ConfigStore
OrganizationConfigStore chronograf.OrganizationConfigStore
}
// Sources returns a noop.SourcesStore if the context has no organization specified
// and a organization.SourcesStore otherwise.
// and an organization.SourcesStore otherwise.
func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
if isServer := hasServerContext(ctx); isServer {
return s.SourcesStore
@ -122,7 +124,7 @@ func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
}
// Servers returns a noop.ServersStore if the context has no organization specified
// and a organization.ServersStore otherwise.
// and an organization.ServersStore otherwise.
func (s *Store) Servers(ctx context.Context) chronograf.ServersStore {
if isServer := hasServerContext(ctx); isServer {
return s.ServersStore
@ -157,7 +159,7 @@ func (s *Store) Users(ctx context.Context) chronograf.UsersStore {
}
// Dashboards returns a noop.DashboardsStore if the context has no organization specified
// and a organization.DashboardsStore otherwise.
// and an organization.DashboardsStore otherwise.
func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
if isServer := hasServerContext(ctx); isServer {
return s.DashboardsStore
@ -169,6 +171,16 @@ func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
return &noop.DashboardsStore{}
}
// OrganizationConfig returns a noop.OrganizationConfigStore if the context has no organization specified
// and an organization.OrganizationConfigStore otherwise.
func (s *Store) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore {
if orgID, ok := hasOrganizationContext(ctx); ok {
return organizations.NewOrganizationConfigStore(s.OrganizationConfigStore, orgID)
}
return &noop.OrganizationConfigStore{}
}
// Organizations returns the underlying OrganizationsStore.
func (s *Store) Organizations(ctx context.Context) chronograf.OrganizationsStore {
if isServer := hasServerContext(ctx); isServer {
@ -191,6 +203,7 @@ func (s *Store) Config(ctx context.Context) chronograf.ConfigStore {
if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin {
return s.ConfigStore
}
return &noop.ConfigStore{}
}

View File

@ -2865,27 +2865,47 @@
}
}
},
"/chronograf/v1/config/:section": {
"/chronograf/v1/config": {
"get": {
"tags": ["config"],
"summary": "Returns the settings for a specific section of the app",
"description": "All settings for a specific section of the app",
"summary": "Returns the global application configuration",
"description": "All global application configurations",
"responses": {
"200": {
"description": "Returns an object with the settings for a section of the app",
"description": "Returns an object with the global configurations",
"schema": {
"oneOf": [
{
"$ref": "#/definitions/LogViewerUIConfig"
},
{
"$ref": "#/definitions/AuthConfig"
}
]
"$ref": "#/definitions/Config"
}
},
"404": {
"description": "Could not find requested section",
"description": "Could not find global application config",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "Unexpected internal server error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/chronograf/v1/config/auth": {
"get": {
"tags": ["config"],
"summary": "Returns the global application configuration for auth",
"description": "All global application configuration for auth",
"responses": {
"200": {
"description": "Returns an object with the global application configuration for auth",
"schema": {
"$ref": "#/definitions/AuthConfig"
}
},
"404": {
"description": "Could not find auth configuration",
"schema": {
"$ref": "#/definitions/Error"
}
@ -2900,49 +2920,120 @@
},
"put": {
"tags": ["config"],
"summary": "Updates the settings for a specific section of the app",
"description": "Updates settings for a specific section of the app",
"summary": "Updates the global application configuration for auth",
"description": "Replaces the global application configuration for auth",
"parameters": [
{
"name": "section",
"in": "path",
"type": "string",
"description": "Section name for the target section settings",
"required": true
},
{
"name": {
"enum": ["logViewer", "auth"]
},
"name": "auth",
"in": "body",
"description":
"Section configuration update parameters",
"Auth configuration update object",
"schema": {
"oneOf": [
{
"$ref": "#/definitions/LogViewerUIConfig"
},
{
"$ref": "#/definitions/AuthConfig"
}
]
"$ref": "#/definitions/AuthConfig"
},
"required": true
}
],
"responses": {
"200": {
"description": "Returns an object with the updated UI settings for a specific section",
"description": "Returns an object with the updated auth configuration",
"schema": {
"oneOf": [
{
"$ref": "#/definitions/LogViewerUIConfig"
}
]
"$ref": "#/definitions/AuthConfig"
}
},
"404": {
"description": "Could not find requested section",
"description": "Could not find auth configuration",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "Unexpected internal server error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/chronograf/v1/org_config": {
"get": {
"tags": ["organization config"],
"summary": "Retrieve the organization configuration",
"description": "Organization-specific configurations such as log viewer configs",
"responses": {
"200": {
"description": "Returns an object with the organization-specific configurations",
"schema": {
"$ref": "#/definitions/OrganizationConfig"
}
},
"404": {
"description": "Could not find organization config",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "Unexpected internal server error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
}
},
"/chronograf/v1/org_config/logviewer": {
"get": {
"tags": ["organization config"],
"summary": "Retrieve the organization-specific log viewer configurations",
"description": "Retrieve the log viewer configurations for the user's current organization",
"responses": {
"200": {
"description": "Returns an log viewer configuration object",
"schema": {
"$ref": "#/definitions/LogViewerConfig"
}
},
"404": {
"description": "Could not find the log viewer configuration for this organization",
"schema": {
"$ref": "#/definitions/Error"
}
},
"default": {
"description": "Unexpected internal server error",
"schema": {
"$ref": "#/definitions/Error"
}
}
}
},
"put": {
"tags": ["organization config"],
"summary": "Update the log viewer configuration",
"description": "Update the log viewer configuration for a specific organization",
"parameters": [
{
"name": "logViewer",
"in": "body",
"description":
"Log Viewer configuration update object",
"schema": {
"$ref": "#/definitions/LogViewerConfig"
},
"required": true
}
],
"responses": {
"200": {
"description": "Returns an object with the updated log viewer configurations",
"schema": {
"$ref": "#/definitions/LogViewerConfig"
}
},
"404": {
"description": "Could not find log viewer configurations for the specified organization",
"schema": {
"$ref": "#/definitions/Error"
}
@ -5144,79 +5235,99 @@
}
}
},
"LogViewerUIColumn": {
"description": "Contains the settings for the log viewer page UI",
"Config": {
"description": "Global application configuration",
"type": "object",
"required": [
"name",
"encoding",
"position"
],
"properties": {
"name": {
"description": "Unique identifier name of the column",
"type": "string"
},
"position": {
"type": "integer",
"format": "int32"
},
"encoding": {
"description": "Composable encoding options for the column",
"type": "array",
"items": {
"description":"Type and value and optional name of an encoding",
"type": "object",
"required": ["type", "value"],
"properties": {
"type": {
"type": "string"
},
"value": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
"auth": {
"$ref": "#/definitions/AuthConfig"
}
},
"example": {
"name": "severity",
"position": 0,
"encoding": [
{
"type": "label",
"value": "icon"
},
{
"type": "label",
"value": "text"
},
{
"type": "visibility",
"value": "visible"
},
{
"type": "color",
"name": "ruby",
"value": "emergency"
},
{
"type": "color",
"name": "rainforest",
"value": "info"
},
{
"type": "displayName",
"value": "Log Severity!"
}
]
"auth": {
"superAdminNewUsers": true
}
}
},
"LogViewerUIConfig": {
"description": "Contains the settings for the log viewer page UI",
"AuthConfig": {
"description": "Global application configuration for auth",
"type": "object",
"required": ["superAdminNewUsers"],
"properties": {
"superAdminNewUsers": {
"type": "boolean",
"default": true
}
},
"example": {
"superAdminNewUsers": true
}
},
"OrganizationConfig": {
"description": "Configurations for a specific organization",
"type": "object",
"required": ["logViewer"],
"properties": {
"organization": {
"type": "string",
"readOnly": true
},
"logViewer": {
"$ref": "#/definitions/LogViewerConfig"
}
},
"example": {
"organization": "default",
"logViewer": {
"columns": [
{
"name": "severity",
"position": 0,
"encodings": [
{
"type": "label",
"value": "icon"
},
{
"type": "label",
"value": "text"
},
{
"type": "visibility",
"value": "visible"
},
{
"type": "color",
"name": "ruby",
"value": "emergency"
},
{
"type": "color",
"name": "rainforest",
"value": "info"
},
{
"type": "displayName",
"value": "Log Severity!"
}
]
},
{
"name": "messages",
"position": 1,
"encodings": [
{
"type": "visibility",
"value": "hidden"
}
]
}
]
}
}
},
"LogViewerConfig": {
"description": "Contains the organization-specific configuration for the log viewer",
"type": "object",
"required": ["columns"],
"properties": {
@ -5224,7 +5335,7 @@
"description": "Defines the order, names, and visibility of columns in the log viewer table",
"type": "array",
"items": {
"$ref": "#/definitions/LogViewerUIColumn"
"$ref": "#/definitions/LogViewerColumn"
}
}
},
@ -5233,7 +5344,7 @@
{
"name": "severity",
"position": 0,
"encoding": [
"encodings": [
{
"type": "label",
"value": "icon"
@ -5265,7 +5376,7 @@
{
"name": "messages",
"position": 1,
"encoding": [
"encodings": [
{
"type": "visibility",
"value": "hidden"
@ -5275,15 +5386,76 @@
]
}
},
"AuthConfig": {
"LogViewerColumn": {
"description": "Contains the organization-specific configuration for the log viewer",
"type": "object",
"required": ["superAdminNewUsers"],
"required": [
"name",
"encodings",
"position"
],
"properties": {
"superAdminNewUsers": {
"type": "boolean",
"default": true
"name": {
"description": "Unique identifier name of the column",
"type": "string"
},
"position": {
"type": "integer",
"format": "int32"
},
"encodings": {
"description": "Composable encoding options for the column",
"type": "array",
"items": {
"description":"Type and value and optional name of an encoding",
"type": "object",
"required": ["type", "value"],
"properties": {
"type": {
"type": "string"
},
"value": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
},
"example": {
"name": "severity",
"position": 0,
"encodings": [
{
"type": "label",
"value": "icon"
},
{
"type": "label",
"value": "text"
},
{
"type": "visibility",
"value": "visible"
},
{
"type": "color",
"name": "ruby",
"value": "emergency"
},
{
"type": "color",
"name": "rainforest",
"value": "info"
},
{
"type": "displayName",
"value": "Log Severity!"
}
]
}
}
},
"Routes": {
"type": "object",

View File

@ -174,10 +174,13 @@ class DragAndDrop extends PureComponent<Props, State> {
const reader = new FileReader()
reader.readAsText(file)
reader.onload = loadEvent => {
this.setState({
uploadContent: loadEvent.target.result,
fileName: file.name,
})
this.setState(
{
uploadContent: loadEvent.target.result,
fileName: file.name,
},
this.handleSubmit
)
}
}

View File

@ -37,7 +37,6 @@ class MapTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
public render() {
const {onUpdateDefaultTemplateValue, template} = this.props
const {templateValuesString} = this.state
const pluralizer = template.values.length === 1 ? '' : 's'
return (
<>
@ -65,7 +64,7 @@ class MapTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
<div className="form-group col-xs-12 temp-builder--results">
<p className="temp-builder--validation">
Mapping contains <strong>{template.values.length}</strong> key-value
pair{pluralizer}
pair{this.pluralizer}
</p>
{template.values.length > 0 && (
<TemplatePreviewList
@ -78,6 +77,10 @@ class MapTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
)
}
private get pluralizer(): string {
return this.props.template.values.length === 1 ? '' : 's'
}
private handleUploadFile = (
uploadContent: string,
fileName: string
@ -114,9 +117,10 @@ class MapTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
this.setState({templateValuesString: e.target.value})
}
private constructValuesFromString(templateValuesString) {
private constructValuesFromString(templateValuesString: string) {
const {notify} = this.props
const parsedTVS = Papa.parse(templateValuesString)
const trimmed = _.trimEnd(templateValuesString, '\n')
const parsedTVS = Papa.parse(trimmed)
const templateValuesData = getDeep<string[][]>(parsedTVS, 'data', [[]])
if (templateValuesData.length === 0) {

View File

@ -42,7 +42,7 @@ class TemplatePreviewListItem extends PureComponent<Props> {
private get mapTempVarKey(): string {
const {item} = this.props
if (item.type === TemplateValueType.Map) {
return `${item.key} -->`
return `${item.key} --> `
}
}