Merge branch 'master' into misc-ui-polish
parent
bde0eccdad
commit
c20f3f1681
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,11 +1,26 @@
|
||||||
## v1.3.4.0 [unreleased]
|
## v1.3.4.0 [unreleased]
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
### Features
|
### Features
|
||||||
### UI Improvements
|
1. [#1645](https://github.com/influxdata/chronograf/pull/1645): Add Auth0 as a supported OAuth2 provider
|
||||||
|
|
||||||
## v1.3.3.1 [2017-06-20]
|
### UI Improvements
|
||||||
|
1. [#1644](https://github.com/influxdata/chronograf/pull/1644): Redesign Alerts History table to have sticky headers
|
||||||
|
1. [#1581](https://github.com/influxdata/chronograf/pull/1581): Refresh template variable values on dashboard page load
|
||||||
|
|
||||||
|
## v1.3.3.3 [2017-06-21]
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
1. [1651](https://github.com/influxdata/chronograf/pull/1651): Add back in x and y axes and revert some style changes on Line + Single Stat graphs
|
||||||
|
|
||||||
|
## v1.3.3.2 [2017-06-21]
|
||||||
|
### Bug Fixes
|
||||||
|
1. [1650](https://github.com/influxdata/chronograf/pull/1650): Fix broken cpu reporting on hosts page and normalize InfluxQL
|
||||||
|
|
||||||
|
## v1.3.3.1 [2017-06-21]
|
||||||
|
### Bug Fixes
|
||||||
|
1. [#1641](https://github.com/influxdata/chronograf/pull/1641): Fix enable / disable being out of sync on Kapacitor Rules Page
|
||||||
|
|
||||||
|
### Features
|
||||||
|
### UI Improvements
|
||||||
1. [#1642](https://github.com/influxdata/chronograf/pull/1642): Do not prefix basepath to external link for news feed
|
1. [#1642](https://github.com/influxdata/chronograf/pull/1642): Do not prefix basepath to external link for news feed
|
||||||
|
|
||||||
## v1.3.3.0 [2017-06-19]
|
## v1.3.3.0 [2017-06-19]
|
||||||
|
|
|
@ -171,16 +171,25 @@ func (c *Client) AllStatus(ctx context.Context) (map[string]string, error) {
|
||||||
|
|
||||||
// Status returns the status of a task in kapacitor
|
// Status returns the status of a task in kapacitor
|
||||||
func (c *Client) Status(ctx context.Context, href string) (string, error) {
|
func (c *Client) Status(ctx context.Context, href string) (string, error) {
|
||||||
kapa, err := c.kapaClient(c.URL, c.Username, c.Password)
|
s, err := c.status(ctx, href)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
task, err := kapa.Task(client.Link{Href: href}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.Status.String(), nil
|
return s.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) status(ctx context.Context, href string) (client.TaskStatus, error) {
|
||||||
|
kapa, err := c.kapaClient(c.URL, c.Username, c.Password)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
task, err := kapa.Task(client.Link{Href: href}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.Status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// All returns all tasks in kapacitor
|
// All returns all tasks in kapacitor
|
||||||
|
@ -259,6 +268,11 @@ func (c *Client) Update(ctx context.Context, href string, rule chronograf.AlertR
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevStatus, err := c.status(ctx, href)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// We need to disable the kapacitor task followed by enabling it during update.
|
// We need to disable the kapacitor task followed by enabling it during update.
|
||||||
opts := client.UpdateTaskOptions{
|
opts := client.UpdateTaskOptions{
|
||||||
TICKscript: string(script),
|
TICKscript: string(script),
|
||||||
|
@ -277,10 +291,12 @@ func (c *Client) Update(ctx context.Context, href string, rule chronograf.AlertR
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now enable the task.
|
// Now enable the task if previously enabled
|
||||||
|
if prevStatus == client.Enabled {
|
||||||
if _, err := c.Enable(ctx, href); err != nil {
|
if _, err := c.Enable(ctx, href); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Task{
|
return &Task{
|
||||||
ID: task.ID,
|
ID: task.ID,
|
||||||
|
|
|
@ -13,7 +13,12 @@ import (
|
||||||
type MockKapa struct {
|
type MockKapa struct {
|
||||||
ResTask client.Task
|
ResTask client.Task
|
||||||
ResTasks []client.Task
|
ResTasks []client.Task
|
||||||
Error error
|
TaskError error
|
||||||
|
UpdateError error
|
||||||
|
CreateError error
|
||||||
|
ListError error
|
||||||
|
DeleteError error
|
||||||
|
LastStatus client.TaskStatus
|
||||||
|
|
||||||
*client.CreateTaskOptions
|
*client.CreateTaskOptions
|
||||||
client.Link
|
client.Link
|
||||||
|
@ -24,31 +29,34 @@ type MockKapa struct {
|
||||||
|
|
||||||
func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) {
|
func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) {
|
||||||
m.CreateTaskOptions = &opt
|
m.CreateTaskOptions = &opt
|
||||||
return m.ResTask, m.Error
|
return m.ResTask, m.CreateError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKapa) Task(link client.Link, opt *client.TaskOptions) (client.Task, error) {
|
func (m *MockKapa) Task(link client.Link, opt *client.TaskOptions) (client.Task, error) {
|
||||||
m.Link = link
|
m.Link = link
|
||||||
m.TaskOptions = opt
|
m.TaskOptions = opt
|
||||||
return m.ResTask, m.Error
|
return m.ResTask, m.TaskError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKapa) ListTasks(opt *client.ListTasksOptions) ([]client.Task, error) {
|
func (m *MockKapa) ListTasks(opt *client.ListTasksOptions) ([]client.Task, error) {
|
||||||
m.ListTasksOptions = opt
|
m.ListTasksOptions = opt
|
||||||
return m.ResTasks, m.Error
|
return m.ResTasks, m.ListError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKapa) UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) {
|
func (m *MockKapa) UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) {
|
||||||
m.Link = link
|
m.Link = link
|
||||||
|
m.LastStatus = opt.Status
|
||||||
|
|
||||||
if m.UpdateTaskOptions == nil {
|
if m.UpdateTaskOptions == nil {
|
||||||
m.UpdateTaskOptions = &opt
|
m.UpdateTaskOptions = &opt
|
||||||
}
|
}
|
||||||
return m.ResTask, m.Error
|
|
||||||
|
return m.ResTask, m.UpdateError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKapa) DeleteTask(link client.Link) error {
|
func (m *MockKapa) DeleteTask(link client.Link) error {
|
||||||
m.Link = link
|
m.Link = link
|
||||||
return m.Error
|
return m.DeleteError
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockID struct {
|
type MockID struct {
|
||||||
|
@ -150,7 +158,7 @@ func TestClient_AllStatus(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
kapa.ResTask = tt.resTask
|
kapa.ResTask = tt.resTask
|
||||||
kapa.ResTasks = tt.resTasks
|
kapa.ResTasks = tt.resTasks
|
||||||
kapa.Error = tt.resError
|
kapa.ListError = tt.resError
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
|
@ -426,7 +434,7 @@ trigger
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
kapa.ResTask = tt.resTask
|
kapa.ResTask = tt.resTask
|
||||||
kapa.ResTasks = tt.resTasks
|
kapa.ResTasks = tt.resTasks
|
||||||
kapa.Error = tt.resError
|
kapa.ListError = tt.resError
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
URL: tt.fields.URL,
|
URL: tt.fields.URL,
|
||||||
|
@ -710,7 +718,7 @@ trigger
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
kapa.ResTask = tt.resTask
|
kapa.ResTask = tt.resTask
|
||||||
kapa.ResTasks = tt.resTasks
|
kapa.ResTasks = tt.resTasks
|
||||||
kapa.Error = tt.resError
|
kapa.TaskError = tt.resError
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
URL: tt.fields.URL,
|
URL: tt.fields.URL,
|
||||||
|
@ -854,7 +862,7 @@ func TestClient_updateStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
kapa.ResTask = tt.resTask
|
kapa.ResTask = tt.resTask
|
||||||
kapa.Error = tt.resError
|
kapa.UpdateError = tt.resError
|
||||||
kapa.UpdateTaskOptions = nil
|
kapa.UpdateTaskOptions = nil
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
|
@ -904,6 +912,7 @@ func TestClient_Update(t *testing.T) {
|
||||||
resError error
|
resError error
|
||||||
wantErr bool
|
wantErr bool
|
||||||
updateTaskOptions *client.UpdateTaskOptions
|
updateTaskOptions *client.UpdateTaskOptions
|
||||||
|
wantStatus client.TaskStatus
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "update alert rule error",
|
name: "update alert rule error",
|
||||||
|
@ -937,6 +946,7 @@ func TestClient_Update(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
wantStatus: client.Disabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "update alert rule",
|
name: "update alert rule",
|
||||||
|
@ -984,11 +994,60 @@ func TestClient_Update(t *testing.T) {
|
||||||
Name: "howdy",
|
Name: "howdy",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
wantStatus: client.Enabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stays disabled when already disabled",
|
||||||
|
fields: fields{
|
||||||
|
kapaClient: func(url, username, password string) (KapaClient, error) {
|
||||||
|
return kapa, nil
|
||||||
|
},
|
||||||
|
Ticker: &Alert{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
href: "/kapacitor/v1/tasks/howdy",
|
||||||
|
rule: chronograf.AlertRule{
|
||||||
|
ID: "howdy",
|
||||||
|
Query: &chronograf.QueryConfig{
|
||||||
|
Database: "db",
|
||||||
|
RetentionPolicy: "rp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resTask: client.Task{
|
||||||
|
ID: "howdy",
|
||||||
|
Status: client.Disabled,
|
||||||
|
Link: client.Link{
|
||||||
|
Href: "/kapacitor/v1/tasks/howdy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
updateTaskOptions: &client.UpdateTaskOptions{
|
||||||
|
TICKscript: "",
|
||||||
|
Type: client.StreamTask,
|
||||||
|
Status: client.Disabled,
|
||||||
|
DBRPs: []client.DBRP{
|
||||||
|
{
|
||||||
|
Database: "db",
|
||||||
|
RetentionPolicy: "rp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &Task{
|
||||||
|
ID: "howdy",
|
||||||
|
Href: "/kapacitor/v1/tasks/howdy",
|
||||||
|
HrefOutput: "/kapacitor/v1/tasks/howdy/output",
|
||||||
|
Rule: chronograf.AlertRule{
|
||||||
|
ID: "howdy",
|
||||||
|
Name: "howdy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantStatus: client.Disabled,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
kapa.ResTask = tt.resTask
|
kapa.ResTask = tt.resTask
|
||||||
kapa.Error = tt.resError
|
kapa.UpdateError = tt.resError
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
URL: tt.fields.URL,
|
URL: tt.fields.URL,
|
||||||
|
@ -1009,6 +1068,9 @@ func TestClient_Update(t *testing.T) {
|
||||||
if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) {
|
if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) {
|
||||||
t.Errorf("Client.Update() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions)
|
t.Errorf("Client.Update() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions)
|
||||||
}
|
}
|
||||||
|
if tt.wantStatus != kapa.LastStatus {
|
||||||
|
t.Errorf("Client.Update() = %v, want %v", kapa.LastStatus, tt.wantStatus)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1126,7 +1188,7 @@ func TestClient_Create(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
kapa.ResTask = tt.resTask
|
kapa.ResTask = tt.resTask
|
||||||
kapa.Error = tt.resError
|
kapa.CreateError = tt.resError
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
URL: tt.fields.URL,
|
URL: tt.fields.URL,
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/influxdata/chronograf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth0 struct {
|
||||||
|
Generic
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuth0(auth0Domain, clientID, clientSecret, redirectURL string, logger chronograf.Logger) (Auth0, error) {
|
||||||
|
domain, err := url.Parse(auth0Domain)
|
||||||
|
if err != nil {
|
||||||
|
return Auth0{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domain.Scheme = "https"
|
||||||
|
|
||||||
|
domain.Path = "/authorize"
|
||||||
|
authURL := domain.String()
|
||||||
|
|
||||||
|
domain.Path = "/oauth/token"
|
||||||
|
tokenURL := domain.String()
|
||||||
|
|
||||||
|
domain.Path = "/userinfo"
|
||||||
|
apiURL := domain.String()
|
||||||
|
|
||||||
|
return Auth0{
|
||||||
|
Generic: Generic{
|
||||||
|
PageName: "auth0",
|
||||||
|
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
|
||||||
|
RequiredScopes: []string{"openid"},
|
||||||
|
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
AuthURL: authURL,
|
||||||
|
TokenURL: tokenURL,
|
||||||
|
APIURL: apiURL,
|
||||||
|
|
||||||
|
Logger: logger,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -80,6 +80,10 @@ type Server struct {
|
||||||
|
|
||||||
StatusFeedURL string `long:"status-feed-url" description:"URL of a JSON Feed to display as a News Feed on the client Status page." default:"https://www.influxdata.com/feed/json" env:"STATUS_FEED_URL"`
|
StatusFeedURL string `long:"status-feed-url" description:"URL of a JSON Feed to display as a News Feed on the client Status page." default:"https://www.influxdata.com/feed/json" env:"STATUS_FEED_URL"`
|
||||||
|
|
||||||
|
Auth0Domain string `long:"auth0-domain" description:"Subdomain of auth0.com used for Auth0 OAuth2 authentication" env:"AUTH0_DOMAIN"`
|
||||||
|
Auth0ClientID string `long:"auth0-client-id" description:"Auth0 Client ID for OAuth2 support" env:"AUTH0_CLIENT_ID"`
|
||||||
|
Auth0ClientSecret string `long:"auth0-client-secret" description:"Auth0 Client Secret for OAuth2 support" env:"AUTH0_CLIENT_SECRET"`
|
||||||
|
|
||||||
ReportingDisabled bool `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id,uptime) once every 24hr" env:"REPORTING_DISABLED"`
|
ReportingDisabled bool `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id,uptime) once every 24hr" env:"REPORTING_DISABLED"`
|
||||||
LogLevel string `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"error" default:"info" description:"Set the logging level" env:"LOG_LEVEL"`
|
LogLevel string `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"error" default:"info" description:"Set the logging level" env:"LOG_LEVEL"`
|
||||||
Basepath string `short:"p" long:"basepath" description:"A URL path prefix under which all chronograf routes will be mounted" env:"BASE_PATH"`
|
Basepath string `short:"p" long:"basepath" description:"A URL path prefix under which all chronograf routes will be mounted" env:"BASE_PATH"`
|
||||||
|
@ -113,6 +117,10 @@ func (s *Server) UseHeroku() bool {
|
||||||
return s.TokenSecret != "" && s.HerokuClientID != "" && s.HerokuSecret != ""
|
return s.TokenSecret != "" && s.HerokuClientID != "" && s.HerokuSecret != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) UseAuth0() bool {
|
||||||
|
return s.Auth0ClientID != "" && s.Auth0ClientSecret != ""
|
||||||
|
}
|
||||||
|
|
||||||
// UseGenericOAuth2 validates the CLI parameters to enable generic oauth support
|
// UseGenericOAuth2 validates the CLI parameters to enable generic oauth support
|
||||||
func (s *Server) UseGenericOAuth2() bool {
|
func (s *Server) UseGenericOAuth2() bool {
|
||||||
return s.TokenSecret != "" && s.GenericClientID != "" &&
|
return s.TokenSecret != "" && s.GenericClientID != "" &&
|
||||||
|
@ -176,6 +184,27 @@ func (s *Server) genericOAuth(logger chronograf.Logger, auth oauth2.Authenticato
|
||||||
return &gen, genMux, s.UseGenericOAuth2
|
return &gen, genMux, s.UseGenericOAuth2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) auth0OAuth(logger chronograf.Logger, auth oauth2.Authenticator) (oauth2.Provider, oauth2.Mux, func() bool) {
|
||||||
|
redirectPath := path.Join(s.Basepath, "oauth", "auth0", "callback")
|
||||||
|
redirectURL, err := url.Parse(s.PublicURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error parsing public URL: err:", err)
|
||||||
|
return &oauth2.Auth0{}, &oauth2.AuthMux{}, func() bool { return false }
|
||||||
|
}
|
||||||
|
redirectURL.Path = redirectPath
|
||||||
|
|
||||||
|
auth0, err := oauth2.NewAuth0(s.Auth0Domain, s.Auth0ClientID, s.Auth0ClientSecret, redirectURL.String(), logger)
|
||||||
|
|
||||||
|
jwt := oauth2.NewJWT(s.TokenSecret)
|
||||||
|
genMux := oauth2.NewAuthMux(&auth0, auth, jwt, s.Basepath, logger)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Error parsing Auth0 domain: err:", err)
|
||||||
|
return &auth0, genMux, func() bool { return false }
|
||||||
|
}
|
||||||
|
return &auth0, genMux, s.UseAuth0
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) genericRedirectURL() string {
|
func (s *Server) genericRedirectURL() string {
|
||||||
if s.PublicURL == "" {
|
if s.PublicURL == "" {
|
||||||
return ""
|
return ""
|
||||||
|
@ -202,7 +231,7 @@ type BuildInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) useAuth() bool {
|
func (s *Server) useAuth() bool {
|
||||||
return s.UseGithub() || s.UseGoogle() || s.UseHeroku() || s.UseGenericOAuth2()
|
return s.UseGithub() || s.UseGoogle() || s.UseHeroku() || s.UseGenericOAuth2() || s.UseAuth0()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) useTLS() bool {
|
func (s *Server) useTLS() bool {
|
||||||
|
@ -273,6 +302,7 @@ func (s *Server) Serve(ctx context.Context) error {
|
||||||
providerFuncs = append(providerFuncs, provide(s.googleOAuth(logger, auth)))
|
providerFuncs = append(providerFuncs, provide(s.googleOAuth(logger, auth)))
|
||||||
providerFuncs = append(providerFuncs, provide(s.herokuOAuth(logger, auth)))
|
providerFuncs = append(providerFuncs, provide(s.herokuOAuth(logger, auth)))
|
||||||
providerFuncs = append(providerFuncs, provide(s.genericOAuth(logger, auth)))
|
providerFuncs = append(providerFuncs, provide(s.genericOAuth(logger, auth)))
|
||||||
|
providerFuncs = append(providerFuncs, provide(s.auth0OAuth(logger, auth)))
|
||||||
|
|
||||||
s.handler = NewMux(MuxOpts{
|
s.handler = NewMux(MuxOpts{
|
||||||
Develop: s.Develop,
|
Develop: s.Develop,
|
||||||
|
|
|
@ -2,6 +2,10 @@ import React, {Component, PropTypes} from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import {Link} from 'react-router'
|
import {Link} from 'react-router'
|
||||||
|
|
||||||
|
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||||
|
|
||||||
|
import {ALERTS_TABLE} from 'src/alerts/constants/tableSizing'
|
||||||
|
|
||||||
class AlertsTable extends Component {
|
class AlertsTable extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
@ -55,11 +59,11 @@ class AlertsTable extends Component {
|
||||||
sortableClasses(key) {
|
sortableClasses(key) {
|
||||||
if (this.state.sortKey === key) {
|
if (this.state.sortKey === key) {
|
||||||
if (this.state.sortDirection === 'asc') {
|
if (this.state.sortDirection === 'asc') {
|
||||||
return 'sortable-header sorting-ascending'
|
return 'alert-history-table--th sortable-header sorting-ascending'
|
||||||
}
|
}
|
||||||
return 'sortable-header sorting-descending'
|
return 'alert-history-table--th sortable-header sorting-descending'
|
||||||
}
|
}
|
||||||
return 'sortable-header'
|
return 'alert-history-table--th sortable-header'
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(alerts, key, direction) {
|
sort(alerts, key, direction) {
|
||||||
|
@ -80,64 +84,93 @@ class AlertsTable extends Component {
|
||||||
this.state.sortKey,
|
this.state.sortKey,
|
||||||
this.state.sortDirection
|
this.state.sortDirection
|
||||||
)
|
)
|
||||||
|
const {colName, colLevel, colTime, colHost, colValue} = ALERTS_TABLE
|
||||||
return this.props.alerts.length
|
return this.props.alerts.length
|
||||||
? <table className="table v-center table-highlight">
|
? <div className="alert-history-table">
|
||||||
<thead>
|
<div className="alert-history-table--thead">
|
||||||
<tr>
|
<div
|
||||||
<th
|
|
||||||
onClick={() => this.changeSort('name')}
|
onClick={() => this.changeSort('name')}
|
||||||
className={this.sortableClasses('name')}
|
className={this.sortableClasses('name')}
|
||||||
|
style={{width: colName}}
|
||||||
>
|
>
|
||||||
Name
|
Name
|
||||||
</th>
|
</div>
|
||||||
<th
|
<div
|
||||||
onClick={() => this.changeSort('level')}
|
onClick={() => this.changeSort('level')}
|
||||||
className={this.sortableClasses('level')}
|
className={this.sortableClasses('level')}
|
||||||
|
style={{width: colLevel}}
|
||||||
>
|
>
|
||||||
Level
|
Level
|
||||||
</th>
|
</div>
|
||||||
<th
|
<div
|
||||||
onClick={() => this.changeSort('time')}
|
onClick={() => this.changeSort('time')}
|
||||||
className={this.sortableClasses('time')}
|
className={this.sortableClasses('time')}
|
||||||
|
style={{width: colTime}}
|
||||||
>
|
>
|
||||||
Time
|
Time
|
||||||
</th>
|
</div>
|
||||||
<th
|
<div
|
||||||
onClick={() => this.changeSort('host')}
|
onClick={() => this.changeSort('host')}
|
||||||
className={this.sortableClasses('host')}
|
className={this.sortableClasses('host')}
|
||||||
|
style={{width: colHost}}
|
||||||
>
|
>
|
||||||
Host
|
Host
|
||||||
</th>
|
</div>
|
||||||
<th
|
<div
|
||||||
onClick={() => this.changeSort('value')}
|
onClick={() => this.changeSort('value')}
|
||||||
className={this.sortableClasses('value')}
|
className={this.sortableClasses('value')}
|
||||||
|
style={{width: colValue}}
|
||||||
>
|
>
|
||||||
Value
|
Value
|
||||||
</th>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
<FancyScrollbar
|
||||||
<tbody>
|
className="alert-history-table--tbody"
|
||||||
|
autoHide={false}
|
||||||
|
>
|
||||||
{alerts.map(({name, level, time, host, value}) => {
|
{alerts.map(({name, level, time, host, value}) => {
|
||||||
return (
|
return (
|
||||||
<tr key={`${name}-${level}-${time}-${host}-${value}`}>
|
<div
|
||||||
<td className="monotype">{name}</td>
|
className="alert-history-table--tr"
|
||||||
<td className={`monotype alert-level-${level.toLowerCase()}`}>
|
key={`${name}-${level}-${time}-${host}-${value}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert-history-table--td"
|
||||||
|
style={{width: colName}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`alert-history-table--td alert-level-${level.toLowerCase()}`}
|
||||||
|
style={{width: colLevel}}
|
||||||
|
>
|
||||||
{level}
|
{level}
|
||||||
</td>
|
</div>
|
||||||
<td className="monotype">
|
<div
|
||||||
|
className="alert-history-table--td"
|
||||||
|
style={{width: colTime}}
|
||||||
|
>
|
||||||
{new Date(Number(time)).toISOString()}
|
{new Date(Number(time)).toISOString()}
|
||||||
</td>
|
</div>
|
||||||
<td className="monotype">
|
<div
|
||||||
|
className="alert-history-table--td"
|
||||||
|
style={{width: colHost}}
|
||||||
|
>
|
||||||
<Link to={`/sources/${id}/hosts/${host}`}>
|
<Link to={`/sources/${id}/hosts/${host}`}>
|
||||||
{host}
|
{host}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</div>
|
||||||
<td className="monotype">{value}</td>
|
<div
|
||||||
</tr>
|
className="alert-history-table--td"
|
||||||
|
style={{width: colValue}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</FancyScrollbar>
|
||||||
</table>
|
</div>
|
||||||
: this.renderTableEmpty()
|
: this.renderTableEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +272,7 @@ class SearchBar extends Component {
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder="Filter Alerts by Name..."
|
placeholder="Filter Alerts..."
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
value={this.state.searchTerm}
|
value={this.state.searchTerm}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const ALERTS_TABLE = {
|
||||||
|
colName: '15%',
|
||||||
|
colLevel: '10%',
|
||||||
|
colTime: '25%',
|
||||||
|
colHost: '25%',
|
||||||
|
colValue: '25%',
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import SourceIndicator from 'shared/components/SourceIndicator'
|
||||||
import AlertsTable from 'src/alerts/components/AlertsTable'
|
import AlertsTable from 'src/alerts/components/AlertsTable'
|
||||||
import NoKapacitorError from 'shared/components/NoKapacitorError'
|
import NoKapacitorError from 'shared/components/NoKapacitorError'
|
||||||
import CustomTimeRangeDropdown from 'shared/components/CustomTimeRangeDropdown'
|
import CustomTimeRangeDropdown from 'shared/components/CustomTimeRangeDropdown'
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
|
||||||
|
|
||||||
import {getAlerts} from 'src/alerts/apis'
|
import {getAlerts} from 'src/alerts/apis'
|
||||||
import AJAX from 'utils/ajax'
|
import AJAX from 'utils/ajax'
|
||||||
|
@ -160,10 +159,8 @@ class AlertsApp extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return isWidget
|
return isWidget
|
||||||
? <FancyScrollbar autoHide={false}>
|
? this.renderSubComponents()
|
||||||
{this.renderSubComponents()}
|
: <div className="page alert-history-page">
|
||||||
</FancyScrollbar>
|
|
||||||
: <div className="page">
|
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<div className="page-header__container">
|
<div className="page-header__container">
|
||||||
<div className="page-header__left">
|
<div className="page-header__left">
|
||||||
|
@ -183,7 +180,7 @@ class AlertsApp extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FancyScrollbar className="page-contents">
|
<div className="page-contents">
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
|
@ -191,7 +188,7 @@ class AlertsApp extends Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FancyScrollbar>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
updateDashboardCell as updateDashboardCellAJAX,
|
updateDashboardCell as updateDashboardCellAJAX,
|
||||||
addDashboardCell as addDashboardCellAJAX,
|
addDashboardCell as addDashboardCellAJAX,
|
||||||
deleteDashboardCell as deleteDashboardCellAJAX,
|
deleteDashboardCell as deleteDashboardCellAJAX,
|
||||||
|
runTemplateVariableQuery,
|
||||||
} from 'src/dashboards/apis'
|
} from 'src/dashboards/apis'
|
||||||
|
|
||||||
import {publishAutoDismissingNotification} from 'shared/dispatchers'
|
import {publishAutoDismissingNotification} from 'shared/dispatchers'
|
||||||
|
@ -13,6 +14,10 @@ import {errorThrown} from 'shared/actions/errors'
|
||||||
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants'
|
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants'
|
||||||
|
|
||||||
import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes'
|
import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes'
|
||||||
|
import {
|
||||||
|
makeQueryForTemplate,
|
||||||
|
} from 'src/dashboards/utils/templateVariableQueryGenerator'
|
||||||
|
import parsers from 'shared/parsing'
|
||||||
|
|
||||||
export const loadDashboards = (dashboards, dashboardID) => ({
|
export const loadDashboards = (dashboards, dashboardID) => ({
|
||||||
type: 'LOAD_DASHBOARDS',
|
type: 'LOAD_DASHBOARDS',
|
||||||
|
@ -123,12 +128,26 @@ export const templateVariableSelected = (dashboardID, templateID, values) => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const editTemplateVariableValues = (
|
||||||
|
dashboardID,
|
||||||
|
templateID,
|
||||||
|
values
|
||||||
|
) => ({
|
||||||
|
type: 'EDIT_TEMPLATE_VARIABLE_VALUES',
|
||||||
|
payload: {
|
||||||
|
dashboardID,
|
||||||
|
templateID,
|
||||||
|
values,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// Async Action Creators
|
// Async Action Creators
|
||||||
|
|
||||||
export const getDashboardsAsync = () => async dispatch => {
|
export const getDashboardsAsync = () => async dispatch => {
|
||||||
try {
|
try {
|
||||||
const {data: {dashboards}} = await getDashboardsAJAX()
|
const {data: {dashboards}} = await getDashboardsAJAX()
|
||||||
dispatch(loadDashboards(dashboards))
|
dispatch(loadDashboards(dashboards))
|
||||||
|
return dashboards
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
dispatch(errorThrown(error))
|
dispatch(errorThrown(error))
|
||||||
|
@ -145,6 +164,17 @@ export const putDashboard = dashboard => async dispatch => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const putDashboardByID = dashboardID => async (dispatch, getState) => {
|
||||||
|
try {
|
||||||
|
const {dashboardUI: {dashboards}} = getState()
|
||||||
|
const dashboard = dashboards.find(d => d.id === +dashboardID)
|
||||||
|
await updateDashboardAJAX(dashboard)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
dispatch(errorThrown(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const updateDashboardCell = (dashboard, cell) => async dispatch => {
|
export const updateDashboardCell = (dashboard, cell) => async dispatch => {
|
||||||
try {
|
try {
|
||||||
const {data} = await updateDashboardCellAJAX(cell)
|
const {data} = await updateDashboardCellAJAX(cell)
|
||||||
|
@ -195,3 +225,23 @@ export const deleteDashboardCellAsync = (dashboard, cell) => async dispatch => {
|
||||||
dispatch(errorThrown(error))
|
dispatch(errorThrown(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updateTempVarValues = (source, dashboard) => async dispatch => {
|
||||||
|
try {
|
||||||
|
const tempsWithQueries = dashboard.templates.filter(t => !!t.query.influxql)
|
||||||
|
const asyncQueries = tempsWithQueries.map(({query}) =>
|
||||||
|
runTemplateVariableQuery(source, {query: makeQueryForTemplate(query)})
|
||||||
|
)
|
||||||
|
|
||||||
|
const results = await Promise.all(asyncQueries)
|
||||||
|
|
||||||
|
results.forEach(({data}, i) => {
|
||||||
|
const {type, query, id} = tempsWithQueries[i]
|
||||||
|
const vals = parsers[type](data, query.tagKey || query.measurement)[type]
|
||||||
|
dispatch(editTemplateVariableValues(dashboard.id, id, vals))
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
dispatch(errorThrown(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,10 +24,6 @@ const Dashboard = ({
|
||||||
onSelectTemplate,
|
onSelectTemplate,
|
||||||
showTemplateControlBar,
|
showTemplateControlBar,
|
||||||
}) => {
|
}) => {
|
||||||
if (!dashboard) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const cells = dashboard.cells.map(cell => {
|
const cells = dashboard.cells.map(cell => {
|
||||||
const dashboardCell = {...cell}
|
const dashboardCell = {...cell}
|
||||||
dashboardCell.queries = dashboardCell.queries.map(
|
dashboardCell.queries = dashboardCell.queries.map(
|
||||||
|
|
|
@ -10,7 +10,7 @@ const TemplateControlBar = ({
|
||||||
onSelectTemplate,
|
onSelectTemplate,
|
||||||
onOpenTemplateManager,
|
onOpenTemplateManager,
|
||||||
isOpen,
|
isOpen,
|
||||||
}) =>
|
}) => (
|
||||||
<div className={classnames('template-control-bar', {show: isOpen})}>
|
<div className={classnames('template-control-bar', {show: isOpen})}>
|
||||||
<div className="template-control--container">
|
<div className="template-control--container">
|
||||||
<div className="template-control--controls">
|
<div className="template-control--controls">
|
||||||
|
@ -53,6 +53,7 @@ const TemplateControlBar = ({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay'
|
||||||
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
||||||
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
|
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
|
||||||
import Dashboard from 'src/dashboards/components/Dashboard'
|
import Dashboard from 'src/dashboards/components/Dashboard'
|
||||||
import TemplateVariableManager from 'src/dashboards/components/TemplateVariableManager'
|
import TemplateVariableManager
|
||||||
|
from 'src/dashboards/components/TemplateVariableManager'
|
||||||
|
|
||||||
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
||||||
|
|
||||||
|
@ -57,13 +58,23 @@ class DashboardPage extends Component {
|
||||||
this.synchronizer = ::this.synchronizer
|
this.synchronizer = ::this.synchronizer
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
const {
|
const {
|
||||||
params: {dashboardID},
|
params: {dashboardID},
|
||||||
dashboardActions: {getDashboardsAsync},
|
dashboardActions: {
|
||||||
|
getDashboardsAsync,
|
||||||
|
updateTempVarValues,
|
||||||
|
putDashboardByID,
|
||||||
|
},
|
||||||
|
source,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
getDashboardsAsync(dashboardID)
|
const dashboards = await getDashboardsAsync()
|
||||||
|
const dashboard = dashboards.find(d => d.id === +dashboardID)
|
||||||
|
|
||||||
|
// Refresh and persists influxql generated template variable values
|
||||||
|
await updateTempVarValues(source, dashboard)
|
||||||
|
await putDashboardByID(dashboardID)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenTemplateManager() {
|
handleOpenTemplateManager() {
|
||||||
|
@ -272,10 +283,8 @@ class DashboardPage extends Component {
|
||||||
values: [],
|
values: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const templatesIncludingDashTime =
|
const templatesIncludingDashTime = (dashboard &&
|
||||||
(dashboard &&
|
dashboard.templates.concat(dashboardTime).concat(interval)) || []
|
||||||
dashboard.templates.concat(dashboardTime).concat(interval)) ||
|
|
||||||
[]
|
|
||||||
|
|
||||||
const {selectedCell, isEditMode, isTemplating} = this.state
|
const {selectedCell, isEditMode, isTemplating} = this.state
|
||||||
|
|
||||||
|
@ -328,13 +337,13 @@ class DashboardPage extends Component {
|
||||||
showTemplateControlBar={showTemplateControlBar}
|
showTemplateControlBar={showTemplateControlBar}
|
||||||
>
|
>
|
||||||
{dashboards
|
{dashboards
|
||||||
? dashboards.map((d, i) =>
|
? dashboards.map((d, i) => (
|
||||||
<li className="dropdown-item" key={i}>
|
<li className="dropdown-item" key={i}>
|
||||||
<Link to={`/sources/${sourceID}/dashboards/${d.id}`}>
|
<Link to={`/sources/${sourceID}/dashboards/${d.id}`}>
|
||||||
{d.name}
|
{d.name}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
)
|
))
|
||||||
: null}
|
: null}
|
||||||
</DashboardHeader>}
|
</DashboardHeader>}
|
||||||
{dashboard
|
{dashboard
|
||||||
|
|
|
@ -11,6 +11,7 @@ const initialState = {
|
||||||
}
|
}
|
||||||
|
|
||||||
import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes'
|
import {TEMPLATE_VARIABLE_SELECTED} from 'shared/constants/actionTypes'
|
||||||
|
import {TEMPLATE_VARIABLE_TYPES} from 'src/dashboards/constants'
|
||||||
|
|
||||||
export default function ui(state = initialState, action) {
|
export default function ui(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
@ -210,6 +211,34 @@ export default function ui(state = initialState, action) {
|
||||||
})
|
})
|
||||||
return {...state, dashboards: newDashboards}
|
return {...state, dashboards: newDashboards}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'EDIT_TEMPLATE_VARIABLE_VALUES': {
|
||||||
|
const {dashboardID, templateID, values} = action.payload
|
||||||
|
|
||||||
|
const dashboards = state.dashboards.map(
|
||||||
|
dashboard =>
|
||||||
|
(dashboard.id === dashboardID
|
||||||
|
? {
|
||||||
|
...dashboard,
|
||||||
|
templates: dashboard.templates.map(
|
||||||
|
template =>
|
||||||
|
(template.id === templateID
|
||||||
|
? {
|
||||||
|
...template,
|
||||||
|
values: values.map((value, i) => ({
|
||||||
|
selected: i === 0,
|
||||||
|
value,
|
||||||
|
type: TEMPLATE_VARIABLE_TYPES[template.type],
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
: template)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: dashboard)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {...state, dashboards}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -53,4 +53,10 @@ const generateTemplateVariableQuery = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const makeQueryForTemplate = ({influxql, db, measurement, tagKey}) =>
|
||||||
|
influxql
|
||||||
|
.replace(':database:', db)
|
||||||
|
.replace(':measurement:', measurement)
|
||||||
|
.replace(':tagKey:', tagKey)
|
||||||
|
|
||||||
export default generateTemplateVariableQuery
|
export default generateTemplateVariableQuery
|
||||||
|
|
|
@ -5,13 +5,13 @@ import _ from 'lodash'
|
||||||
export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
|
export function getCpuAndLoadForHosts(proxyLink, telegrafDB) {
|
||||||
return proxy({
|
return proxy({
|
||||||
source: proxyLink,
|
source: proxyLink,
|
||||||
query: `select mean(usage_user) from cpu where cpu = "cpu-total" and time > now() - 10m group by host;
|
query: `SELECT mean("usage_user") FROM cpu WHERE "cpu" = 'cpu-total' AND time > now() - 10m GROUP BY host;
|
||||||
select mean("load1") from "system" where time > now() - 10m group by host;
|
SELECT mean("load1") FROM "system" WHERE time > now() - 10m GROUP BY host;
|
||||||
select non_negative_derivative(mean(uptime)) as deltaUptime from "system" where time > now() - 10m group by host, time(1m) fill(0);
|
SELECT non_negative_derivative(mean(uptime)) AS deltaUptime FROM "system" WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
|
||||||
select mean("Percent_Processor_Time") from win_cpu where time > now() - 10m group by host;
|
SELECT mean("Percent_Processor_Time") FROM win_cpu WHERE time > now() - 10m GROUP BY host;
|
||||||
select mean("Processor_Queue_Length") from win_system where time > now() - 10s group by host;
|
SELECT mean("Processor_Queue_Length") FROM win_system WHERE time > now() - 10s GROUP BY host;
|
||||||
select non_negative_derivative(mean("System_Up_Time")) as deltaUptime from "telegraf"."autogen"."win_uptime" where time > now() - 10m group by host, time(1m) fill(0);
|
SELECT non_negative_derivative(mean("System_Up_Time")) AS deltaUptime FROM "telegraf"."autogen"."win_uptime" WHERE time > now() - 10m GROUP BY host, time(1m) fill(0);
|
||||||
show tag values from /win_system|system/ with key = "host"`,
|
SHOW TAG VALUES FROM /win_system|system/ WITH KEY = "host"`,
|
||||||
db: telegrafDB,
|
db: telegrafDB,
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
const hosts = {}
|
const hosts = {}
|
||||||
|
|
|
@ -126,25 +126,7 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
const singleStatOptions = {
|
const singleStatOptions = {
|
||||||
labels,
|
...options,
|
||||||
connectSeparatedPoints: true,
|
|
||||||
labelsKMB: true,
|
|
||||||
axes: {
|
|
||||||
x: {
|
|
||||||
drawGrid: false,
|
|
||||||
drawAxis: false,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
drawGrid: false,
|
|
||||||
drawAxis: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title,
|
|
||||||
rightGap: 0,
|
|
||||||
strokeWidth: 1.5,
|
|
||||||
drawAxesAtZero: true,
|
|
||||||
underlayCallback,
|
|
||||||
...displayOptions,
|
|
||||||
highlightSeriesOpts: {
|
highlightSeriesOpts: {
|
||||||
strokeWidth: 1.5,
|
strokeWidth: 1.5,
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,7 +50,8 @@
|
||||||
Sortable Tables
|
Sortable Tables
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
*/
|
*/
|
||||||
table.table thead th.sortable-header {
|
table.table thead th.sortable-header,
|
||||||
|
.alert-history-table--th.sortable-header {
|
||||||
transition:
|
transition:
|
||||||
color 0.25s ease,
|
color 0.25s ease,
|
||||||
background-color 0.25s ease;
|
background-color 0.25s ease;
|
||||||
|
@ -173,17 +174,25 @@ $table-tab-scrollbar-height: 6px;
|
||||||
background-color: $g4-onyx;
|
background-color: $g4-onyx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Responsive Tables
|
Alert History "Page"
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
.alert-history-page {
|
||||||
|
.page-contents > .container-fluid,
|
||||||
|
.page-contents > .container-fluid > .row,
|
||||||
|
.page-contents > .container-fluid > .row > .col-md-12,
|
||||||
|
.page-contents > .container-fluid > .row > .col-md-12 > .panel {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
.col-md-12 > .panel {
|
||||||
.table-responsive {
|
display: flex;
|
||||||
border-radius: 3px;
|
flex-direction: column;
|
||||||
border-color: $g5-pepper;
|
align-items: stretch;
|
||||||
@include custom-scrollbar($g5-pepper, $c-pool);
|
|
||||||
|
> .panel-body {flex: 1 0 0;}
|
||||||
|
.generic-empty-state {height: 100%;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,4 +208,48 @@ $table-tab-scrollbar-height: 6px;
|
||||||
.table .table--temp-var {
|
.table .table--temp-var {
|
||||||
color: $c-comet;
|
color: $c-comet;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Alert History "Table"
|
||||||
|
----------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
.alert-history-table {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.alert-history-table--thead {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 2px solid $g5-pepper;
|
||||||
|
}
|
||||||
|
.alert-history-table--th {
|
||||||
|
@include no-user-select();
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $g17-whisper;
|
||||||
|
}
|
||||||
|
.alert-history-table--tbody {
|
||||||
|
flex: 1 0 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.alert-history-table--tr {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
&:hover {
|
||||||
|
background-color: $g4-onyx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert-history-table--td {
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: $code-font;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 4px 8px;
|
||||||
|
line-height: 1.42857143em;
|
||||||
|
color: $g13-mist;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,3 +192,15 @@ br {
|
||||||
|
|
||||||
p {font-size: 13px;}
|
p {font-size: 13px;}
|
||||||
}
|
}
|
||||||
|
.alerts-widget {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
> .btn {margin: 20px 0;}
|
||||||
|
|
||||||
|
.alert-history-table {
|
||||||
|
flex: 1 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue