Add API to get/update log viewer UI config

Co-authored-by: Jared Scheib <jared.scheib@gmail.com>
pull/3806/head
Jared Scheib 2018-06-29 18:44:57 -07:00
parent acfa7b311c
commit 4125399054
9 changed files with 835 additions and 959 deletions

View File

@ -36,6 +36,111 @@ func (s *ConfigStore) Initialize(ctx context.Context) error {
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "time",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "severity",
Position: 1,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
{
Type: "label",
Value: "text",
},
},
},
{
Name: "timestamp",
Position: 2,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Proc ID",
},
},
},
{
Name: "appname",
Position: 6,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
}
return s.Update(ctx, &cfg)
}

View File

@ -24,6 +24,111 @@ func TestConfig_Get(t *testing.T) {
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "time",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "hidden",
},
},
},
{
Name: "severity",
Position: 1,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "icon",
},
{
Type: "label",
Value: "text",
},
},
},
{
Name: "timestamp",
Position: 2,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Proc ID",
},
},
},
{
Name: "appname",
Position: 6,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
},
},
@ -67,6 +172,107 @@ func TestConfig_Update(t *testing.T) {
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "time",
Position: 1,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "severity",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "text",
},
},
},
{
Name: "timestamp",
Position: 2,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Milkshake",
},
},
},
{
Name: "appname",
Position: 6,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
},
wants: wants{
@ -74,6 +280,107 @@ func TestConfig_Update(t *testing.T) {
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "time",
Position: 1,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "severity",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "label",
Value: "text",
},
},
},
{
Name: "timestamp",
Position: 2,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "message",
Position: 3,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "facility",
Position: 4,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
{
Name: "procid",
Position: 5,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Milkshake",
},
},
},
{
Name: "appname",
Position: 6,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
{
Type: "displayName",
Value: "Application",
},
},
},
{
Name: "host",
Position: 7,
Encoding: []chronograf.ColumnEncoding{
{
Type: "visibility",
Value: "visible",
},
},
},
},
},
},
},
},

View File

