181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/influxdata/influxdb/v2/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
|
|
}
|