From 4b12179b02727f650e2167f7c2b19411382cb585 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 19 Oct 2017 22:48:31 -0500 Subject: [PATCH 01/20] Add insecure ssl support to connect to kapacitor --- chronograf.go | 15 ++++---- kapacitor/client.go | 49 +++++++++++++------------ kapacitor/client_test.go | 38 ++++++++++---------- server/kapacitors.go | 76 +++++++++++++++++++++------------------ server/kapacitors_test.go | 8 +++++ server/swagger.json | 6 ++++ 6 files changed, 109 insertions(+), 83 deletions(-) diff --git a/chronograf.go b/chronograf.go index 89ed7bbc0..5e37ea8f2 100644 --- a/chronograf.go +++ b/chronograf.go @@ -557,13 +557,14 @@ type KapacitorProperty struct { // Server represents a proxy connection to an HTTP server type Server struct { - ID int // ID is the unique ID of the server - SrcID int // SrcID of the data source - Name string // Name is the user-defined name for the server - Username string // Username is the username to connect to the server - Password string // Password is in CLEARTEXT - URL string // URL are the connections to the server - Active bool // Is this the active server for the source? + ID int // ID is the unique ID of the server + SrcID int // SrcID of the data source + Name string // Name is the user-defined name for the server + Username string // Username is the username to connect to the server + Password string // Password is in CLEARTEXT + URL string // URL are the connections to the server + InsecureSkipVerify bool // InsecureSkipVerify as true means any certificate presented by the server is accepted. + Active bool // Is this the active server for the source? } // ServersStore stores connection information for a `Server` diff --git a/kapacitor/client.go b/kapacitor/client.go index 1e7fbf9f5..da6eaa8a5 100644 --- a/kapacitor/client.go +++ b/kapacitor/client.go @@ -19,12 +19,13 @@ const ( // Client communicates to kapacitor type Client struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + URL string + Username string + Password string + InsecureSkipVerify bool + ID chronograf.ID + Ticker chronograf.Ticker + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } // KapaClient represents a connection to a kapacitor instance @@ -37,14 +38,15 @@ type KapaClient interface { } // NewClient creates a client that interfaces with Kapacitor tasks -func NewClient(url, username, password string) *Client { +func NewClient(url, username, password string, insecureSkipVerify bool) *Client { return &Client{ - URL: url, - Username: username, - Password: password, - ID: &uuid.V4{}, - Ticker: &Alert{}, - kapaClient: NewKapaClient, + URL: url, + Username: username, + Password: password, + InsecureSkipVerify: insecureSkipVerify, + ID: &uuid.V4{}, + Ticker: &Alert{}, + kapaClient: NewKapaClient, } } @@ -121,7 +123,7 @@ func (c *Client) Create(ctx context.Context, rule chronograf.AlertRule) (*Task, return nil, err } - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -189,7 +191,7 @@ func (c *Client) createFromQueryConfig(rule chronograf.AlertRule) (*client.Creat // Delete removes tickscript task from kapacitor func (c *Client) Delete(ctx context.Context, href string) error { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return err } @@ -197,7 +199,7 @@ func (c *Client) Delete(ctx context.Context, href string) error { } func (c *Client) updateStatus(ctx context.Context, href string, status client.TaskStatus) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -235,7 +237,7 @@ func (c *Client) Status(ctx context.Context, href string) (string, error) { } func (c *Client) status(ctx context.Context, href string) (client.TaskStatus, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return 0, err } @@ -249,7 +251,7 @@ func (c *Client) status(ctx context.Context, href string) (client.TaskStatus, er // All returns all tasks in kapacitor func (c *Client) All(ctx context.Context) (map[string]*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -286,7 +288,7 @@ func (c *Client) Reverse(id string, script chronograf.TICKScript) chronograf.Ale // Get returns a single alert in kapacitor func (c *Client) Get(ctx context.Context, id string) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -301,7 +303,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Task, error) { // Update changes the tickscript of a given id. func (c *Client) Update(ctx context.Context, href string, rule chronograf.AlertRule) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -386,7 +388,7 @@ func toTask(q *chronograf.QueryConfig) client.TaskType { } // NewKapaClient creates a Kapacitor client connection -func NewKapaClient(url, username, password string) (KapaClient, error) { +func NewKapaClient(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { var creds *client.Credentials if username != "" { creds = &client.Credentials{ @@ -397,8 +399,9 @@ func NewKapaClient(url, username, password string) (KapaClient, error) { } clnt, err := client.New(client.Config{ - URL: url, - Credentials: creds, + URL: url, + Credentials: creds, + InsecureSkipVerify: insecureSkipVerify, }) if err != nil { diff --git a/kapacitor/client_test.go b/kapacitor/client_test.go index 1932b1a6c..29b780542 100644 --- a/kapacitor/client_test.go +++ b/kapacitor/client_test.go @@ -75,7 +75,7 @@ func TestClient_All(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -100,7 +100,7 @@ func TestClient_All(t *testing.T) { { name: "return no tasks", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -110,7 +110,7 @@ func TestClient_All(t *testing.T) { { name: "return a non-reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -141,7 +141,7 @@ func TestClient_All(t *testing.T) { { name: "return a reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -380,7 +380,7 @@ func TestClient_Get(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -406,7 +406,7 @@ func TestClient_Get(t *testing.T) { { name: "return no task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -423,7 +423,7 @@ func TestClient_Get(t *testing.T) { { name: "return non-reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -465,7 +465,7 @@ func TestClient_Get(t *testing.T) { { name: "return reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -706,7 +706,7 @@ func TestClient_updateStatus(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -727,7 +727,7 @@ func TestClient_updateStatus(t *testing.T) { { name: "disable alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -777,7 +777,7 @@ func TestClient_updateStatus(t *testing.T) { { name: "fail to enable alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -797,7 +797,7 @@ func TestClient_updateStatus(t *testing.T) { { name: "enable alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -880,7 +880,7 @@ func TestClient_Update(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -902,7 +902,7 @@ func TestClient_Update(t *testing.T) { { name: "update alert rule error", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -936,7 +936,7 @@ func TestClient_Update(t *testing.T) { { name: "update alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -1000,7 +1000,7 @@ func TestClient_Update(t *testing.T) { { name: "stays disabled when already disabled", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -1099,7 +1099,7 @@ func TestClient_Create(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -1119,7 +1119,7 @@ func TestClient_Create(t *testing.T) { { name: "create alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -1185,7 +1185,7 @@ func TestClient_Create(t *testing.T) { { name: "create alert rule error", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, diff --git a/server/kapacitors.go b/server/kapacitors.go index e7982a8cb..fde467a74 100644 --- a/server/kapacitors.go +++ b/server/kapacitors.go @@ -12,11 +12,12 @@ import ( ) type postKapacitorRequest struct { - Name *string `json:"name"` // User facing name of kapacitor instance.; Required: true - URL *string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092);/ Required: true - Username string `json:"username,omitempty"` // Username for authentication to kapacitor - Password string `json:"password,omitempty"` - Active bool `json:"active"` + Name *string `json:"name"` // User facing name of kapacitor instance.; Required: true + URL *string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092);/ Required: true + Username string `json:"username,omitempty"` // Username for authentication to kapacitor + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. + Active bool `json:"active"` } func (p *postKapacitorRequest) Valid() error { @@ -44,13 +45,14 @@ type kapaLinks struct { } type kapacitor struct { - ID int `json:"id,string"` // Unique identifier representing a kapacitor instance. - Name string `json:"name"` // User facing name of kapacitor instance. - URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092) - Username string `json:"username,omitempty"` // Username for authentication to kapacitor - Password string `json:"password,omitempty"` - Active bool `json:"active"` - Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor + ID int `json:"id,string"` // Unique identifier representing a kapacitor instance. + Name string `json:"name"` // User facing name of kapacitor instance. + URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092) + Username string `json:"username,omitempty"` // Username for authentication to kapacitor + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. + Active bool `json:"active"` + Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor } // NewKapacitor adds valid kapacitor store store. @@ -79,12 +81,13 @@ func (h *Service) NewKapacitor(w http.ResponseWriter, r *http.Request) { } srv := chronograf.Server{ - SrcID: srcID, - Name: *req.Name, - Username: req.Username, - Password: req.Password, - URL: *req.URL, - Active: req.Active, + SrcID: srcID, + Name: *req.Name, + Username: req.Username, + Password: req.Password, + InsecureSkipVerify: req.InsecureSkipVerify, + URL: *req.URL, + Active: req.Active, } if srv, err = h.ServersStore.Add(ctx, srv); err != nil { @@ -101,11 +104,12 @@ func (h *Service) NewKapacitor(w http.ResponseWriter, r *http.Request) { func newKapacitor(srv chronograf.Server) kapacitor { httpAPISrcs := "/chronograf/v1/sources" return kapacitor{ - ID: srv.ID, - Name: srv.Name, - Username: srv.Username, - URL: srv.URL, - Active: srv.Active, + ID: srv.ID, + Name: srv.Name, + Username: srv.Username, + URL: srv.URL, + Active: srv.Active, + InsecureSkipVerify: srv.InsecureSkipVerify, Links: kapaLinks{ Self: fmt.Sprintf("%s/%d/kapacitors/%d", httpAPISrcs, srv.SrcID, srv.ID), Proxy: fmt.Sprintf("%s/%d/kapacitors/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), @@ -204,11 +208,12 @@ func (h *Service) RemoveKapacitor(w http.ResponseWriter, r *http.Request) { } type patchKapacitorRequest struct { - Name *string `json:"name,omitempty"` // User facing name of kapacitor instance. - URL *string `json:"url,omitempty"` // URL for the kapacitor - Username *string `json:"username,omitempty"` // Username for kapacitor auth - Password *string `json:"password,omitempty"` - Active *bool `json:"active"` + Name *string `json:"name,omitempty"` // User facing name of kapacitor instance. + URL *string `json:"url,omitempty"` // URL for the kapacitor + Username *string `json:"username,omitempty"` // Username for kapacitor auth + Password *string `json:"password,omitempty"` + InsecureSkipVerify *bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. + Active *bool `json:"active"` } func (p *patchKapacitorRequest) Valid() error { @@ -268,6 +273,9 @@ func (h *Service) UpdateKapacitor(w http.ResponseWriter, r *http.Request) { if req.Username != nil { srv.Username = *req.Username } + if req.InsecureSkipVerify != nil { + srv.InsecureSkipVerify = *req.InsecureSkipVerify + } if req.Active != nil { srv.Active = *req.Active } @@ -303,7 +311,7 @@ func (h *Service) KapacitorRulesPost(w http.ResponseWriter, r *http.Request) { return } - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) var req chronograf.AlertRule if err = json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -440,7 +448,7 @@ func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) { } tid := httprouter.GetParamFromContext(ctx, "tid") - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) var req chronograf.AlertRule if err = json.NewDecoder(r.Body).Decode(&req); err != nil { invalidJSON(w, h.Logger) @@ -510,7 +518,7 @@ func (h *Service) KapacitorRulesStatus(w http.ResponseWriter, r *http.Request) { } tid := httprouter.GetParamFromContext(ctx, "tid") - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) var req KapacitorStatus if err = json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -570,7 +578,7 @@ func (h *Service) KapacitorRulesGet(w http.ResponseWriter, r *http.Request) { return } - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) tasks, err := c.All(ctx) if err != nil { Error(w, http.StatusInternalServerError, err.Error(), h.Logger) @@ -613,7 +621,7 @@ func (h *Service) KapacitorRulesID(w http.ResponseWriter, r *http.Request) { } tid := httprouter.GetParamFromContext(ctx, "tid") - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) // Check if the rule exists within scope task, err := c.Get(ctx, tid) @@ -651,7 +659,7 @@ func (h *Service) KapacitorRulesDelete(w http.ResponseWriter, r *http.Request) { return } - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) tid := httprouter.GetParamFromContext(ctx, "tid") // Check if the rule is linked to this server and kapacitor diff --git a/server/kapacitors_test.go b/server/kapacitors_test.go index 2a55ea637..b6f475171 100644 --- a/server/kapacitors_test.go +++ b/server/kapacitors_test.go @@ -174,6 +174,14 @@ func Test_KapacitorRulesGet(t *testing.T) { // setup mock service and test logger testLogger := mocks.TestLogger{} svc := &server.Service{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { + return chronograf.Source{ + ID: ID, + InsecureSkipVerify: true, + }, nil + }, + }, ServersStore: &mocks.ServersStore{ GetF: func(ctx context.Context, ID int) (chronograf.Server, error) { return chronograf.Server{ diff --git a/server/swagger.json b/server/swagger.json index 11c40d906..58ebff54e 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -2357,6 +2357,7 @@ "name": "kapa", "url": "http://localhost:9092", "active": false, + "insecureSkipVerify": false, "links": { "proxy": "/chronograf/v1/sources/4/kapacitors/4/proxy", "self": "/chronograf/v1/sources/4/kapacitors/4", @@ -2387,6 +2388,11 @@ "description": "URL for the kapacitor backend (e.g. http://localhost:9092)" }, + "insecureSkipVerify": { + "type": "boolean", + "description": + "True means any certificate presented by the kapacitor is accepted. Typically used for self-signed certs. Probably should only be used for testing." + }, "active": { "type": "boolean", "description": From a53c7d7abe0d74373a00e37879402459803accc6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Oct 2017 13:39:16 -0700 Subject: [PATCH 02/20] Fix error to console when range is zero for dygraph logscale --- ui/src/shared/components/Dygraph.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 43f21725c..95caa9efa 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -43,7 +43,6 @@ export default class Dygraph extends Component { componentDidMount() { const { axes: {y, y2}, - ruleValues, isGraphFilled: fillGraph, isBarGraph, options, @@ -63,9 +62,7 @@ export default class Dygraph extends Component { plugins: [new Dygraphs.Plugins.Crosshair({direction: 'vertical'})], axes: { y: { - valueRange: options.stackedGraph - ? getStackedRange(y.bounds) - : getRange(timeSeries, y.bounds, ruleValues), + valueRange: this.getYRange(timeSeries), axisLabelFormatter: (yval, __, opts) => numberValueFormatter(yval, opts, y.prefix, y.suffix), axisLabelWidth: this.getLabelWidth(), @@ -130,7 +127,7 @@ export default class Dygraph extends Component { } componentDidUpdate() { - const {labels, axes: {y, y2}, options, ruleValues, isBarGraph} = this.props + const {labels, axes: {y, y2}, options, isBarGraph} = this.props const dygraph = this.dygraph if (!dygraph) { @@ -149,9 +146,7 @@ export default class Dygraph extends Component { ylabel: this.getLabel('y'), axes: { y: { - valueRange: options.stackedGraph - ? getStackedRange(y.bounds) - : getRange(timeSeries, y.bounds, ruleValues), + valueRange: this.getYRange(timeSeries), axisLabelFormatter: (yval, __, opts) => numberValueFormatter(yval, opts, y.prefix, y.suffix), axisLabelWidth: this.getLabelWidth(), @@ -176,6 +171,24 @@ export default class Dygraph extends Component { this.props.setResolution(w) } + getYRange = timeSeries => { + const {options, axes: {y}, ruleValues} = this.props + + if (options.stackedGraph) { + return getStackedRange(y.bounds) + } + + const range = getRange(timeSeries, y.bounds, ruleValues) + const [min, max] = range + + // Bug in Dygraph calculates a negative range for logscale when min range is 0 + if (y.scale === LOG && min <= 0) { + return [0.1, max] + } + + return range + } + handleZoom = (lower, upper) => { const {onZoom} = this.props From edaa0fed99715416c5fafc7c91309859b8de2dcf Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Oct 2017 13:39:50 -0700 Subject: [PATCH 03/20] Prevent user from deprecating number to below zero if logscale --- ui/src/dashboards/components/AxesOptions.js | 3 +++ ui/src/shared/components/ClickOutsideInput.js | 3 +++ ui/src/shared/components/Dygraph.js | 2 +- ui/src/shared/components/OptIn.js | 7 ++++--- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/src/dashboards/components/AxesOptions.js b/ui/src/dashboards/components/AxesOptions.js index d44640ea7..fcce69a05 100644 --- a/ui/src/dashboards/components/AxesOptions.js +++ b/ui/src/dashboards/components/AxesOptions.js @@ -6,6 +6,7 @@ import {Tabber, Tab} from 'src/dashboards/components/Tabber' import {DISPLAY_OPTIONS, TOOLTIP_CONTENT} from 'src/dashboards/constants' const {LINEAR, LOG, BASE_2, BASE_10} = DISPLAY_OPTIONS +const getInputMin = scale => (scale === LOG ? '0' : null) const AxesOptions = ({ axes: {y: {bounds, label, prefix, suffix, base, scale, defaultYLabel}}, @@ -38,6 +39,7 @@ const AxesOptions = ({ customValue={min} onSetValue={onSetYAxisBoundMin} type="number" + min={getInputMin(scale)} />
@@ -47,6 +49,7 @@ const AxesOptions = ({ customValue={max} onSetValue={onSetYAxisBoundMax} type="number" + min={getInputMin(scale)} />
(this.customValueInput = el) render() { - const {fixedPlaceholder, customPlaceholder, type} = this.props + const {fixedPlaceholder, customPlaceholder, type, min} = this.props const {useCustomValue, customValue} = this.state return ( @@ -110,6 +110,7 @@ class OptIn extends Component { > -
Date: Tue, 24 Oct 2017 13:49:31 -0700 Subject: [PATCH 04/20] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2615c899d..d6269b676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## v1.3.11.0 [unreleased] ### Bug Fixes +1. [#2157](https://github.com/influxdata/chronograf/pull/2157): Fix logscale producing console errors when only one point in graph ### Features ### UI Improvements From bbc6c76a61c6b5168178f2774b8de7d2ca3590ec Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Oct 2017 14:28:37 -0700 Subject: [PATCH 05/20] Fix cannot connect to source false error flag --- ui/src/dashboards/actions/index.js | 3 ++- ui/src/shared/parsing/index.js | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index b151b5e19..0108e3ab9 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -281,7 +281,8 @@ export const updateTempVarValues = (source, dashboard) => async dispatch => { results.forEach(({data}, i) => { const {type, query, id} = tempsWithQueries[i] - const vals = parsers[type](data, query.tagKey || query.measurement)[type] + const parsed = parsers[type](data, query.tagKey || query.measurement) + const vals = parsed[type] dispatch(editTemplateVariableValues(dashboard.id, id, vals)) }) } catch (error) { diff --git a/ui/src/shared/parsing/index.js b/ui/src/shared/parsing/index.js index 7ac11b03b..5ea98db42 100644 --- a/ui/src/shared/parsing/index.js +++ b/ui/src/shared/parsing/index.js @@ -1,3 +1,4 @@ +import _ from 'lodash' import databases from 'shared/parsing/showDatabases' import measurements from 'shared/parsing/showMeasurements' import fieldKeys from 'shared/parsing/showFieldKeys' @@ -8,16 +9,19 @@ const parsers = { databases, measurements: data => { const {errors, measurementSets} = measurements(data) - return {errors, measurements: measurementSets[0].measurements} + return { + errors, + measurements: _.get(measurementSets, ['0', 'measurements'], []), + } }, fieldKeys: (data, key) => { const {errors, fieldSets} = fieldKeys(data) - return {errors, fieldKeys: fieldSets[key]} + return {errors, fieldKeys: _.get(fieldSets, key, [])} }, tagKeys, tagValues: (data, key) => { const {errors, tags} = tagValues(data) - return {errors, tagValues: tags[key]} + return {errors, tagValues: _.get(tags, key, [])} }, } From b5e6eb784afce37329888090bdc53e61c0cca056 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Oct 2017 14:38:57 -0700 Subject: [PATCH 06/20] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2615c899d..a797bea68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## v1.3.11.0 [unreleased] ### Bug Fixes +1. [#2158](https://github.com/influxdata/chronograf/pull/2158): Fix 'Cannot connect to source' false error flag on Dashboard page ### Features ### UI Improvements From 933d267cdfd7c31266d7f65e0090bd3a87b07256 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 25 Oct 2017 09:48:10 -0700 Subject: [PATCH 07/20] Update database list to include retention --- ui/src/shared/components/DatabaseList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/components/DatabaseList.js b/ui/src/shared/components/DatabaseList.js index 076cbdfdc..3b20a501d 100644 --- a/ui/src/shared/components/DatabaseList.js +++ b/ui/src/shared/components/DatabaseList.js @@ -97,7 +97,7 @@ const DatabaseList = React.createClass({ return (
-
Databases
+
DB.RetentionPolicy
{sortedNamespaces.map(namespace => { From daecb50a193aff9c65d51096cec9e6290aca6a0b Mon Sep 17 00:00:00 2001 From: deniz kusefoglu Date: Thu, 26 Oct 2017 12:52:36 -0700 Subject: [PATCH 08/20] Adds fractions of seconds to time field in csv export --- ui/src/shared/parsing/resultsToCSV.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/parsing/resultsToCSV.js b/ui/src/shared/parsing/resultsToCSV.js index 8ba25b2d5..c3d3fdd02 100644 --- a/ui/src/shared/parsing/resultsToCSV.js +++ b/ui/src/shared/parsing/resultsToCSV.js @@ -2,7 +2,7 @@ import _ from 'lodash' import moment from 'moment' export const formatDate = timestamp => - moment(timestamp).format('M/D/YYYY h:mm:ss A') + moment(timestamp).format('M/D/YYYY h:mm:ss.SSSSSSSSS A') export const resultsToCSV = results => { if (!_.get(results, ['0', 'series', '0'])) { From 63a85eb9c6426bf99013ebdf6bbf4e4957cbbcf5 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 26 Oct 2017 14:07:38 -0700 Subject: [PATCH 09/20] Highlight circles are back --- ui/src/external/dygraph.js | 2 + ui/src/shared/components/Dygraph.js | 72 +++-------------------- ui/src/shared/components/DygraphLegend.js | 16 +++-- ui/src/shared/graphs/helpers.js | 59 +++++++++++++++++++ 4 files changed, 82 insertions(+), 67 deletions(-) diff --git a/ui/src/external/dygraph.js b/ui/src/external/dygraph.js index e46364b2e..597e42077 100644 --- a/ui/src/external/dygraph.js +++ b/ui/src/external/dygraph.js @@ -283,11 +283,13 @@ Dygraph.prototype.findClosestPoint = function(domX, domY) { minYDist = ydist closestRow = point.idx closestSeries = setIdx + closestPoint = point } else if (xdist === minXDist && ydist < minYDist) { minXDist = xdist minYDist = ydist closestRow = point.idx closestSeries = setIdx + closestPoint = point } } } diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 43f21725c..b227e5a78 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -29,6 +29,7 @@ export default class Dygraph extends Component { x: null, series: [], }, + pageX: null, sortType: '', filterText: '', isSynced: false, @@ -36,7 +37,6 @@ export default class Dygraph extends Component { isAscending: true, isSnipped: false, isFilterVisible: false, - legendArrowPosition: 'top', } } @@ -171,9 +171,8 @@ export default class Dygraph extends Component { dygraph.updateOptions(updateOptions) const {w} = this.dygraph.getArea() - this.resize() - this.dygraph.resize() this.props.setResolution(w) + this.resize() } handleZoom = (lower, upper) => { @@ -298,6 +297,7 @@ export default class Dygraph extends Component { resize = () => { this.dygraph.resizeElements_() this.dygraph.predraw_() + this.dygraph.resize() } formatTimeRange = timeRange => { @@ -341,64 +341,8 @@ export default class Dygraph extends Component { } } - highlightCallback = e => { - const chronografChromeSize = 60 // Width & Height of navigation page elements - - // Move the Legend on hover - const graphRect = this.graphRef.getBoundingClientRect() - const legendRect = this.legendRef.getBoundingClientRect() - - const graphWidth = graphRect.width + 32 // Factoring in padding from parent - const graphHeight = graphRect.height - const graphBottom = graphRect.bottom - const legendWidth = legendRect.width - const legendHeight = legendRect.height - const screenHeight = window.innerHeight - const legendMaxLeft = graphWidth - legendWidth / 2 - const trueGraphX = e.pageX - graphRect.left - - let legendLeft = trueGraphX - - // Enforcing max & min legend offsets - if (trueGraphX < legendWidth / 2) { - legendLeft = legendWidth / 2 - } else if (trueGraphX > legendMaxLeft) { - legendLeft = legendMaxLeft - } - - // Disallow screen overflow of legend - const isLegendBottomClipped = graphBottom + legendHeight > screenHeight - const isLegendTopClipped = - legendHeight > graphRect.top - chronografChromeSize - const willLegendFitLeft = e.pageX - chronografChromeSize > legendWidth - - let legendTop = graphHeight + 8 - this.setState({legendArrowPosition: 'top'}) - - // If legend is only clipped on the bottom, position above graph - if (isLegendBottomClipped && !isLegendTopClipped) { - this.setState({legendArrowPosition: 'bottom'}) - legendTop = -legendHeight - } - // If legend is clipped on top and bottom, posiition on either side of crosshair - if (isLegendBottomClipped && isLegendTopClipped) { - legendTop = 0 - - if (willLegendFitLeft) { - this.setState({legendArrowPosition: 'right'}) - legendLeft = trueGraphX - legendWidth / 2 - legendLeft -= 8 - } else { - this.setState({legendArrowPosition: 'left'}) - legendLeft = trueGraphX + legendWidth / 2 - legendLeft += 32 - } - } - - this.legendRef.style.left = `${legendLeft}px` - this.legendRef.style.top = `${legendTop}px` - - this.setState({isHidden: false}) + highlightCallback = ({pageX}) => { + this.setState({isHidden: false, pageX}) } legendFormatter = legend => { @@ -424,12 +368,12 @@ export default class Dygraph extends Component { render() { const { legend, + pageX, sortType, isHidden, isSnipped, filterText, isAscending, - legendArrowPosition, isFilterVisible, } = this.state @@ -437,6 +381,9 @@ export default class Dygraph extends Component {
{ diff --git a/ui/src/shared/components/DygraphLegend.js b/ui/src/shared/components/DygraphLegend.js index f0d7c79ea..6d8bf619d 100644 --- a/ui/src/shared/components/DygraphLegend.js +++ b/ui/src/shared/components/DygraphLegend.js @@ -2,6 +2,8 @@ import React, {PropTypes} from 'react' import _ from 'lodash' import classnames from 'classnames' +import {makeLegendStyles} from 'shared/graphs/helpers' + const removeMeasurement = (label = '') => { const [measurement] = label.match(/^(.*)[.]/g) || [''] return label.replace(measurement, '') @@ -9,6 +11,9 @@ const removeMeasurement = (label = '') => { const DygraphLegend = ({ xHTML, + pageX, + graph, + legend, series, onSort, onSnip, @@ -20,7 +25,6 @@ const DygraphLegend = ({ filterText, isAscending, onInputChange, - arrowPosition, isFilterVisible, onToggleFilter, }) => { @@ -28,9 +32,11 @@ const DygraphLegend = ({ series, ({y, label}) => (sortType === 'numeric' ? y : label) ) + const ordered = isAscending ? sorted : sorted.reverse() const filtered = ordered.filter(s => s.label.match(filterText)) const hidden = isHidden ? 'hidden' : '' + const style = makeLegendStyles(graph, legend, pageX) const renderSortAlpha = (
@@ -141,7 +148,9 @@ DygraphLegend.propTypes = { yHTML: string, }) ), - dygraph: shape(), + pageX: number, + legend: shape({}), + graph: shape({}), onSnip: func.isRequired, onHide: func.isRequired, onSort: func.isRequired, @@ -154,7 +163,6 @@ DygraphLegend.propTypes = { legendRef: func.isRequired, isSnipped: bool.isRequired, isFilterVisible: bool.isRequired, - arrowPosition: string.isRequired, } export default DygraphLegend diff --git a/ui/src/shared/graphs/helpers.js b/ui/src/shared/graphs/helpers.js index e210d36f5..082f7e049 100644 --- a/ui/src/shared/graphs/helpers.js +++ b/ui/src/shared/graphs/helpers.js @@ -114,6 +114,65 @@ export const barPlotter = e => { } } +export const makeLegendStyles = (graph, legend, pageX) => { + if (!graph || !legend || pageX === null) { + return {} + } + + // Move the Legend on hover + const chronografChromeSize = 60 // Width & Height of navigation page elements + const graphRect = graph.getBoundingClientRect() + const legendRect = legend.getBoundingClientRect() + + const graphWidth = graphRect.width + 32 // Factoring in padding from parent + const graphHeight = graphRect.height + const graphBottom = graphRect.bottom + const legendWidth = legendRect.width + const legendHeight = legendRect.height + const screenHeight = window.innerHeight + const legendMaxLeft = graphWidth - legendWidth / 2 + const trueGraphX = pageX - graphRect.left + + let legendLeft = trueGraphX + + // Enforcing max & min legend offsets + if (trueGraphX < legendWidth / 2) { + legendLeft = legendWidth / 2 + } else if (trueGraphX > legendMaxLeft) { + legendLeft = legendMaxLeft + } + + // Disallow screen overflow of legend + const isLegendBottomClipped = graphBottom + legendHeight > screenHeight + const isLegendTopClipped = legendHeight > graphRect.top - chronografChromeSize + const willLegendFitLeft = pageX - chronografChromeSize > legendWidth + + let legendTop = graphHeight + 8 + + // If legend is only clipped on the bottom, position above graph + if (isLegendBottomClipped && !isLegendTopClipped) { + legendTop = -legendHeight + } + + // If legend is clipped on top and bottom, posiition on either side of crosshair + if (isLegendBottomClipped && isLegendTopClipped) { + legendTop = 0 + + if (willLegendFitLeft) { + legendLeft = trueGraphX - legendWidth / 2 + legendLeft -= 8 + } else { + legendLeft = trueGraphX + legendWidth / 2 + legendLeft += 32 + } + } + + return { + left: `${legendLeft}px`, + top: `${legendTop}px`, + } +} + export const OPTIONS = { rightGap: 0, axisLineWidth: 2, From 3a0e3425d586d3fc08cf5fc6fcf7afe045e84859 Mon Sep 17 00:00:00 2001 From: deniz kusefoglu Date: Thu, 26 Oct 2017 14:35:03 -0700 Subject: [PATCH 10/20] Fix tests --- ui/spec/shared/parsing/resultsToCSVSpec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/spec/shared/parsing/resultsToCSVSpec.js b/ui/spec/shared/parsing/resultsToCSVSpec.js index 6945e713d..aa8199566 100644 --- a/ui/spec/shared/parsing/resultsToCSVSpec.js +++ b/ui/spec/shared/parsing/resultsToCSVSpec.js @@ -3,13 +3,16 @@ import { formatDate, dashboardtoCSV, } from 'shared/parsing/resultsToCSV' +import moment from 'moment' describe('formatDate', () => { it('converts timestamp to an excel compatible date string', () => { const timestamp = 1000000000000 const result = formatDate(timestamp) expect(result).to.be.a('string') - expect(+new Date(result)).to.equal(timestamp) + expect(moment(result, 'M/D/YYYY h:mm:ss.SSSSSSSSS A').valueOf()).to.equal( + timestamp + ) }) }) From 26895710abffb4d5c523dfcc25e92bbd73e0dcec Mon Sep 17 00:00:00 2001 From: deniz kusefoglu Date: Thu, 26 Oct 2017 14:37:23 -0700 Subject: [PATCH 11/20] Edit changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2615c899d..7d930ec4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## v1.3.11.0 [unreleased] ### Bug Fixes +1. [#2167](https://github.com/influxdata/chronograf/pull/2167): Add fractions of seconds to time field in csv export ### Features ### UI Improvements From 2e869f5536e4540426143882ac140473e865e993 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 26 Oct 2017 17:37:28 -0500 Subject: [PATCH 12/20] Add flush interval to kapacitor proxy to fix buffering --- server/proxy.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/proxy.go b/server/proxy.go index b00709e07..b05f08645 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "time" ) // KapacitorProxy proxies requests to kapacitor using the path query parameter. @@ -54,8 +55,12 @@ func (h *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) { req.SetBasicAuth(srv.Username, srv.Password) } } + + // Without a FlushInterval the HTTP Chunked response for kapacitor logs is + // buffered and flushed every 30 seconds. proxy := &httputil.ReverseProxy{ - Director: director, + Director: director, + FlushInterval: time.Second, } proxy.ServeHTTP(w, r) } From 9e1fe7bf29da57d85308fb5428b8e023c0f9db9e Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 26 Oct 2017 17:38:03 -0500 Subject: [PATCH 13/20] Fix kapacitor proxy to accept url query parameters --- server/proxy.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/server/proxy.go b/server/proxy.go index b05f08645..3ba5713c5 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "strings" "time" ) @@ -35,20 +36,21 @@ func (h *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) { return } - u, err := url.Parse(srv.URL) + // To preserve any HTTP query arguments to the kapacitor path, + // we concat and parse them into u. + uri := singleJoiningSlash(srv.URL, path) + u, err := url.Parse(uri) if err != nil { msg := fmt.Sprintf("Error parsing kapacitor url: %v", err) Error(w, http.StatusUnprocessableEntity, msg, h.Logger) return } - u.Path = path - director := func(req *http.Request) { // Set the Host header of the original Kapacitor URL req.Host = u.Host - req.URL = u + // Because we are acting as a proxy, kapacitor needs to have the basic auth information set as // a header directly if srv.Username != "" && srv.Password != "" { @@ -84,3 +86,15 @@ func (h *Service) KapacitorProxyGet(w http.ResponseWriter, r *http.Request) { func (h *Service) KapacitorProxyDelete(w http.ResponseWriter, r *http.Request) { h.KapacitorProxy(w, r) } + +func singleJoiningSlash(a, b string) string { + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + if aslash && bslash { + return a + b[1:] + } + if !aslash && !bslash { + return a + "/" + b + } + return a + b +} From 832589f9b92bbb458a0133eae8b889e903e77d80 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 26 Oct 2017 17:38:20 -0500 Subject: [PATCH 14/20] Fix logger and redirector to be flushers allowing HTTP chunking --- server/logger.go | 44 ++++++++++++++++++++++++---------- server/prefixing_redirector.go | 22 ++++++++++++++--- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/server/logger.go b/server/logger.go index 81465aafa..3ca5ab24f 100644 --- a/server/logger.go +++ b/server/logger.go @@ -7,39 +7,57 @@ import ( "github.com/influxdata/chronograf" ) -type logResponseWriter struct { +// statusWriterFlusher captures the status header of an http.ResponseWriter +// and is a flusher +type statusWriter struct { http.ResponseWriter - - responseCode int + Flusher http.Flusher + status int } -func (l *logResponseWriter) WriteHeader(status int) { - l.responseCode = status - l.ResponseWriter.WriteHeader(status) +func (w *statusWriter) WriteHeader(status int) { + w.status = status + w.ResponseWriter.WriteHeader(status) +} + +func (w *statusWriter) Status() int { return w.status } + +// Flush is here because the underlying HTTP chunked transfer response writer +// to implement http.Flusher. Without it data is silently buffered. This +// was discovered when proxying kapacitor chunked logs. +func (w *statusWriter) Flush() { + if w.Flusher != nil { + w.Flusher.Flush() + } } // Logger is middleware that logs the request func Logger(logger chronograf.Logger, next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { now := time.Now() - logger. - WithField("component", "server"). + logger.WithField("component", "server"). WithField("remote_addr", r.RemoteAddr). WithField("method", r.Method). WithField("url", r.URL). - Info("Request") + Debug("Request") - lrr := &logResponseWriter{w, 0} - next.ServeHTTP(lrr, r) + sw := &statusWriter{ + ResponseWriter: w, + } + if f, ok := w.(http.Flusher); ok { + sw.Flusher = f + } + next.ServeHTTP(sw, r) later := time.Now() elapsed := later.Sub(now) logger. WithField("component", "server"). WithField("remote_addr", r.RemoteAddr). + WithField("method", r.Method). WithField("response_time", elapsed.String()). - WithField("code", lrr.responseCode). - Info("Response: ", http.StatusText(lrr.responseCode)) + WithField("status", sw.Status()). + Info("Response: ", http.StatusText(sw.Status())) } return http.HandlerFunc(fn) } diff --git a/server/prefixing_redirector.go b/server/prefixing_redirector.go index 86f957efa..2c4652d87 100644 --- a/server/prefixing_redirector.go +++ b/server/prefixing_redirector.go @@ -9,7 +9,8 @@ import ( type interceptingResponseWriter struct { http.ResponseWriter - Prefix string + Flusher http.Flusher + Prefix string } func (i *interceptingResponseWriter) WriteHeader(status int) { @@ -25,11 +26,26 @@ func (i *interceptingResponseWriter) WriteHeader(status int) { i.ResponseWriter.WriteHeader(status) } -// PrefixingRedirector alters the Location header of downstream http.Handlers +// Flush is here because the underlying HTTP chunked transfer response writer +// to implement http.Flusher. Without it data is silently buffered. This +// was discovered when proxying kapacitor chunked logs. +func (i *interceptingResponseWriter) Flush() { + if i.Flusher != nil { + i.Flusher.Flush() + } +} + +// PrefixedRedirect alters the Location header of downstream http.Handlers // to include a specified prefix func PrefixedRedirect(prefix string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - iw := &interceptingResponseWriter{w, prefix} + iw := &interceptingResponseWriter{ + ResponseWriter: w, + Prefix: prefix, + } + if flusher, ok := w.(http.Flusher); ok { + iw.Flusher = flusher + } next.ServeHTTP(iw, r) }) } From f81f1c0b7a49d441715967c7c0943adc96dbf7b8 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 26 Oct 2017 19:04:23 -0500 Subject: [PATCH 15/20] Update Go and Node to 1.9.2 and 6.11.5 --- etc/Dockerfile_build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/Dockerfile_build b/etc/Dockerfile_build index 0150bfc47..af8deb426 100644 --- a/etc/Dockerfile_build +++ b/etc/Dockerfile_build @@ -18,7 +18,7 @@ RUN pip install boto requests python-jose --upgrade RUN gem install fpm # Install node -ENV NODE_VERSION v6.10.3 +ENV NODE_VERSION v6.11.5 RUN wget -q https://nodejs.org/dist/latest-v6.x/node-${NODE_VERSION}-linux-x64.tar.gz; \ tar -xvf node-${NODE_VERSION}-linux-x64.tar.gz -C / --strip-components=1; \ rm -f node-${NODE_VERSION}-linux-x64.tar.gz @@ -35,7 +35,7 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ # Install go ENV GOPATH /root/go -ENV GO_VERSION 1.8.1 +ENV GO_VERSION 1.9.2 ENV GO_ARCH amd64 RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \ tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \ From 4d183c8087e3706c4bb1feed9c29c7807fda881c Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 27 Oct 2017 10:11:09 -0500 Subject: [PATCH 16/20] Update circle builds to use go 1.9.2 and node 6.11.5 --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 28a0cff3b..597de9329 100644 --- a/circle.yml +++ b/circle.yml @@ -3,7 +3,7 @@ machine: services: - docker environment: - DOCKER_TAG: chronograf-20170516 + DOCKER_TAG: chronograf-20171027 dependencies: override: From 160b468e04c6b815b2813d4ff2b13dda62ce0351 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 27 Oct 2017 09:10:14 -0700 Subject: [PATCH 17/20] Fix memoizer warning in production build --- ui/webpack/prodConfig.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ui/webpack/prodConfig.js b/ui/webpack/prodConfig.js index 51b405526..188a4566c 100644 --- a/ui/webpack/prodConfig.js +++ b/ui/webpack/prodConfig.js @@ -1,14 +1,14 @@ /* eslint-disable no-var */ -var webpack = require('webpack'); -var path = require('path'); -var ExtractTextPlugin = require("extract-text-webpack-plugin"); -var HtmlWebpackPlugin = require("html-webpack-plugin"); -var package = require('../package.json'); -var dependencies = package.dependencies; +var webpack = require('webpack') +var path = require('path') +var ExtractTextPlugin = require('extract-text-webpack-plugin') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var package = require('../package.json') +var dependencies = package.dependencies var config = { bail: true, - devtool: 'eval', + devtool: 'eval', entry: { app: path.resolve(__dirname, '..', 'src', 'index.js'), vendor: Object.keys(dependencies), @@ -28,6 +28,15 @@ var config = { }, }, module: { + noParse: [ + path.resolve( + __dirname, + '..', + 'node_modules', + 'memoizerific', + 'memoizerific.js' + ), + ], preLoaders: [ { test: /\.js$/, From f5af9167c20865b25f17792b5b462fb07ee8b058 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 27 Oct 2017 09:11:03 -0700 Subject: [PATCH 18/20] Prettier webpack --- ui/webpack/prodConfig.js | 51 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/ui/webpack/prodConfig.js b/ui/webpack/prodConfig.js index 188a4566c..1536d8c23 100644 --- a/ui/webpack/prodConfig.js +++ b/ui/webpack/prodConfig.js @@ -51,15 +51,21 @@ var config = { }, { test: /\.scss$/, - loader: ExtractTextPlugin.extract('style-loader', 'css-loader!sass-loader!resolve-url!sass?sourceMap'), + loader: ExtractTextPlugin.extract( + 'style-loader', + 'css-loader!sass-loader!resolve-url!sass?sourceMap' + ), }, { test: /\.css$/, - loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader'), + loader: ExtractTextPlugin.extract( + 'style-loader', + 'css-loader!postcss-loader' + ), }, { - test : /\.(ico|png|cur|jpg|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, - loader : 'file', + test: /\.(ico|png|cur|jpg|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + loader: 'file', }, { test: /\.js$/, @@ -83,10 +89,10 @@ var config = { }, }), new webpack.ProvidePlugin({ - $: "jquery", - jQuery: "jquery", + $: 'jquery', + jQuery: 'jquery', }), - new ExtractTextPlugin("chronograf.css"), + new ExtractTextPlugin('chronograf.css'), new HtmlWebpackPlugin({ template: path.resolve(__dirname, '..', 'src', 'index.template.html'), inject: 'body', @@ -95,21 +101,28 @@ var config = { }), new webpack.optimize.UglifyJsPlugin({ compress: { - warnings: false - } + warnings: false, + }, }), new webpack.optimize.CommonsChunkPlugin({ names: ['vendor', 'manifest'], }), - function() { /* Webpack does not exit with non-zero status if error. */ - this.plugin("done", function(stats) { - if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf("--watch") == -1) { - console.log(stats.compilation.errors.toString({ - colors: true - })); - process.exit(1); + function() { + /* Webpack does not exit with non-zero status if error. */ + this.plugin('done', function(stats) { + if ( + stats.compilation.errors && + stats.compilation.errors.length && + process.argv.indexOf('--watch') == -1 + ) { + console.log( + stats.compilation.errors.toString({ + colors: true, + }) + ) + process.exit(1) } - }); + }) }, new webpack.DefinePlugin({ VERSION: JSON.stringify(require('../package.json').version), @@ -117,6 +130,6 @@ var config = { ], postcss: require('./postcss'), target: 'web', -}; +} -module.exports = config; +module.exports = config From c772801bde97461ed3ba29c6391d87567dcb3c60 Mon Sep 17 00:00:00 2001 From: Nicholas Drone Date: Fri, 27 Oct 2017 18:33:19 -0400 Subject: [PATCH 19/20] Fixes #1077 by removing win_system and system from the select query. --- CHANGELOG.md | 1 + ui/src/hosts/apis/index.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d930ec4b..66c97bc51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## v1.3.11.0 [unreleased] ### Bug Fixes 1. [#2167](https://github.com/influxdata/chronograf/pull/2167): Add fractions of seconds to time field in csv export +2. [#1077](https://github.com/influxdata/chronograf/pull/2087): Fix Chronograf requires users to run Telegraf's CPU and system plugins to ensure that all Apps appear on the HOST LIST page. ### Features ### UI Improvements diff --git a/ui/src/hosts/apis/index.js b/ui/src/hosts/apis/index.js index 7267496e3..39caf9fbf 100644 --- a/ui/src/hosts/apis/index.js +++ b/ui/src/hosts/apis/index.js @@ -11,7 +11,7 @@ export function getCpuAndLoadForHosts(proxyLink, telegrafDB) { 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 non_negative_derivative(mean("System_Up_Time")) AS winDeltaUptime FROM win_system WHERE time > now() - 10m GROUP BY host, time(1m) fill(0); - SHOW TAG VALUES FROM /win_system|system/ WITH KEY = "host"`, + SHOW TAG VALUES WITH KEY = "host";`, db: telegrafDB, }).then(resp => { const hosts = {} @@ -87,7 +87,7 @@ export async function getAllHosts(proxyLink, telegrafDB) { try { const resp = await proxy({ source: proxyLink, - query: 'show tag values from /win_system|system/ with key = "host"', + query: 'show tag values with key = "host"', db: telegrafDB, }) const hosts = {} From 735cef1fd4d869b3a9102f3b18ef9ac462127d28 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Mon, 30 Oct 2017 19:27:02 -0500 Subject: [PATCH 20/20] Update chronograf logrotate policy to not rotate if file is 0 bytes --- etc/scripts/logrotate | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/scripts/logrotate b/etc/scripts/logrotate index 39ebe4088..f172c0dff 100644 --- a/etc/scripts/logrotate +++ b/etc/scripts/logrotate @@ -5,4 +5,5 @@ dateext copytruncate compress + notifempty }