@ -716,10 +716,31 @@ func UnmarshalOrganizationPB(data []byte, o *Organization) error {
// MarshalConfig encodes a config to binary protobuf format.
func MarshalConfig(c *chronograf.Config) ([]byte, error) {
columns := make([]*LogViewerUIColumn, len(c.LogViewerUI.Columns))
for i, column := range c.LogViewerUI.Columns {
encodings := make([]*ColumnEncoding, len(column.Encoding))
for j, e := range column.Encoding {
encodings[j] = &ColumnEncoding{
Type: e.Type,
Value: e.Value,
Name: e.Name,
}
}
columns[i] = &LogViewerUIColumn{
Name: column.Name,
Position: column.Position,
Encoding: encodings,
}
}
return MarshalConfigPB(&Config{
Auth: &AuthConfig{
SuperAdminNewUsers: c.Auth.SuperAdminNewUsers,
},
LogViewerUI: &LogViewerUIConfig{
Columns: columns,
},
})
}
@ -739,6 +760,28 @@ func UnmarshalConfig(data []byte, c *chronograf.Config) error {
}
c.Auth.SuperAdminNewUsers = pb.Auth.SuperAdminNewUsers
if pb.LogViewerUI == nil {
return fmt.Errorf("Log Viewer UI config is nil")
}
columns := make([]chronograf.LogViewerUIColumn, len(pb.LogViewerUI.Columns))
for i, c := range pb.LogViewerUI.Columns {
columns[i].Name = c.Name
columns[i].Position = c.Position
encodings := make([]chronograf.ColumnEncoding, len(c.Encoding))
for j, e := range c.Encoding {
encodings[j].Type = e.Type
encodings[j].Value = e.Value
encodings[j].Name = e.Name
}
columns[i].Encoding = encodings
}
c.LogViewerUI.Columns = columns
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -207,13 +207,30 @@ message Organization {
}
message Config {
AuthConfig Auth = 1; // Auth is the configuration for options that auth related
AuthConfig Auth = 1; // Auth is the configuration for options that auth related
LogViewerUIConfig LogViewerUI = 2; // LogViewerUI is the configuration for the Log Viewer UI
}
message AuthConfig {
bool SuperAdminNewUsers = 1; // SuperAdminNewUsers configuration option that specifies which users will auto become super admin
}
message LogViewerUIConfig {
repeated LogViewerUIColumn Columns = 1; // Columns is the array of columns in the log viewer UI
}
message LogViewerUIColumn {
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 Encoding = 3; // Encoding 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

View File

@ -736,20 +736,35 @@ 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"`
Auth AuthConfig `json:"auth"`
LogViewerUI LogViewerUIConfig `json:"logViewerUI"`
}
// AuthConfig is the global application config section for auth parameters
type AuthConfig struct {
SuperAdminNewUsers bool `json:"superAdminNewUsers"`
}
// LogViewerUIConfig is the config sections for log viewer section of the application
type LogViewerUIConfig struct {
Columns []LogViewerUIColumn `json:"columns"`
}
// LogViewerUIColumn is a specific column of the log viewer UI
type LogViewerUIColumn struct {
Name string `json:"name"`
Position int32 `json:"position"`
Encoding []ColumnEncoding `json:"encoding"`
}
// 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"`
}
// ConfigStore is the storage and retrieval of global application Config

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"path"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf"
@ -37,6 +38,20 @@ func newAuthConfigResponse(config chronograf.Config) *authConfigResponse {
}
}
type logViewerUIResponse struct {
Links selfLinks `json:"links"`
chronograf.LogViewerUIConfig
}
func newLogViewerUIConfigResponse(config chronograf.Config) *logViewerUIResponse {
return &logViewerUIResponse{
Links: selfLinks{
Self: "/chronograf/v1/config/logViewer",
},
LogViewerUIConfig: config.LogViewerUI,
}
}
// Config retrieves the global application configuration
func (s *Service) Config(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -70,11 +85,14 @@ func (s *Service) ConfigSection(w http.ResponseWriter, r *http.Request) {
return
}
section := httprouter.GetParamFromContext(ctx, "section")
_, section := path.Split(r.URL.String())
var res interface{}
switch section {
case "auth":
res = newAuthConfigResponse(*config)
case "logViewer":
res = newLogViewerUIConfigResponse(*config)
default:
Error(w, http.StatusBadRequest, fmt.Sprintf("received unknown section %q", section), s.Logger)
return
@ -109,6 +127,14 @@ func (s *Service) ReplaceConfigSection(w http.ResponseWriter, r *http.Request) {
}
config.Auth = authConfig
res = newAuthConfigResponse(*config)
case "logViewer":
var logViewerUIConfig chronograf.LogViewerUIConfig
if err := json.NewDecoder(r.Body).Decode(&logViewerUIConfig); err != nil {
invalidJSON(w, s.Logger)
return
}
config.LogViewerUI = logViewerUIConfig
res = newLogViewerUIConfigResponse(*config)
default:
Error(w, http.StatusBadRequest, fmt.Sprintf("received unknown section %q", section), s.Logger)
return

View File

@ -36,13 +36,37 @@ func TestConfig(t *testing.T) {
Auth: chronograf.AuthConfig{
SuperAdminNewUsers: false,
},
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "severity",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "emergency",
Name: "ruby",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "displayName",
Value: "Log Severity",
},
},
},
},
},
},
},
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"auth": {"superAdminNewUsers": false}, "links": {"self": "/chronograf/v1/config"}}`,
body: `{"links":{"self":"/chronograf/v1/config"},"auth":{"superAdminNewUsers":false},"logViewerUI":{"columns":[{"name":"severity","position":0,"encoding":[{"type":"color","value":"emergency","name":"ruby"},{"type":"color","value":"info","name":"rainforest"},{"type":"displayName","value":"Log Severity"}]}]}}`,
},
},
}
@ -117,6 +141,47 @@ func TestConfigSection(t *testing.T) {
body: `{"superAdminNewUsers": false, "links": {"self": "/chronograf/v1/config/auth"}}`,
},
},
{
name: "Get log viewer configuration",
fields: fields{
ConfigStore: &mocks.ConfigStore{
Config: &chronograf.Config{
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "severity",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "emergency",
Name: "ruby",
},
{
Type: "color",
Value: "info",
Name: "rainforest",
},
{
Type: "displayName",
Value: "Log Severity",
},
},
},
},
},
},
},
},
args: args{
section: "logViewer",
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"links":{"self":"/chronograf/v1/config/logViewer"},"columns":[{"name":"severity","position":0,"encoding":[{"type":"color","value":"emergency","name":"ruby"},{"type":"color","value":"info","name":"rainforest"},{"type":"displayName","value":"Log Severity"}]}]}`,
},
},
{
name: "Get unknown configuration",
fields: fields{
@ -221,6 +286,68 @@ func TestReplaceConfigSection(t *testing.T) {
body: `{"superAdminNewUsers": true, "links": {"self": "/chronograf/v1/config/auth"}}`,
},
},
{
name: "Set log viewer configuration",
fields: fields{
ConfigStore: &mocks.ConfigStore{
Config: &chronograf.Config{
LogViewerUI: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "severity",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "rainforest",
},
},
},
},
},
},
},
},
args: args{
section: "logViewer",
payload: chronograf.LogViewerUIConfig{
Columns: []chronograf.LogViewerUIColumn{
{
Name: "severity",
Position: 1,
Encoding: []chronograf.ColumnEncoding{
{
Type: "color",
Value: "info",
Name: "pineapple",
},
{
Type: "color",
Value: "emergency",
Name: "ruby",
},
},
},
{
Name: "messages",
Position: 0,
Encoding: []chronograf.ColumnEncoding{
{
Type: "displayName",
Value: "Log Messages",
},
},
},
},
},
},
wants: wants{
statusCode: 200,
contentType: "application/json",
body: `{"links":{"self":"/chronograf/v1/config/logViewer"},"columns":[{"name":"severity","position":1,"encoding":[{"type":"color","value":"info","name":"pineapple"},{"type":"color","value":"emergency","name":"ruby"}]},{"name":"messages","position":0,"encoding":[{"type":"displayName","value":"Log Messages"}]}]}`,
},
},
{
name: "Set unknown configuration",
fields: fields{

View File

@ -313,8 +313,10 @@ 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/logViewer", EnsureViewer(service.ConfigSection))
router.PUT("/chronograf/v1/config/logViewer", EnsureEditor(service.ReplaceConfigSection))
router.GET("/chronograf/v1/config/auth", EnsureSuperAdmin(service.ConfigSection))
router.PUT("/chronograf/v1/config/auth", EnsureSuperAdmin(service.ReplaceConfigSection))
router.GET("/chronograf/v1/env", EnsureViewer(service.Environment))