Merge pull request #2355 from influxdata/multitenancy_all_users_superadmin_toggle
UI Toggle & API for SuperAdminFirstUserOnly server configpull/2580/head
commit
456488f0ac
|
@ -23,6 +23,7 @@ type Client struct {
|
|||
DashboardsStore *DashboardsStore
|
||||
UsersStore *UsersStore
|
||||
OrganizationsStore *OrganizationsStore
|
||||
ConfigStore *ConfigStore
|
||||
}
|
||||
|
||||
// NewClient initializes all stores
|
||||
|
@ -40,6 +41,7 @@ func NewClient() *Client {
|
|||
}
|
||||
c.UsersStore = &UsersStore{client: c}
|
||||
c.OrganizationsStore = &OrganizationsStore{client: c}
|
||||
c.ConfigStore = &ConfigStore{client: c}
|
||||
return c
|
||||
}
|
||||
|
||||
|
@ -77,6 +79,10 @@ func (c *Client) Open(ctx context.Context) error {
|
|||
if _, err := tx.CreateBucketIfNotExists(UsersBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
// Always create Config bucket.
|
||||
if _, err := tx.CreateBucketIfNotExists(ConfigBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
|
@ -98,6 +104,9 @@ func (c *Client) Open(ctx context.Context) error {
|
|||
if err := c.DashboardsStore.Migrate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.ConfigStore.Migrate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/bolt/internal"
|
||||
)
|
||||
|
||||
// Ensure ConfigStore implements chronograf.ConfigStore.
|
||||
var _ chronograf.ConfigStore = &ConfigStore{}
|
||||
|
||||
// ConfigBucket is used to store chronograf application state
|
||||
var ConfigBucket = []byte("ConfigV1")
|
||||
|
||||
// configID is the boltDB key where the configuration object is stored
|
||||
var configID = []byte("config/v1")
|
||||
|
||||
// ConfigStore uses bolt to store and retrieve global
|
||||
// application configuration
|
||||
type ConfigStore struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (s *ConfigStore) Migrate(ctx context.Context) error {
|
||||
if _, err := s.Get(ctx); err != nil {
|
||||
return s.Initialize(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigStore) Initialize(ctx context.Context) error {
|
||||
cfg := chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: true,
|
||||
},
|
||||
}
|
||||
return s.Update(ctx, &cfg)
|
||||
}
|
||||
|
||||
func (s *ConfigStore) Get(ctx context.Context) (*chronograf.Config, error) {
|
||||
var cfg chronograf.Config
|
||||
err := s.client.db.View(func(tx *bolt.Tx) error {
|
||||
v := tx.Bucket(ConfigBucket).Get(configID)
|
||||
if v == nil {
|
||||
return chronograf.ErrConfigNotFound
|
||||
}
|
||||
return internal.UnmarshalConfig(v, &cfg)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func (s *ConfigStore) Update(ctx context.Context, cfg *chronograf.Config) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("config provided was nil")
|
||||
}
|
||||
return s.client.db.Update(func(tx *bolt.Tx) error {
|
||||
if v, err := internal.MarshalConfig(cfg); err != nil {
|
||||
return err
|
||||
} else if err := tx.Bucket(ConfigBucket).Put(configID, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package bolt_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func TestConfig_Get(t *testing.T) {
|
||||
type wants struct {
|
||||
config *chronograf.Config
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get config",
|
||||
wants: wants{
|
||||
config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, err := NewTestClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := client.Open(context.TODO()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
s := client.ConfigStore
|
||||
got, err := s.Get(context.Background())
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
if diff := cmp.Diff(got, tt.wants.config); diff != "" {
|
||||
t.Errorf("%q. ConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Update(t *testing.T) {
|
||||
type args struct {
|
||||
config *chronograf.Config
|
||||
}
|
||||
type wants struct {
|
||||
config *chronograf.Config
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Set config",
|
||||
args: args{
|
||||
config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, err := NewTestClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := client.Open(context.TODO()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
s := client.ConfigStore
|
||||
err = s.Update(context.Background(), tt.args.config)
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
|
||||
got, _ := s.Get(context.Background())
|
||||
if (tt.wants.err != nil) != (err != nil) {
|
||||
t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, tt.wants.config); diff != "" {
|
||||
t.Errorf("%q. ConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package internal
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/influxdata/chronograf"
|
||||
|
@ -591,3 +592,39 @@ func UnmarshalOrganizationPB(data []byte, o *Organization) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalConfig encodes a config to binary protobuf format.
|
||||
func MarshalConfig(c *chronograf.Config) ([]byte, error) {
|
||||
return MarshalConfigPB(&Config{
|
||||
Auth: &AuthConfig{
|
||||
SuperAdminNewUsers: c.Auth.SuperAdminNewUsers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// MarshalConfigPB encodes a config to binary protobuf format.
|
||||
func MarshalConfigPB(c *Config) ([]byte, error) {
|
||||
return proto.Marshal(c)
|
||||
}
|
||||
|
||||
// UnmarshalConfig decodes a config from binary protobuf data.
|
||||
func UnmarshalConfig(data []byte, c *chronograf.Config) error {
|
||||
var pb Config
|
||||
if err := UnmarshalConfigPB(data, &pb); err != nil {
|
||||
return err
|
||||
}
|
||||
if pb.Auth == nil {
|
||||
return fmt.Errorf("Auth config is nil")
|
||||
}
|
||||
c.Auth.SuperAdminNewUsers = pb.Auth.SuperAdminNewUsers
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalConfigPB decodes a config from binary protobuf data.
|
||||
func UnmarshalConfigPB(data []byte, c *Config) error {
|
||||
if err := proto.Unmarshal(data, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ It has these top-level messages:
|
|||
User
|
||||
Role
|
||||
Organization
|
||||
Config
|
||||
AuthConfig
|
||||
*/
|
||||
package internal
|
||||
|
||||
|
@ -389,6 +391,31 @@ func (m *Organization) String() string { return proto.CompactTextStri
|
|||
func (*Organization) ProtoMessage() {}
|
||||
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
|
||||
|
||||
type Config struct {
|
||||
Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth" json:"Auth,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Config) Reset() { *m = Config{} }
|
||||
func (m *Config) String() string { return proto.CompactTextString(m) }
|
||||
func (*Config) ProtoMessage() {}
|
||||
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
|
||||
|
||||
func (m *Config) GetAuth() *AuthConfig {
|
||||
if m != nil {
|
||||
return m.Auth
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
SuperAdminNewUsers bool `protobuf:"varint,1,opt,name=SuperAdminNewUsers,proto3" json:"SuperAdminNewUsers,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AuthConfig) Reset() { *m = AuthConfig{} }
|
||||
func (m *AuthConfig) String() string { return proto.CompactTextString(m) }
|
||||
func (*AuthConfig) ProtoMessage() {}
|
||||
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Source)(nil), "internal.Source")
|
||||
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
|
||||
|
@ -408,89 +435,94 @@ func init() {
|
|||
proto.RegisterType((*User)(nil), "internal.User")
|
||||
proto.RegisterType((*Role)(nil), "internal.Role")
|
||||
proto.RegisterType((*Organization)(nil), "internal.Organization")
|
||||
proto.RegisterType((*Config)(nil), "internal.Config")
|
||||
proto.RegisterType((*AuthConfig)(nil), "internal.AuthConfig")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
||||
|
||||
var fileDescriptorInternal = []byte{
|
||||
// 1264 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x57, 0xdf, 0x8e, 0xdb, 0xc4,
|
||||
0x17, 0x96, 0xe3, 0x38, 0xb1, 0x4f, 0xb6, 0xfd, 0x55, 0xf3, 0xab, 0xa8, 0x29, 0x12, 0x0a, 0x16,
|
||||
0x88, 0x45, 0xd0, 0x05, 0xb5, 0x42, 0x42, 0x5c, 0x20, 0x65, 0x37, 0xa8, 0x2c, 0xfd, 0xb7, 0x9d,
|
||||
0x74, 0xcb, 0x15, 0xaa, 0x26, 0xce, 0x49, 0x62, 0xd5, 0xb1, 0xcd, 0xd8, 0xde, 0x8d, 0x79, 0x18,
|
||||
0x24, 0x24, 0x9e, 0x00, 0x71, 0xcf, 0x2d, 0xe2, 0x96, 0x77, 0xe0, 0x15, 0xb8, 0x45, 0x67, 0x66,
|
||||
0xec, 0x38, 0x9b, 0x50, 0xf5, 0x02, 0x71, 0x37, 0xdf, 0x39, 0x93, 0x33, 0x67, 0xce, 0xf9, 0xce,
|
||||
0x37, 0x0e, 0x5c, 0x8f, 0x92, 0x02, 0x65, 0x22, 0xe2, 0xa3, 0x4c, 0xa6, 0x45, 0xca, 0xdc, 0x1a,
|
||||
0x07, 0x7f, 0x76, 0xa0, 0x37, 0x49, 0x4b, 0x19, 0x22, 0xbb, 0x0e, 0x9d, 0xd3, 0xb1, 0x6f, 0x0d,
|
||||
0xad, 0x43, 0x9b, 0x77, 0x4e, 0xc7, 0x8c, 0x41, 0xf7, 0xb1, 0x58, 0xa1, 0xdf, 0x19, 0x5a, 0x87,
|
||||
0x1e, 0x57, 0x6b, 0xb2, 0x3d, 0xab, 0x32, 0xf4, 0x6d, 0x6d, 0xa3, 0x35, 0xbb, 0x0d, 0xee, 0x79,
|
||||
0x4e, 0xd1, 0x56, 0xe8, 0x77, 0x95, 0xbd, 0xc1, 0xe4, 0x3b, 0x13, 0x79, 0x7e, 0x99, 0xca, 0x99,
|
||||
0xef, 0x68, 0x5f, 0x8d, 0xd9, 0x0d, 0xb0, 0xcf, 0xf9, 0x43, 0xbf, 0xa7, 0xcc, 0xb4, 0x64, 0x3e,
|
||||
0xf4, 0xc7, 0x38, 0x17, 0x65, 0x5c, 0xf8, 0xfd, 0xa1, 0x75, 0xe8, 0xf2, 0x1a, 0x52, 0x9c, 0x67,
|
||||
0x18, 0xe3, 0x42, 0x8a, 0xb9, 0xef, 0xea, 0x38, 0x35, 0x66, 0x47, 0xc0, 0x4e, 0x93, 0x1c, 0xc3,
|
||||
0x52, 0xe2, 0xe4, 0x65, 0x94, 0x3d, 0x47, 0x19, 0xcd, 0x2b, 0xdf, 0x53, 0x01, 0xf6, 0x78, 0xe8,
|
||||
0x94, 0x47, 0x58, 0x08, 0x3a, 0x1b, 0x54, 0xa8, 0x1a, 0xb2, 0x00, 0x0e, 0x26, 0x4b, 0x21, 0x71,
|
||||
0x36, 0xc1, 0x50, 0x62, 0xe1, 0x0f, 0x94, 0x7b, 0xcb, 0x46, 0x7b, 0x9e, 0xc8, 0x85, 0x48, 0xa2,
|
||||
0xef, 0x45, 0x11, 0xa5, 0x89, 0x7f, 0xa0, 0xf7, 0xb4, 0x6d, 0x54, 0x25, 0x9e, 0xc6, 0xe8, 0x5f,
|
||||
0xd3, 0x55, 0xa2, 0x75, 0xf0, 0x8b, 0x05, 0xde, 0x58, 0xe4, 0xcb, 0x69, 0x2a, 0xe4, 0xec, 0xb5,
|
||||
0x6a, 0x7d, 0x07, 0x9c, 0x10, 0xe3, 0x38, 0xf7, 0xed, 0xa1, 0x7d, 0x38, 0xb8, 0x7b, 0xeb, 0xa8,
|
||||
0x69, 0x62, 0x13, 0xe7, 0x04, 0xe3, 0x98, 0xeb, 0x5d, 0xec, 0x13, 0xf0, 0x0a, 0x5c, 0x65, 0xb1,
|
||||
0x28, 0x30, 0xf7, 0xbb, 0xea, 0x27, 0x6c, 0xf3, 0x93, 0x67, 0xc6, 0xc5, 0x37, 0x9b, 0x76, 0xae,
|
||||
0xe2, 0xec, 0x5e, 0x25, 0xf8, 0xa3, 0x03, 0xd7, 0xb6, 0x8e, 0x63, 0x07, 0x60, 0xad, 0x55, 0xe6,
|
||||
0x0e, 0xb7, 0xd6, 0x84, 0x2a, 0x95, 0xb5, 0xc3, 0xad, 0x8a, 0xd0, 0xa5, 0xe2, 0x86, 0xc3, 0xad,
|
||||
0x4b, 0x42, 0x4b, 0xc5, 0x08, 0x87, 0x5b, 0x4b, 0xf6, 0x01, 0xf4, 0xbf, 0x2b, 0x51, 0x46, 0x98,
|
||||
0xfb, 0x8e, 0xca, 0xee, 0x7f, 0x9b, 0xec, 0x9e, 0x96, 0x28, 0x2b, 0x5e, 0xfb, 0xa9, 0x1a, 0x8a,
|
||||
0x4d, 0x9a, 0x1a, 0x6a, 0x4d, 0xb6, 0x82, 0x98, 0xd7, 0xd7, 0x36, 0x5a, 0x9b, 0x2a, 0x6a, 0x3e,
|
||||
0x50, 0x15, 0x3f, 0x85, 0xae, 0x58, 0x63, 0xee, 0x7b, 0x2a, 0xfe, 0x3b, 0xff, 0x50, 0xb0, 0xa3,
|
||||
0xd1, 0x1a, 0xf3, 0x2f, 0x93, 0x42, 0x56, 0x5c, 0x6d, 0x67, 0xef, 0x43, 0x2f, 0x4c, 0xe3, 0x54,
|
||||
0xe6, 0x3e, 0x5c, 0x4d, 0xec, 0x84, 0xec, 0xdc, 0xb8, 0x6f, 0xdf, 0x07, 0xaf, 0xf9, 0x2d, 0xd1,
|
||||
0xf7, 0x25, 0x56, 0xaa, 0x12, 0x1e, 0xa7, 0x25, 0x7b, 0x17, 0x9c, 0x0b, 0x11, 0x97, 0xba, 0x8b,
|
||||
0x83, 0xbb, 0xd7, 0x37, 0x61, 0x46, 0xeb, 0x28, 0xe7, 0xda, 0xf9, 0x79, 0xe7, 0x33, 0x2b, 0x58,
|
||||
0x80, 0xa3, 0x22, 0xb7, 0x78, 0xe0, 0xd5, 0x3c, 0x50, 0xf3, 0xd5, 0x69, 0xcd, 0xd7, 0x0d, 0xb0,
|
||||
0xbf, 0xc2, 0xb5, 0x19, 0x39, 0x5a, 0x36, 0x6c, 0xe9, 0xb6, 0xd8, 0x72, 0x13, 0x9c, 0xe7, 0xea,
|
||||
0x70, 0xdd, 0x45, 0x0d, 0x82, 0x9f, 0x2d, 0xe8, 0xd2, 0xe1, 0xd4, 0xeb, 0x18, 0x17, 0x22, 0xac,
|
||||
0x8e, 0xd3, 0x32, 0x99, 0xe5, 0xbe, 0x35, 0xb4, 0x0f, 0x6d, 0xbe, 0x65, 0x63, 0x6f, 0x40, 0x6f,
|
||||
0xaa, 0xbd, 0x9d, 0xa1, 0x7d, 0xe8, 0x71, 0x83, 0x28, 0x74, 0x2c, 0xa6, 0x18, 0x9b, 0x14, 0x34,
|
||||
0xa0, 0xdd, 0x99, 0xc4, 0x79, 0xb4, 0x36, 0x69, 0x18, 0x44, 0xf6, 0xbc, 0x9c, 0x93, 0x5d, 0x67,
|
||||
0x62, 0x10, 0x25, 0x3d, 0x15, 0x79, 0xd3, 0x54, 0x5a, 0x53, 0xe4, 0x3c, 0x14, 0x71, 0xdd, 0x55,
|
||||
0x0d, 0x82, 0x5f, 0x2d, 0x9a, 0x76, 0xcd, 0xd2, 0x9d, 0x0a, 0xbd, 0x09, 0x2e, 0x31, 0xf8, 0xc5,
|
||||
0x85, 0x90, 0xa6, 0x4a, 0x7d, 0xc2, 0xcf, 0x85, 0x64, 0x1f, 0x43, 0x4f, 0x95, 0x78, 0xcf, 0xc4,
|
||||
0xd4, 0xe1, 0x54, 0x55, 0xb8, 0xd9, 0xd6, 0x70, 0xaa, 0xdb, 0xe2, 0x54, 0x73, 0x59, 0xa7, 0x7d,
|
||||
0xd9, 0x3b, 0xe0, 0x10, 0x39, 0x2b, 0x95, 0xfd, 0xde, 0xc8, 0x9a, 0xc2, 0x7a, 0x57, 0x70, 0x0e,
|
||||
0xd7, 0xb6, 0x4e, 0x6c, 0x4e, 0xb2, 0xb6, 0x4f, 0xda, 0xd0, 0xc5, 0x33, 0xf4, 0x20, 0xa5, 0xcb,
|
||||
0x31, 0xc6, 0xb0, 0xc0, 0x99, 0xaa, 0xb7, 0xcb, 0x1b, 0x1c, 0xfc, 0x68, 0x6d, 0xe2, 0xaa, 0xf3,
|
||||
0x48, 0xcb, 0xc2, 0x74, 0xb5, 0x12, 0xc9, 0xcc, 0x84, 0xae, 0x21, 0xd5, 0x6d, 0x36, 0x35, 0xa1,
|
||||
0x3b, 0xb3, 0x29, 0x61, 0x99, 0x99, 0x0e, 0x76, 0x64, 0xc6, 0x86, 0x30, 0x58, 0xa1, 0xc8, 0x4b,
|
||||
0x89, 0x2b, 0x4c, 0x0a, 0x53, 0x82, 0xb6, 0x89, 0xdd, 0x82, 0x7e, 0x21, 0x16, 0x2f, 0x88, 0xe4,
|
||||
0xa6, 0x93, 0x85, 0x58, 0x3c, 0xc0, 0x8a, 0xbd, 0x05, 0xde, 0x3c, 0xc2, 0x78, 0xa6, 0x5c, 0xba,
|
||||
0x9d, 0xae, 0x32, 0x3c, 0xc0, 0x2a, 0xf8, 0xcd, 0x82, 0xde, 0x04, 0xe5, 0x05, 0xca, 0xd7, 0x12,
|
||||
0xb9, 0xf6, 0xe3, 0x61, 0xbf, 0xe2, 0xf1, 0xe8, 0xee, 0x7f, 0x3c, 0x9c, 0xcd, 0xe3, 0x71, 0x13,
|
||||
0x9c, 0x89, 0x0c, 0x4f, 0xc7, 0x2a, 0x23, 0x9b, 0x6b, 0x40, 0x6c, 0x1c, 0x85, 0x45, 0x74, 0x81,
|
||||
0xe6, 0x45, 0x31, 0x68, 0x47, 0xfb, 0xdc, 0x3d, 0xda, 0xf7, 0x83, 0x05, 0xbd, 0x87, 0xa2, 0x4a,
|
||||
0xcb, 0x62, 0x87, 0x85, 0x43, 0x18, 0x8c, 0xb2, 0x2c, 0x8e, 0x42, 0xfd, 0x6b, 0x7d, 0xa3, 0xb6,
|
||||
0x89, 0x76, 0x3c, 0x6a, 0xd5, 0x57, 0xdf, 0xad, 0x6d, 0x22, 0xb9, 0x38, 0x51, 0xfa, 0xae, 0xc5,
|
||||
0xba, 0x25, 0x17, 0x5a, 0xd6, 0x95, 0x93, 0x8a, 0x30, 0x2a, 0x8b, 0x74, 0x1e, 0xa7, 0x97, 0xea,
|
||||
0xb6, 0x2e, 0x6f, 0x70, 0xf0, 0x7b, 0x07, 0xba, 0xff, 0x95, 0x26, 0x1f, 0x80, 0x15, 0x99, 0x66,
|
||||
0x5b, 0x51, 0xa3, 0xd0, 0xfd, 0x96, 0x42, 0xfb, 0xd0, 0xaf, 0xa4, 0x48, 0x16, 0x98, 0xfb, 0xae,
|
||||
0x52, 0x97, 0x1a, 0x2a, 0x8f, 0x9a, 0x23, 0x2d, 0xcd, 0x1e, 0xaf, 0x61, 0x33, 0x17, 0xd0, 0x9a,
|
||||
0x8b, 0x8f, 0x8c, 0x8a, 0x0f, 0x54, 0x46, 0xfe, 0x76, 0x59, 0xae, 0x8a, 0xf7, 0xbf, 0xa7, 0xc9,
|
||||
0x7f, 0x59, 0xe0, 0x34, 0x43, 0x75, 0xb2, 0x3d, 0x54, 0x27, 0x9b, 0xa1, 0x1a, 0x1f, 0xd7, 0x43,
|
||||
0x35, 0x3e, 0x26, 0xcc, 0xcf, 0xea, 0xa1, 0xe2, 0x67, 0xd4, 0xac, 0xfb, 0x32, 0x2d, 0xb3, 0xe3,
|
||||
0x4a, 0x77, 0xd5, 0xe3, 0x0d, 0x26, 0x26, 0x7e, 0xb3, 0x44, 0x69, 0x4a, 0xed, 0x71, 0x83, 0x88,
|
||||
0xb7, 0x0f, 0x95, 0xe0, 0xe8, 0xe2, 0x6a, 0xc0, 0xde, 0x03, 0x87, 0x53, 0xf1, 0x54, 0x85, 0xb7,
|
||||
0xfa, 0xa2, 0xcc, 0x5c, 0x7b, 0x29, 0xa8, 0xfe, 0x7a, 0x33, 0x04, 0xae, 0xbf, 0xe5, 0x3e, 0x84,
|
||||
0xde, 0x64, 0x19, 0xcd, 0x8b, 0xfa, 0x2d, 0xfc, 0x7f, 0x4b, 0xb0, 0xa2, 0x15, 0x2a, 0x1f, 0x37,
|
||||
0x5b, 0x82, 0xa7, 0xe0, 0x35, 0xc6, 0x4d, 0x3a, 0x56, 0x3b, 0x1d, 0x06, 0xdd, 0xf3, 0x24, 0x2a,
|
||||
0xea, 0xd1, 0xa5, 0x35, 0x5d, 0xf6, 0x69, 0x29, 0x92, 0x22, 0x2a, 0xaa, 0x7a, 0x74, 0x6b, 0x1c,
|
||||
0xdc, 0x33, 0xe9, 0x53, 0xb8, 0xf3, 0x2c, 0x43, 0x69, 0x64, 0x40, 0x03, 0x75, 0x48, 0x7a, 0x89,
|
||||
0x5a, 0xc1, 0x6d, 0xae, 0x41, 0xf0, 0x2d, 0x78, 0xa3, 0x18, 0x65, 0xc1, 0xcb, 0x18, 0xf7, 0xbd,
|
||||
0x8c, 0x5f, 0x4f, 0x9e, 0x3c, 0xae, 0x33, 0xa0, 0xf5, 0x66, 0xe4, 0xed, 0x2b, 0x23, 0xff, 0x40,
|
||||
0x64, 0xe2, 0x74, 0xac, 0x78, 0x6e, 0x73, 0x83, 0x82, 0x9f, 0x2c, 0xe8, 0x92, 0xb6, 0xb4, 0x42,
|
||||
0x77, 0x5f, 0xa5, 0x4b, 0x67, 0x32, 0xbd, 0x88, 0x66, 0x28, 0xeb, 0xcb, 0xd5, 0x58, 0x15, 0x3d,
|
||||
0x5c, 0x62, 0xf3, 0x00, 0x1b, 0x44, 0x5c, 0xa3, 0x4f, 0xbd, 0x7a, 0x96, 0x5a, 0x5c, 0x23, 0x33,
|
||||
0xd7, 0x4e, 0xf6, 0x36, 0xc0, 0xa4, 0xcc, 0x50, 0x8e, 0x66, 0xab, 0x28, 0x51, 0x4d, 0x77, 0x79,
|
||||
0xcb, 0x12, 0x7c, 0xa1, 0x3f, 0x1e, 0x77, 0x14, 0xca, 0xda, 0xff, 0xa1, 0x79, 0x35, 0xf3, 0x20,
|
||||
0xde, 0xfe, 0xdd, 0x6b, 0xdd, 0x76, 0x08, 0x03, 0xf3, 0xa5, 0xad, 0xbe, 0x5b, 0x8d, 0x58, 0xb5,
|
||||
0x4c, 0x74, 0xe7, 0xb3, 0x72, 0x1a, 0x47, 0xa1, 0xba, 0xb3, 0xcb, 0x0d, 0x9a, 0xf6, 0xd4, 0x1f,
|
||||
0x8a, 0x7b, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0xe0, 0xc4, 0x7a, 0x3e, 0x62, 0x0c, 0x00, 0x00,
|
||||
// 1310 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44,
|
||||
0x10, 0x97, 0x63, 0x3b, 0xb1, 0x27, 0xd7, 0x52, 0x99, 0x8a, 0x9a, 0x22, 0xa1, 0x60, 0x81, 0x08,
|
||||
0x82, 0x1e, 0xe8, 0x2a, 0x24, 0x84, 0x10, 0x52, 0xee, 0x82, 0xca, 0xd1, 0x7f, 0xd7, 0x4d, 0xaf,
|
||||
0x3c, 0xa1, 0x6a, 0xe3, 0x4c, 0x12, 0xab, 0x8e, 0x6d, 0xd6, 0xf6, 0x5d, 0xcc, 0x87, 0x41, 0x42,
|
||||
0xe2, 0x13, 0x20, 0xde, 0x79, 0x45, 0xbc, 0xf2, 0x1d, 0xf8, 0x0a, 0xbc, 0xa2, 0xd9, 0x5d, 0x3b,
|
||||
0xce, 0x25, 0x54, 0x7d, 0x40, 0xbc, 0xed, 0x6f, 0x66, 0x3d, 0x3b, 0x7f, 0x7e, 0x33, 0xbb, 0x86,
|
||||
0xeb, 0x51, 0x52, 0xa0, 0x48, 0x78, 0x7c, 0x98, 0x89, 0xb4, 0x48, 0x3d, 0xa7, 0xc6, 0xc1, 0x5f,
|
||||
0x1d, 0xe8, 0x4e, 0xd2, 0x52, 0x84, 0xe8, 0x5d, 0x87, 0xce, 0xe9, 0xd8, 0x37, 0x06, 0xc6, 0xd0,
|
||||
0x64, 0x9d, 0xd3, 0xb1, 0xe7, 0x81, 0xf5, 0x88, 0xaf, 0xd0, 0xef, 0x0c, 0x8c, 0xa1, 0xcb, 0xe4,
|
||||
0x9a, 0x64, 0x4f, 0xab, 0x0c, 0x7d, 0x53, 0xc9, 0x68, 0xed, 0xdd, 0x06, 0xe7, 0x3c, 0x27, 0x6b,
|
||||
0x2b, 0xf4, 0x2d, 0x29, 0x6f, 0x30, 0xe9, 0xce, 0x78, 0x9e, 0x5f, 0xa6, 0x62, 0xe6, 0xdb, 0x4a,
|
||||
0x57, 0x63, 0xef, 0x06, 0x98, 0xe7, 0xec, 0x81, 0xdf, 0x95, 0x62, 0x5a, 0x7a, 0x3e, 0xf4, 0xc6,
|
||||
0x38, 0xe7, 0x65, 0x5c, 0xf8, 0xbd, 0x81, 0x31, 0x74, 0x58, 0x0d, 0xc9, 0xce, 0x53, 0x8c, 0x71,
|
||||
0x21, 0xf8, 0xdc, 0x77, 0x94, 0x9d, 0x1a, 0x7b, 0x87, 0xe0, 0x9d, 0x26, 0x39, 0x86, 0xa5, 0xc0,
|
||||
0xc9, 0x8b, 0x28, 0x7b, 0x86, 0x22, 0x9a, 0x57, 0xbe, 0x2b, 0x0d, 0xec, 0xd1, 0xd0, 0x29, 0x0f,
|
||||
0xb1, 0xe0, 0x74, 0x36, 0x48, 0x53, 0x35, 0xf4, 0x02, 0x38, 0x98, 0x2c, 0xb9, 0xc0, 0xd9, 0x04,
|
||||
0x43, 0x81, 0x85, 0xdf, 0x97, 0xea, 0x2d, 0x19, 0xed, 0x79, 0x2c, 0x16, 0x3c, 0x89, 0x7e, 0xe0,
|
||||
0x45, 0x94, 0x26, 0xfe, 0x81, 0xda, 0xd3, 0x96, 0x51, 0x96, 0x58, 0x1a, 0xa3, 0x7f, 0x4d, 0x65,
|
||||
0x89, 0xd6, 0xc1, 0xaf, 0x06, 0xb8, 0x63, 0x9e, 0x2f, 0xa7, 0x29, 0x17, 0xb3, 0x57, 0xca, 0xf5,
|
||||
0x1d, 0xb0, 0x43, 0x8c, 0xe3, 0xdc, 0x37, 0x07, 0xe6, 0xb0, 0x7f, 0x74, 0xeb, 0xb0, 0x29, 0x62,
|
||||
0x63, 0xe7, 0x04, 0xe3, 0x98, 0xa9, 0x5d, 0xde, 0x27, 0xe0, 0x16, 0xb8, 0xca, 0x62, 0x5e, 0x60,
|
||||
0xee, 0x5b, 0xf2, 0x13, 0x6f, 0xf3, 0xc9, 0x53, 0xad, 0x62, 0x9b, 0x4d, 0x3b, 0xa1, 0xd8, 0xbb,
|
||||
0xa1, 0x04, 0x7f, 0x76, 0xe0, 0xda, 0xd6, 0x71, 0xde, 0x01, 0x18, 0x6b, 0xe9, 0xb9, 0xcd, 0x8c,
|
||||
0x35, 0xa1, 0x4a, 0x7a, 0x6d, 0x33, 0xa3, 0x22, 0x74, 0x29, 0xb9, 0x61, 0x33, 0xe3, 0x92, 0xd0,
|
||||
0x52, 0x32, 0xc2, 0x66, 0xc6, 0xd2, 0xfb, 0x00, 0x7a, 0xdf, 0x97, 0x28, 0x22, 0xcc, 0x7d, 0x5b,
|
||||
0x7a, 0xf7, 0xda, 0xc6, 0xbb, 0x27, 0x25, 0x8a, 0x8a, 0xd5, 0x7a, 0xca, 0x86, 0x64, 0x93, 0xa2,
|
||||
0x86, 0x5c, 0x93, 0xac, 0x20, 0xe6, 0xf5, 0x94, 0x8c, 0xd6, 0x3a, 0x8b, 0x8a, 0x0f, 0x94, 0xc5,
|
||||
0x4f, 0xc1, 0xe2, 0x6b, 0xcc, 0x7d, 0x57, 0xda, 0x7f, 0xe7, 0x5f, 0x12, 0x76, 0x38, 0x5a, 0x63,
|
||||
0xfe, 0x55, 0x52, 0x88, 0x8a, 0xc9, 0xed, 0xde, 0xfb, 0xd0, 0x0d, 0xd3, 0x38, 0x15, 0xb9, 0x0f,
|
||||
0x57, 0x1d, 0x3b, 0x21, 0x39, 0xd3, 0xea, 0xdb, 0xf7, 0xc0, 0x6d, 0xbe, 0x25, 0xfa, 0xbe, 0xc0,
|
||||
0x4a, 0x66, 0xc2, 0x65, 0xb4, 0xf4, 0xde, 0x05, 0xfb, 0x82, 0xc7, 0xa5, 0xaa, 0x62, 0xff, 0xe8,
|
||||
0xfa, 0xc6, 0xcc, 0x68, 0x1d, 0xe5, 0x4c, 0x29, 0x3f, 0xef, 0x7c, 0x66, 0x04, 0x0b, 0xb0, 0xa5,
|
||||
0xe5, 0x16, 0x0f, 0xdc, 0x9a, 0x07, 0xb2, 0xbf, 0x3a, 0xad, 0xfe, 0xba, 0x01, 0xe6, 0xd7, 0xb8,
|
||||
0xd6, 0x2d, 0x47, 0xcb, 0x86, 0x2d, 0x56, 0x8b, 0x2d, 0x37, 0xc1, 0x7e, 0x26, 0x0f, 0x57, 0x55,
|
||||
0x54, 0x20, 0xf8, 0xc5, 0x00, 0x8b, 0x0e, 0xa7, 0x5a, 0xc7, 0xb8, 0xe0, 0x61, 0x75, 0x9c, 0x96,
|
||||
0xc9, 0x2c, 0xf7, 0x8d, 0x81, 0x39, 0x34, 0xd9, 0x96, 0xcc, 0x7b, 0x03, 0xba, 0x53, 0xa5, 0xed,
|
||||
0x0c, 0xcc, 0xa1, 0xcb, 0x34, 0x22, 0xd3, 0x31, 0x9f, 0x62, 0xac, 0x5d, 0x50, 0x80, 0x76, 0x67,
|
||||
0x02, 0xe7, 0xd1, 0x5a, 0xbb, 0xa1, 0x11, 0xc9, 0xf3, 0x72, 0x4e, 0x72, 0xe5, 0x89, 0x46, 0xe4,
|
||||
0xf4, 0x94, 0xe7, 0x4d, 0x51, 0x69, 0x4d, 0x96, 0xf3, 0x90, 0xc7, 0x75, 0x55, 0x15, 0x08, 0x7e,
|
||||
0x33, 0xa8, 0xdb, 0x15, 0x4b, 0x77, 0x32, 0xf4, 0x26, 0x38, 0xc4, 0xe0, 0xe7, 0x17, 0x5c, 0xe8,
|
||||
0x2c, 0xf5, 0x08, 0x3f, 0xe3, 0xc2, 0xfb, 0x18, 0xba, 0x32, 0xc5, 0x7b, 0x3a, 0xa6, 0x36, 0x27,
|
||||
0xb3, 0xc2, 0xf4, 0xb6, 0x86, 0x53, 0x56, 0x8b, 0x53, 0x4d, 0xb0, 0x76, 0x3b, 0xd8, 0x3b, 0x60,
|
||||
0x13, 0x39, 0x2b, 0xe9, 0xfd, 0x5e, 0xcb, 0x8a, 0xc2, 0x6a, 0x57, 0x70, 0x0e, 0xd7, 0xb6, 0x4e,
|
||||
0x6c, 0x4e, 0x32, 0xb6, 0x4f, 0xda, 0xd0, 0xc5, 0xd5, 0xf4, 0xa0, 0x49, 0x97, 0x63, 0x8c, 0x61,
|
||||
0x81, 0x33, 0x99, 0x6f, 0x87, 0x35, 0x38, 0xf8, 0xc9, 0xd8, 0xd8, 0x95, 0xe7, 0xd1, 0x2c, 0x0b,
|
||||
0xd3, 0xd5, 0x8a, 0x27, 0x33, 0x6d, 0xba, 0x86, 0x94, 0xb7, 0xd9, 0x54, 0x9b, 0xee, 0xcc, 0xa6,
|
||||
0x84, 0x45, 0xa6, 0x2b, 0xd8, 0x11, 0x99, 0x37, 0x80, 0xfe, 0x0a, 0x79, 0x5e, 0x0a, 0x5c, 0x61,
|
||||
0x52, 0xe8, 0x14, 0xb4, 0x45, 0xde, 0x2d, 0xe8, 0x15, 0x7c, 0xf1, 0x9c, 0x48, 0xae, 0x2b, 0x59,
|
||||
0xf0, 0xc5, 0x7d, 0xac, 0xbc, 0xb7, 0xc0, 0x9d, 0x47, 0x18, 0xcf, 0xa4, 0x4a, 0x95, 0xd3, 0x91,
|
||||
0x82, 0xfb, 0x58, 0x05, 0xbf, 0x1b, 0xd0, 0x9d, 0xa0, 0xb8, 0x40, 0xf1, 0x4a, 0x43, 0xae, 0x7d,
|
||||
0x79, 0x98, 0x2f, 0xb9, 0x3c, 0xac, 0xfd, 0x97, 0x87, 0xbd, 0xb9, 0x3c, 0x6e, 0x82, 0x3d, 0x11,
|
||||
0xe1, 0xe9, 0x58, 0x7a, 0x64, 0x32, 0x05, 0x88, 0x8d, 0xa3, 0xb0, 0x88, 0x2e, 0x50, 0xdf, 0x28,
|
||||
0x1a, 0xed, 0xcc, 0x3e, 0x67, 0xcf, 0xec, 0xfb, 0xd1, 0x80, 0xee, 0x03, 0x5e, 0xa5, 0x65, 0xb1,
|
||||
0xc3, 0xc2, 0x01, 0xf4, 0x47, 0x59, 0x16, 0x47, 0xa1, 0xfa, 0x5a, 0x45, 0xd4, 0x16, 0xd1, 0x8e,
|
||||
0x87, 0xad, 0xfc, 0xaa, 0xd8, 0xda, 0x22, 0x1a, 0x17, 0x27, 0x72, 0xbe, 0xab, 0x61, 0xdd, 0x1a,
|
||||
0x17, 0x6a, 0xac, 0x4b, 0x25, 0x25, 0x61, 0x54, 0x16, 0xe9, 0x3c, 0x4e, 0x2f, 0x65, 0xb4, 0x0e,
|
||||
0x6b, 0x70, 0xf0, 0x47, 0x07, 0xac, 0xff, 0x6b, 0x26, 0x1f, 0x80, 0x11, 0xe9, 0x62, 0x1b, 0x51,
|
||||
0x33, 0xa1, 0x7b, 0xad, 0x09, 0xed, 0x43, 0xaf, 0x12, 0x3c, 0x59, 0x60, 0xee, 0x3b, 0x72, 0xba,
|
||||
0xd4, 0x50, 0x6a, 0x64, 0x1f, 0xa9, 0xd1, 0xec, 0xb2, 0x1a, 0x36, 0x7d, 0x01, 0xad, 0xbe, 0xf8,
|
||||
0x48, 0x4f, 0xf1, 0xbe, 0xf4, 0xc8, 0xdf, 0x4e, 0xcb, 0xd5, 0xe1, 0xfd, 0xdf, 0xcd, 0xe4, 0xbf,
|
||||
0x0d, 0xb0, 0x9b, 0xa6, 0x3a, 0xd9, 0x6e, 0xaa, 0x93, 0x4d, 0x53, 0x8d, 0x8f, 0xeb, 0xa6, 0x1a,
|
||||
0x1f, 0x13, 0x66, 0x67, 0x75, 0x53, 0xb1, 0x33, 0x2a, 0xd6, 0x3d, 0x91, 0x96, 0xd9, 0x71, 0xa5,
|
||||
0xaa, 0xea, 0xb2, 0x06, 0x13, 0x13, 0xbf, 0x5d, 0xa2, 0xd0, 0xa9, 0x76, 0x99, 0x46, 0xc4, 0xdb,
|
||||
0x07, 0x72, 0xe0, 0xa8, 0xe4, 0x2a, 0xe0, 0xbd, 0x07, 0x36, 0xa3, 0xe4, 0xc9, 0x0c, 0x6f, 0xd5,
|
||||
0x45, 0x8a, 0x99, 0xd2, 0x92, 0x51, 0xf5, 0x7a, 0xd3, 0x04, 0xae, 0xdf, 0x72, 0x1f, 0x42, 0x77,
|
||||
0xb2, 0x8c, 0xe6, 0x45, 0x7d, 0x17, 0xbe, 0xde, 0x1a, 0x58, 0xd1, 0x0a, 0xa5, 0x8e, 0xe9, 0x2d,
|
||||
0xc1, 0x13, 0x70, 0x1b, 0xe1, 0xc6, 0x1d, 0xa3, 0xed, 0x8e, 0x07, 0xd6, 0x79, 0x12, 0x15, 0x75,
|
||||
0xeb, 0xd2, 0x9a, 0x82, 0x7d, 0x52, 0xf2, 0xa4, 0x88, 0x8a, 0xaa, 0x6e, 0xdd, 0x1a, 0x07, 0x77,
|
||||
0xb5, 0xfb, 0x64, 0xee, 0x3c, 0xcb, 0x50, 0xe8, 0x31, 0xa0, 0x80, 0x3c, 0x24, 0xbd, 0x44, 0x35,
|
||||
0xc1, 0x4d, 0xa6, 0x40, 0xf0, 0x1d, 0xb8, 0xa3, 0x18, 0x45, 0xc1, 0xca, 0x18, 0xf7, 0xdd, 0x8c,
|
||||
0xdf, 0x4c, 0x1e, 0x3f, 0xaa, 0x3d, 0xa0, 0xf5, 0xa6, 0xe5, 0xcd, 0x2b, 0x2d, 0x7f, 0x9f, 0x67,
|
||||
0xfc, 0x74, 0x2c, 0x79, 0x6e, 0x32, 0x8d, 0x82, 0x9f, 0x0d, 0xb0, 0x68, 0xb6, 0xb4, 0x4c, 0x5b,
|
||||
0x2f, 0x9b, 0x4b, 0x67, 0x22, 0xbd, 0x88, 0x66, 0x28, 0xea, 0xe0, 0x6a, 0x2c, 0x93, 0x1e, 0x2e,
|
||||
0xb1, 0xb9, 0x80, 0x35, 0x22, 0xae, 0xd1, 0x53, 0xaf, 0xee, 0xa5, 0x16, 0xd7, 0x48, 0xcc, 0x94,
|
||||
0xd2, 0x7b, 0x1b, 0x60, 0x52, 0x66, 0x28, 0x46, 0xb3, 0x55, 0x94, 0xc8, 0xa2, 0x3b, 0xac, 0x25,
|
||||
0x09, 0xbe, 0x54, 0x8f, 0xc7, 0x9d, 0x09, 0x65, 0xec, 0x7f, 0x68, 0x5e, 0xf5, 0x3c, 0x88, 0xb7,
|
||||
0xbf, 0x7b, 0xa5, 0x68, 0x07, 0xd0, 0xd7, 0x2f, 0x6d, 0xf9, 0x6e, 0xd5, 0xc3, 0xaa, 0x25, 0xa2,
|
||||
0x98, 0xcf, 0xca, 0x69, 0x1c, 0x85, 0x32, 0x66, 0x87, 0x69, 0x14, 0x1c, 0x41, 0xf7, 0x24, 0x4d,
|
||||
0xe6, 0xd1, 0xc2, 0x1b, 0x82, 0x35, 0x2a, 0x8b, 0xa5, 0x3c, 0xa9, 0x7f, 0x74, 0xb3, 0xd5, 0x68,
|
||||
0x65, 0xb1, 0x54, 0x7b, 0x98, 0xdc, 0x11, 0x7c, 0x01, 0xb0, 0x91, 0xd1, 0xf3, 0x7d, 0x13, 0xfd,
|
||||
0x23, 0xbc, 0xa4, 0x12, 0xe5, 0xd2, 0x8a, 0xc3, 0xf6, 0x68, 0xa6, 0x5d, 0xf9, 0x0b, 0x73, 0xf7,
|
||||
0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf9, 0xde, 0x56, 0x70, 0xd4, 0x0c, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -164,6 +164,14 @@ message Organization {
|
|||
bool Public = 4; // Public specifies that users must be explicitly added to the organization
|
||||
}
|
||||
|
||||
message Config {
|
||||
AuthConfig Auth = 1; // Auth is the configuration for options that auth related
|
||||
}
|
||||
|
||||
message AuthConfig {
|
||||
bool SuperAdminNewUsers = 1; // SuperAdminNewUsers configuration option that specifies which users will auto become super admin
|
||||
}
|
||||
|
||||
// The following is a vim modeline, it autoconfigures vim to have the
|
||||
// appropriate tabbing and whitespace management to edit this file
|
||||
//
|
||||
|
|
|
@ -26,6 +26,7 @@ const (
|
|||
ErrOrganizationNotFound = Error("organization not found")
|
||||
ErrOrganizationAlreadyExists = Error("organization already exists")
|
||||
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
|
||||
ErrConfigNotFound = Error("cannot find configuration")
|
||||
)
|
||||
|
||||
// Error is a domain error encountered while processing chronograf requests
|
||||
|
@ -604,3 +605,30 @@ type OrganizationsStore interface {
|
|||
// DefaultOrganization returns the DefaultOrganization
|
||||
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"`
|
||||
}
|
||||
|
||||
// ConfigStore is the storage and retrieval of global application Config
|
||||
type ConfigStore interface {
|
||||
// Initialize creates the initial configuration
|
||||
Initialize(context.Context) error
|
||||
// Get retrieves the whole Config from the ConfigStore
|
||||
Get(context.Context) (*Config, error)
|
||||
// Update updates the whole Config in the ConfigStore
|
||||
Update(context.Context, *Config) error
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// ConfigStore stores global application configuration
|
||||
type ConfigStore struct {
|
||||
Config *chronograf.Config
|
||||
}
|
||||
|
||||
// Initialize is noop in mocks store
|
||||
func (c ConfigStore) Initialize(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the whole global application configuration
|
||||
func (c ConfigStore) Get(ctx context.Context) (*chronograf.Config, error) {
|
||||
return c.Config, nil
|
||||
}
|
||||
|
||||
// Update updates the whole global application configuration
|
||||
func (c ConfigStore) Update(ctx context.Context, config *chronograf.Config) error {
|
||||
c.Config = config
|
||||
return nil
|
||||
}
|
|
@ -14,6 +14,7 @@ type Store struct {
|
|||
UsersStore chronograf.UsersStore
|
||||
DashboardsStore chronograf.DashboardsStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
}
|
||||
|
||||
func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore {
|
||||
|
@ -39,3 +40,7 @@ func (s *Store) Organizations(ctx context.Context) chronograf.OrganizationsStore
|
|||
func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore {
|
||||
return s.DashboardsStore
|
||||
}
|
||||
|
||||
func (s *Store) Config(ctx context.Context) chronograf.ConfigStore {
|
||||
return s.ConfigStore
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package noop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// ensure ConfigStore implements chronograf.ConfigStore
|
||||
var _ chronograf.ConfigStore = &ConfigStore{}
|
||||
|
||||
type ConfigStore struct{}
|
||||
|
||||
// TODO(desa): this really should be removed
|
||||
func (s *ConfigStore) Initialize(context.Context) error {
|
||||
return fmt.Errorf("cannot initialize")
|
||||
}
|
||||
|
||||
func (s *ConfigStore) Get(context.Context) (*chronograf.Config, error) {
|
||||
return nil, chronograf.ErrConfigNotFound
|
||||
}
|
||||
|
||||
func (s *ConfigStore) Update(context.Context, *chronograf.Config) error {
|
||||
return fmt.Errorf("cannot update conifg")
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
type configResponse struct {
|
||||
Links selfLinks `json:"links"`
|
||||
chronograf.Config
|
||||
}
|
||||
|
||||
func newConfigResponse(config chronograf.Config) *configResponse {
|
||||
return &configResponse{
|
||||
Links: selfLinks{
|
||||
Self: "/chronograf/v1/config",
|
||||
},
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
|
||||
type authConfigResponse struct {
|
||||
Links selfLinks `json:"links"`
|
||||
chronograf.AuthConfig
|
||||
}
|
||||
|
||||
func newAuthConfigResponse(config chronograf.Config) *authConfigResponse {
|
||||
return &authConfigResponse{
|
||||
Links: selfLinks{
|
||||
Self: "/chronograf/v1/config/auth",
|
||||
},
|
||||
AuthConfig: config.Auth,
|
||||
}
|
||||
}
|
||||
|
||||
// Config retrieves the global application configuration
|
||||
func (s *Service) Config(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
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
|
||||
}
|
||||
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) {
|
||||
ctx := r.Context()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
ctx := r.Context()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if err := s.Store.Config(ctx).Update(ctx, config); err != nil {
|
||||
unknownErrorWithMessage(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/log"
|
||||
"github.com/influxdata/chronograf/mocks"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
type fields struct {
|
||||
ConfigStore chronograf.ConfigStore
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get global application configuration",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 200,
|
||||
contentType: "application/json",
|
||||
body: `{"auth": {"superAdminNewUsers": false}, "links": {"self": "/chronograf/v1/config"}}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Store: &mocks.Store{
|
||||
ConfigStore: tt.fields.ConfigStore,
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "http://any.url", nil)
|
||||
|
||||
s.Config(w, r)
|
||||
|
||||
resp := w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigSection(t *testing.T) {
|
||||
type fields struct {
|
||||
ConfigStore chronograf.ConfigStore
|
||||
}
|
||||
type args struct {
|
||||
section string
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Get auth configuration",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Store: &mocks.Store{
|
||||
ConfigStore: tt.fields.ConfigStore,
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
resp := w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceConfigSection(t *testing.T) {
|
||||
type fields struct {
|
||||
ConfigStore chronograf.ConfigStore
|
||||
}
|
||||
type args struct {
|
||||
section string
|
||||
payload interface{} // expects JSON serializable struct
|
||||
}
|
||||
type wants struct {
|
||||
statusCode int
|
||||
contentType string
|
||||
body string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "Set auth configuration",
|
||||
fields: fields{
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
section: "auth",
|
||||
payload: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: true,
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
statusCode: 200,
|
||||
contentType: "application/json",
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
Store: &mocks.Store{
|
||||
ConfigStore: tt.fields.ConfigStore,
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
resp := w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wants.statusCode {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode)
|
||||
}
|
||||
if tt.wants.contentType != "" && content != tt.wants.contentType {
|
||||
t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType)
|
||||
}
|
||||
if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq {
|
||||
t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,6 +5,11 @@ import (
|
|||
"net/url"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type getExternalLinksResponse struct {
|
||||
StatusFeed *string `json:"statusFeed,omitempty"` // Location of the a JSON Feed for client's Status page News Feed
|
||||
CustomLinks []CustomLink `json:"custom,omitempty"` // Any custom external links for client's User menu
|
||||
|
|
13
server/me.go
13
server/me.go
|
@ -320,10 +320,21 @@ func (s *Service) firstUser() bool {
|
|||
return numUsers == 0
|
||||
}
|
||||
func (s *Service) newUsersAreSuperAdmin() bool {
|
||||
// It's not necessary to enforce that the first user is superAdmin here, since
|
||||
// superAdminNewUsers defaults to true, but there's nothing else in the
|
||||
// application that dictates that it must be true.
|
||||
// So for that reason, we kept this here for now. We've discussed the
|
||||
// future possibility of allowing users to override default values via CLI and
|
||||
// this case could possibly happen then.
|
||||
if s.firstUser() {
|
||||
return true
|
||||
}
|
||||
return !s.SuperAdminFirstUserOnly
|
||||
serverCtx := serverContext(context.Background())
|
||||
cfg, err := s.Store.Config(serverCtx).Get(serverCtx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return cfg.Auth.SuperAdminNewUsers
|
||||
}
|
||||
|
||||
func (s *Service) usersOrganizations(ctx context.Context, u *chronograf.User) ([]chronograf.Organization, error) {
|
||||
|
|
|
@ -21,11 +21,11 @@ type MockUsers struct{}
|
|||
|
||||
func TestService_Me(t *testing.T) {
|
||||
type fields struct {
|
||||
UsersStore chronograf.UsersStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
SuperAdminFirstUserOnly bool
|
||||
UsersStore chronograf.UsersStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
|
@ -47,9 +47,15 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -365,9 +371,15 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: false,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -421,9 +433,15 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -477,9 +495,15 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -533,8 +557,14 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
UseAuth: true,
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -582,9 +612,15 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: false,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: false,
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
|
@ -598,9 +634,15 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
ConfigStore: &mocks.ConfigStore{
|
||||
Config: &chronograf.Config{
|
||||
Auth: chronograf.AuthConfig{
|
||||
SuperAdminNewUsers: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
principal: oauth2.Principal{
|
||||
|
@ -661,10 +703,10 @@ func TestService_Me(t *testing.T) {
|
|||
Store: &mocks.Store{
|
||||
UsersStore: tt.fields.UsersStore,
|
||||
OrganizationsStore: tt.fields.OrganizationsStore,
|
||||
ConfigStore: tt.fields.ConfigStore,
|
||||
},
|
||||
Logger: tt.fields.Logger,
|
||||
UseAuth: tt.fields.UseAuth,
|
||||
SuperAdminFirstUserOnly: tt.fields.SuperAdminFirstUserOnly,
|
||||
Logger: tt.fields.Logger,
|
||||
UseAuth: tt.fields.UseAuth,
|
||||
}
|
||||
|
||||
s.Me(tt.args.w, tt.args.r)
|
||||
|
|
|
@ -237,6 +237,11 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
router.PUT("/chronograf/v1/sources/:id/dbs/:dbid/rps/:rpid", EnsureEditor(service.UpdateRetentionPolicy))
|
||||
router.DELETE("/chronograf/v1/sources/:id/dbs/:dbid/rps/:rpid", EnsureEditor(service.DropRetentionPolicy))
|
||||
|
||||
// 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))
|
||||
|
||||
allRoutes := &AllRoutes{
|
||||
Logger: opts.Logger,
|
||||
StatusFeed: opts.StatusFeedURL,
|
||||
|
|
|
@ -36,6 +36,7 @@ type getRoutesResponse struct {
|
|||
Sources string `json:"sources"` // Location of the sources endpoint
|
||||
Me string `json:"me"` // Location of the me endpoint
|
||||
Dashboards string `json:"dashboards"` // Location of the dashboards endpoint
|
||||
Config getConfigLinksResponse `json:"config"` // Location of the config endpoint and its various sections
|
||||
Auth []AuthRoute `json:"auth"` // Location of all auth routes.
|
||||
Logout *string `json:"logout,omitempty"` // Location of the logout route for all auth routes
|
||||
ExternalLinks getExternalLinksResponse `json:"external"` // All external links for the client to use
|
||||
|
@ -68,7 +69,11 @@ func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
Me: "/chronograf/v1/me",
|
||||
Mappings: "/chronograf/v1/mappings",
|
||||
Dashboards: "/chronograf/v1/dashboards",
|
||||
Auth: make([]AuthRoute, len(a.AuthRoutes)), // We want to return at least an empty array, rather than null
|
||||
Config: getConfigLinksResponse{
|
||||
Self: "/chronograf/v1/config",
|
||||
Auth: "/chronograf/v1/config/auth",
|
||||
},
|
||||
Auth: make([]AuthRoute, len(a.AuthRoutes)), // We want to return at least an empty array, rather than null
|
||||
ExternalLinks: getExternalLinksResponse{
|
||||
StatusFeed: &a.StatusFeed,
|
||||
CustomLinks: customLinks,
|
||||
|
|
|
@ -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/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","auth":[],"external":{"statusFeed":""}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""}}
|
||||
`
|
||||
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/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","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":""}}
|
||||
`
|
||||
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/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","dashboards":"/chronograf/v1/dashboards","auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","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"}]}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutesWithExternalLinks\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
|
|
@ -52,12 +52,11 @@ type Server struct {
|
|||
|
||||
NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"type\":\"influx-enterprise\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"cubeapples\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"`
|
||||
|
||||
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
||||
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
||||
CannedPath string `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
|
||||
TokenSecret string `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"`
|
||||
AuthDuration time.Duration `long:"auth-duration" default:"720h" description:"Total duration of cookie life for authentication (in hours). 0 means authentication expires on browser close." env:"AUTH_DURATION"`
|
||||
SuperAdminFirstUserOnly bool `long:"superadmin-first-user-only" description:"All new users will not be given the SuperAdmin status" env:"SUPERADMIN_FIRST_USER_ONLY"`
|
||||
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
||||
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
||||
CannedPath string `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
|
||||
TokenSecret string `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"`
|
||||
AuthDuration time.Duration `long:"auth-duration" default:"720h" description:"Total duration of cookie life for authentication (in hours). 0 means authentication expires on browser close." env:"AUTH_DURATION"`
|
||||
|
||||
GithubClientID string `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"`
|
||||
GithubClientSecret string `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"`
|
||||
|
@ -303,7 +302,6 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
service := openService(ctx, s.BoltPath, layoutBuilder, sourcesBuilder, kapacitorBuilder, logger, s.useAuth())
|
||||
service.SuperAdminFirstUserOnly = s.SuperAdminFirstUserOnly
|
||||
if err := service.HandleNewSources(ctx, s.NewSources); err != nil {
|
||||
logger.
|
||||
WithField("component", "server").
|
||||
|
@ -441,7 +439,7 @@ func openService(ctx context.Context, boltPath string, lBuilder LayoutBuilder, s
|
|||
OrganizationsStore: db.OrganizationsStore,
|
||||
LayoutsStore: layouts,
|
||||
DashboardsStore: db.DashboardsStore,
|
||||
//OrganizationUsersStore: organizations.NewUsersStore(db.UsersStore),
|
||||
ConfigStore: db.ConfigStore,
|
||||
},
|
||||
Logger: logger,
|
||||
UseAuth: useAuth,
|
||||
|
|
|
@ -11,12 +11,11 @@ import (
|
|||
|
||||
// Service handles REST calls to the persistence
|
||||
type Service struct {
|
||||
Store DataStore
|
||||
TimeSeriesClient TimeSeriesClient
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
SuperAdminFirstUserOnly bool
|
||||
Databases chronograf.Databases
|
||||
Store DataStore
|
||||
TimeSeriesClient TimeSeriesClient
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
Databases chronograf.Databases
|
||||
}
|
||||
|
||||
// TimeSeriesClient returns the correct client for a time series database.
|
||||
|
|
|
@ -89,6 +89,7 @@ type DataStore interface {
|
|||
Users(ctx context.Context) chronograf.UsersStore
|
||||
Organizations(ctx context.Context) chronograf.OrganizationsStore
|
||||
Dashboards(ctx context.Context) chronograf.DashboardsStore
|
||||
Config(ctx context.Context) chronograf.ConfigStore
|
||||
}
|
||||
|
||||
// ensure that Store implements a DataStore
|
||||
|
@ -102,6 +103,7 @@ type Store struct {
|
|||
UsersStore chronograf.UsersStore
|
||||
DashboardsStore chronograf.DashboardsStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
ConfigStore chronograf.ConfigStore
|
||||
}
|
||||
|
||||
// Sources returns a noop.SourcesStore if the context has no organization specified
|
||||
|
@ -178,3 +180,14 @@ func (s *Store) Organizations(ctx context.Context) chronograf.OrganizationsStore
|
|||
}
|
||||
return &noop.OrganizationsStore{}
|
||||
}
|
||||
|
||||
// Config returns the underlying ConfigStore.
|
||||
func (s *Store) Config(ctx context.Context) chronograf.ConfigStore {
|
||||
if isServer := hasServerContext(ctx); isServer {
|
||||
return s.ConfigStore
|
||||
}
|
||||
if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin {
|
||||
return s.ConfigStore
|
||||
}
|
||||
return &noop.ConfigStore{}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ import React, {Component, PropTypes} from 'react'
|
|||
|
||||
import uuid from 'node-uuid'
|
||||
|
||||
import Authorized, {SUPERADMIN_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
|
||||
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'
|
||||
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
|
||||
import SlideToggle from 'shared/components/SlideToggle'
|
||||
|
||||
import {PUBLIC_TOOLTIP} from 'src/admin/constants/index'
|
||||
|
||||
|
@ -39,6 +42,8 @@ class OrganizationsTable extends Component {
|
|||
onChooseDefaultRole,
|
||||
onTogglePublic,
|
||||
currentOrganization,
|
||||
authConfig: {superAdminNewUsers},
|
||||
onChangeAuthConfig,
|
||||
} = this.props
|
||||
const {isCreatingOrganization} = this.state
|
||||
|
||||
|
@ -89,13 +94,35 @@ class OrganizationsTable extends Component {
|
|||
currentOrganization={currentOrganization}
|
||||
/>
|
||||
)}
|
||||
<Authorized requiredRole={SUPERADMIN_ROLE}>
|
||||
<table className="table v-center superadmin-config">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{width: 70}}>Config</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={{width: 70}}>
|
||||
<SlideToggle
|
||||
size="xs"
|
||||
active={superAdminNewUsers}
|
||||
onToggle={onChangeAuthConfig('superAdminNewUsers')}
|
||||
/>
|
||||
</td>
|
||||
<td>All new users are SuperAdmins</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Authorized>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
OrganizationsTable.propTypes = {
|
||||
organizations: arrayOf(
|
||||
|
@ -113,5 +140,9 @@ OrganizationsTable.propTypes = {
|
|||
onRenameOrg: func.isRequired,
|
||||
onTogglePublic: func.isRequired,
|
||||
onChooseDefaultRole: func.isRequired,
|
||||
onChangeAuthConfig: func.isRequired,
|
||||
authConfig: shape({
|
||||
superAdminNewUsers: bool,
|
||||
}),
|
||||
}
|
||||
export default OrganizationsTable
|
||||
|
|
|
@ -3,30 +3,36 @@ import {connect} from 'react-redux'
|
|||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import * as adminChronografActionCreators from 'src/admin/actions/chronograf'
|
||||
import * as configActionCreators from 'shared/actions/config'
|
||||
import {getMeAsync} from 'shared/actions/auth'
|
||||
|
||||
import OrganizationsTable from 'src/admin/components/chronograf/OrganizationsTable'
|
||||
|
||||
class OrganizationsPage extends Component {
|
||||
componentDidMount() {
|
||||
const {links, actions: {loadOrganizationsAsync}} = this.props
|
||||
const {
|
||||
links,
|
||||
actionsAdmin: {loadOrganizationsAsync},
|
||||
actionsConfig: {getAuthConfigAsync},
|
||||
} = this.props
|
||||
loadOrganizationsAsync(links.organizations)
|
||||
getAuthConfigAsync(links.config.auth)
|
||||
}
|
||||
|
||||
handleCreateOrganization = async organization => {
|
||||
const {links, actions: {createOrganizationAsync}} = this.props
|
||||
const {links, actionsAdmin: {createOrganizationAsync}} = this.props
|
||||
await createOrganizationAsync(links.organizations, organization)
|
||||
this.refreshMe()
|
||||
}
|
||||
|
||||
handleRenameOrganization = async (organization, name) => {
|
||||
const {actions: {updateOrganizationAsync}} = this.props
|
||||
const {actionsAdmin: {updateOrganizationAsync}} = this.props
|
||||
await updateOrganizationAsync(organization, {...organization, name})
|
||||
this.refreshMe()
|
||||
}
|
||||
|
||||
handleDeleteOrganization = organization => {
|
||||
const {actions: {deleteOrganizationAsync}} = this.props
|
||||
const {actionsAdmin: {deleteOrganizationAsync}} = this.props
|
||||
deleteOrganizationAsync(organization)
|
||||
this.refreshMe()
|
||||
}
|
||||
|
@ -37,7 +43,7 @@ class OrganizationsPage extends Component {
|
|||
}
|
||||
|
||||
handleTogglePublic = organization => {
|
||||
const {actions: {updateOrganizationAsync}} = this.props
|
||||
const {actionsAdmin: {updateOrganizationAsync}} = this.props
|
||||
updateOrganizationAsync(organization, {
|
||||
...organization,
|
||||
public: !organization.public,
|
||||
|
@ -45,34 +51,52 @@ class OrganizationsPage extends Component {
|
|||
}
|
||||
|
||||
handleChooseDefaultRole = (organization, defaultRole) => {
|
||||
const {actions: {updateOrganizationAsync}} = this.props
|
||||
const {actionsAdmin: {updateOrganizationAsync}} = this.props
|
||||
updateOrganizationAsync(organization, {...organization, defaultRole})
|
||||
// refreshMe is here to update the org's defaultRole in `me.organizations`
|
||||
this.refreshMe()
|
||||
}
|
||||
|
||||
handleUpdateAuthConfig = fieldName => updatedValue => {
|
||||
const {
|
||||
actionsConfig: {updateAuthConfigAsync},
|
||||
authConfig,
|
||||
links,
|
||||
} = this.props
|
||||
const updatedAuthConfig = {
|
||||
...authConfig,
|
||||
[fieldName]: updatedValue,
|
||||
}
|
||||
updateAuthConfigAsync(links.config.auth, authConfig, updatedAuthConfig)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {organizations, currentOrganization} = this.props
|
||||
const {organizations, currentOrganization, authConfig} = this.props
|
||||
|
||||
return (
|
||||
<OrganizationsTable
|
||||
organizations={organizations}
|
||||
currentOrganization={currentOrganization}
|
||||
onCreateOrg={this.handleCreateOrganization}
|
||||
onDeleteOrg={this.handleDeleteOrganization}
|
||||
onRenameOrg={this.handleRenameOrganization}
|
||||
onTogglePublic={this.handleTogglePublic}
|
||||
onChooseDefaultRole={this.handleChooseDefaultRole}
|
||||
currentOrganization={currentOrganization}
|
||||
authConfig={authConfig}
|
||||
onChangeAuthConfig={this.handleUpdateAuthConfig}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
OrganizationsPage.propTypes = {
|
||||
links: shape({
|
||||
organizations: string.isRequired,
|
||||
config: shape({
|
||||
auth: string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
organizations: arrayOf(
|
||||
shape({
|
||||
|
@ -81,26 +105,39 @@ OrganizationsPage.propTypes = {
|
|||
link: string,
|
||||
})
|
||||
),
|
||||
actions: shape({
|
||||
actionsAdmin: shape({
|
||||
loadOrganizationsAsync: func.isRequired,
|
||||
createOrganizationAsync: func.isRequired,
|
||||
updateOrganizationAsync: func.isRequired,
|
||||
deleteOrganizationAsync: func.isRequired,
|
||||
}),
|
||||
actionsConfig: shape({
|
||||
getAuthConfigAsync: func.isRequired,
|
||||
updateAuthConfigAsync: func.isRequired,
|
||||
}),
|
||||
getMe: func.isRequired,
|
||||
currentOrganization: shape({
|
||||
name: string.isRequired,
|
||||
id: string.isRequired,
|
||||
}),
|
||||
authConfig: shape({
|
||||
superAdminNewUsers: bool,
|
||||
}),
|
||||
}
|
||||
|
||||
const mapStateToProps = ({links, adminChronograf: {organizations}}) => ({
|
||||
const mapStateToProps = ({
|
||||
links,
|
||||
adminChronograf: {organizations},
|
||||
config: {auth: authConfig},
|
||||
}) => ({
|
||||
links,
|
||||
organizations,
|
||||
authConfig,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
actions: bindActionCreators(adminChronografActionCreators, dispatch),
|
||||
actionsAdmin: bindActionCreators(adminChronografActionCreators, dispatch),
|
||||
actionsConfig: bindActionCreators(configActionCreators, dispatch),
|
||||
getMe: bindActionCreators(getMeAsync, dispatch),
|
||||
})
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ import {isSameUser} from 'shared/reducers/helpers/auth'
|
|||
const initialState = {
|
||||
users: [],
|
||||
organizations: [],
|
||||
authConfig: {
|
||||
superAdminNewUsers: true,
|
||||
},
|
||||
}
|
||||
|
||||
const adminChronograf = (state = initialState, action) => {
|
||||
|
|
|
@ -64,6 +64,7 @@ export const getMeAsync = ({shouldResetMe = false} = {}) => async dispatch => {
|
|||
users,
|
||||
organizations,
|
||||
meLink,
|
||||
config,
|
||||
} = await getMeAJAX()
|
||||
dispatch(
|
||||
meGetCompleted({
|
||||
|
@ -72,7 +73,9 @@ export const getMeAsync = ({shouldResetMe = false} = {}) => async dispatch => {
|
|||
logoutLink,
|
||||
})
|
||||
)
|
||||
dispatch(linksReceived({external, users, organizations, me: meLink})) // TODO: put this before meGetCompleted... though for some reason it doesn't fire the first time then
|
||||
dispatch(
|
||||
linksReceived({external, users, organizations, me: meLink, config})
|
||||
) // TODO: put this before meGetCompleted... though for some reason it doesn't fire the first time then
|
||||
} catch (error) {
|
||||
dispatch(meGetFailed())
|
||||
dispatch(errorThrown(error))
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
getAuthConfig as getAuthConfigAJAX,
|
||||
updateAuthConfig as updateAuthConfigAJAX,
|
||||
} from 'shared/apis/config'
|
||||
|
||||
import {errorThrown} from 'shared/actions/errors'
|
||||
|
||||
export const getAuthConfigRequested = () => ({
|
||||
type: 'CHRONOGRAF_GET_AUTH_CONFIG_REQUESTED',
|
||||
})
|
||||
|
||||
export const getAuthConfigCompleted = authConfig => ({
|
||||
type: 'CHRONOGRAF_GET_AUTH_CONFIG_COMPLETED',
|
||||
payload: {
|
||||
authConfig,
|
||||
},
|
||||
})
|
||||
|
||||
export const getAuthConfigFailed = () => ({
|
||||
type: 'CHRONOGRAF_GET_AUTH_CONFIG_FAILED',
|
||||
})
|
||||
|
||||
export const updateAuthConfigRequested = authConfig => ({
|
||||
type: 'CHRONOGRAF_UPDATE_AUTH_CONFIG_REQUESTED',
|
||||
payload: {
|
||||
authConfig,
|
||||
},
|
||||
})
|
||||
|
||||
export const updateAuthConfigCompleted = () => ({
|
||||
type: 'CHRONOGRAF_UPDATE_AUTH_CONFIG_COMPLETED',
|
||||
})
|
||||
|
||||
export const updateAuthConfigFailed = authConfig => ({
|
||||
type: 'CHRONOGRAF_UPDATE_AUTH_CONFIG_FAILED',
|
||||
payload: {
|
||||
authConfig,
|
||||
},
|
||||
})
|
||||
|
||||
// async actions (thunks)
|
||||
export const getAuthConfigAsync = url => async dispatch => {
|
||||
dispatch(getAuthConfigRequested())
|
||||
try {
|
||||
const {data} = await getAuthConfigAJAX(url)
|
||||
dispatch(getAuthConfigCompleted(data)) // TODO: change authConfig in actions & reducers to reflect final shape
|
||||
} catch (error) {
|
||||
dispatch(errorThrown(error))
|
||||
dispatch(getAuthConfigFailed())
|
||||
}
|
||||
}
|
||||
|
||||
export const updateAuthConfigAsync = (
|
||||
url,
|
||||
oldAuthConfig,
|
||||
updatedAuthConfig
|
||||
) => async dispatch => {
|
||||
const newAuthConfig = {...oldAuthConfig, ...updatedAuthConfig}
|
||||
dispatch(updateAuthConfigRequested(newAuthConfig))
|
||||
try {
|
||||
await updateAuthConfigAJAX(url, newAuthConfig)
|
||||
dispatch(updateAuthConfigCompleted())
|
||||
} catch (error) {
|
||||
dispatch(errorThrown(error))
|
||||
dispatch(updateAuthConfigFailed(oldAuthConfig))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import AJAX from 'src/utils/ajax'
|
||||
|
||||
export const getAuthConfig = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
url,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export const updateAuthConfig = async (url, authConfig) => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'PUT',
|
||||
url,
|
||||
data: authConfig,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
|
@ -5,10 +5,14 @@ class SlideToggle extends Component {
|
|||
super(props)
|
||||
|
||||
this.state = {
|
||||
active: this.props.active,
|
||||
active: props.active,
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({active: nextProps.active})
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
const {onToggle} = this.props
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
const initialState = {
|
||||
links: {},
|
||||
auth: {},
|
||||
}
|
||||
|
||||
const config = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'CHRONOGRAF_GET_AUTH_CONFIG_COMPLETED':
|
||||
case 'CHRONOGRAF_UPDATE_AUTH_CONFIG_REQUESTED':
|
||||
case 'CHRONOGRAF_UPDATE_AUTH_CONFIG_FAILED': {
|
||||
const {authConfig: auth} = action.payload
|
||||
return {
|
||||
...state,
|
||||
auth: {...auth},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default config
|
|
@ -1,5 +1,6 @@
|
|||
import app from './app'
|
||||
import auth from './auth'
|
||||
import config from './config'
|
||||
import errors from './errors'
|
||||
import links from './links'
|
||||
import {notifications, dismissedNotifications} from './notifications'
|
||||
|
@ -8,6 +9,7 @@ import sources from './sources'
|
|||
export default {
|
||||
app,
|
||||
auth,
|
||||
config,
|
||||
errors,
|
||||
links,
|
||||
notifications,
|
||||
|
|
|
@ -163,3 +163,9 @@ input[type="text"].form-control.orgs-table--input {
|
|||
padding: 0 11px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Config table beneath organizations table */
|
||||
.panel .panel-body table.table.superadmin-config {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,15 @@ const addBasepath = (url, excludeBasepath) => {
|
|||
}
|
||||
|
||||
const generateResponseWithLinks = (response, newLinks) => {
|
||||
const {auth, logout, external, users, organizations, me: meLink} = newLinks
|
||||
const {
|
||||
auth,
|
||||
logout,
|
||||
external,
|
||||
users,
|
||||
organizations,
|
||||
me: meLink,
|
||||
config,
|
||||
} = newLinks
|
||||
return {
|
||||
...response,
|
||||
auth: {links: auth},
|
||||
|
@ -19,6 +27,7 @@ const generateResponseWithLinks = (response, newLinks) => {
|
|||
users,
|
||||
organizations,
|
||||
meLink,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue