From 06af77dffb5563f37340b698a6dacd78fe7db872 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 10 Feb 2017 11:12:59 -0800 Subject: [PATCH 1/7] Change rule status --- ui/spec/kapacitor/reducers/rulesSpec.js | 17 +++++++ ui/src/kapacitor/actions/view/index.js | 29 +++++++++++- ui/src/kapacitor/apis/index.js | 8 ++++ .../containers/KapacitorRulesPage.js | 47 +++++++++++++------ ui/src/kapacitor/reducers/rules.js | 8 ++++ 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/ui/spec/kapacitor/reducers/rulesSpec.js b/ui/spec/kapacitor/reducers/rulesSpec.js index c2cfd9f8e..780191327 100644 --- a/ui/spec/kapacitor/reducers/rulesSpec.js +++ b/ui/spec/kapacitor/reducers/rulesSpec.js @@ -9,6 +9,7 @@ import { updateAlerts, updateRuleName, deleteRuleSuccess, + updateRuleStatusSuccess, } from 'src/kapacitor/actions/view'; describe('Kapacitor.Reducers.rules', () => { @@ -134,4 +135,20 @@ describe('Kapacitor.Reducers.rules', () => { const newState = reducer(initialState, updateDetails(ruleID, details)); expect(newState[ruleID].details).to.equal(details); }); + + it('can update status', () => { + const ruleID = 1; + const status = 'enabled'; + + const initialState = { + [ruleID]: { + id: ruleID, + queryID: 988, + status: 'disabled', + } + }; + + const newState = reducer(initialState, updateRuleStatusSuccess(ruleID, status)); + expect(newState[ruleID].status).to.equal(status); + }); }); diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js index 5fbf3f898..aabb10aaf 100644 --- a/ui/src/kapacitor/actions/view/index.js +++ b/ui/src/kapacitor/actions/view/index.js @@ -1,7 +1,12 @@ import uuid from 'node-uuid'; -import {getRules, getRule, deleteRule as deleteRuleAPI} from 'src/kapacitor/apis'; import {getKapacitor} from 'src/shared/apis'; import {publishNotification} from 'src/shared/actions/notifications'; +import { + getRules, + getRule, + deleteRule as deleteRuleAPI, + updateRuleStatus as updateRuleStatusAPI, +} from 'src/kapacitor/apis'; export function fetchRule(source, ruleID) { return (dispatch) => { @@ -126,6 +131,16 @@ export function deleteRuleSuccess(ruleID) { }; } +export function updateRuleStatusSuccess(ruleID, status) { + return { + type: 'UPDATE_RULE_STATUS_SUCCESS', + payload: { + ruleID, + status, + }, + }; +} + export function deleteRule(rule) { return (dispatch) => { deleteRuleAPI(rule).then(() => { @@ -136,3 +151,15 @@ export function deleteRule(rule) { }); }; } + +export function updateRuleStatus(rule, {status}) { + return (dispatch) => { + updateRuleStatusAPI(rule, status).then(() => { + dispatch(updateRuleStatusSuccess(rule.id, status)); + dispatch(publishNotification('success', `${rule.name} ${status} successfully`)); + }).catch(() => { + dispatch(updateRuleStatusSuccess(rule.id, status)); + dispatch(publishNotification('error', `${rule.name} could not be ${status}`)); + }); + }; +} diff --git a/ui/src/kapacitor/apis/index.js b/ui/src/kapacitor/apis/index.js index 8a7c80355..973f0b7f5 100644 --- a/ui/src/kapacitor/apis/index.js +++ b/ui/src/kapacitor/apis/index.js @@ -47,3 +47,11 @@ export function deleteRule(rule) { url: rule.links.self, }); } + +export function updateRuleStatus(rule, status) { + return AJAX({ + method: 'PATCH', + url: rule.links.self, + data: {status}, + }); +} diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.js b/ui/src/kapacitor/containers/KapacitorRulesPage.js index 293b5157c..eedac93a5 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.js +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.js @@ -6,27 +6,35 @@ import {getKapacitor} from 'src/shared/apis'; import * as kapacitorActionCreators from '../actions/view'; import NoKapacitorError from '../../shared/components/NoKapacitorError'; +const { + arrayOf, + func, + shape, + string, +} = PropTypes; + export const KapacitorRulesPage = React.createClass({ propTypes: { - source: PropTypes.shape({ - id: PropTypes.string.isRequired, - links: PropTypes.shape({ - proxy: PropTypes.string.isRequired, - self: PropTypes.string.isRequired, - kapacitors: PropTypes.string.isRequired, + source: shape({ + id: string.isRequired, + links: shape({ + proxy: string.isRequired, + self: string.isRequired, + kapacitors: string.isRequired, }), }), - rules: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string.isRequired, - trigger: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - alerts: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, + rules: arrayOf(shape({ + name: string.isRequired, + trigger: string.isRequired, + message: string.isRequired, + alerts: arrayOf(string.isRequired).isRequired, })).isRequired, - actions: PropTypes.shape({ - fetchRules: PropTypes.func.isRequired, - deleteRule: PropTypes.func.isRequired, + actions: shape({ + fetchRules: func.isRequired, + deleteRule: func.isRequired, + updateRuleStatus: func.isRequired, }).isRequired, - addFlashMessage: PropTypes.func, + addFlashMessage: func, }, getInitialState() { @@ -50,6 +58,11 @@ export const KapacitorRulesPage = React.createClass({ actions.deleteRule(rule); }, + handleRuleStatus(e, rule) { + const status = e.target.checked ? 'enabled' : 'disabled'; + this.props.actions.updateRuleStatus(rule, {status}); + }, + renderSubComponent() { const {source} = this.props; const {hasKapacitor, loading} = this.state; @@ -72,6 +85,7 @@ export const KapacitorRulesPage = React.createClass({ Trigger Message Alerts + Enabled @@ -129,6 +143,9 @@ export const KapacitorRulesPage = React.createClass({ {rule.trigger} {rule.message} {rule.alerts.join(', ')} + + this.enabled = r} checked={rule.status === "enabled"} onClick={(e) => this.handleRuleStatus(e, rule)} /> + ); diff --git a/ui/src/kapacitor/reducers/rules.js b/ui/src/kapacitor/reducers/rules.js index 68fb345aa..1700b9541 100644 --- a/ui/src/kapacitor/reducers/rules.js +++ b/ui/src/kapacitor/reducers/rules.js @@ -93,6 +93,14 @@ export default function rules(state = {}, action) { [ruleID]: {...state[ruleID], details}, }}; } + + case 'UPDATE_RULE_STATUS_SUCCESS': { + const {ruleID, status} = action.payload; + + return {...state, ...{ + [ruleID]: {...state[ruleID], status}, + }}; + } } return state; } From 95e38a4767cbdf042f6c4d7604c7db5c93b4fb34 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 10 Feb 2017 11:12:59 -0800 Subject: [PATCH 2/7] Change rule status --- ui/spec/kapacitor/reducers/rulesSpec.js | 17 +++++++ ui/src/kapacitor/actions/view/index.js | 29 +++++++++++- ui/src/kapacitor/apis/index.js | 8 ++++ .../containers/KapacitorRulesPage.js | 47 +++++++++++++------ ui/src/kapacitor/reducers/rules.js | 8 ++++ 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/ui/spec/kapacitor/reducers/rulesSpec.js b/ui/spec/kapacitor/reducers/rulesSpec.js index c2cfd9f8e..780191327 100644 --- a/ui/spec/kapacitor/reducers/rulesSpec.js +++ b/ui/spec/kapacitor/reducers/rulesSpec.js @@ -9,6 +9,7 @@ import { updateAlerts, updateRuleName, deleteRuleSuccess, + updateRuleStatusSuccess, } from 'src/kapacitor/actions/view'; describe('Kapacitor.Reducers.rules', () => { @@ -134,4 +135,20 @@ describe('Kapacitor.Reducers.rules', () => { const newState = reducer(initialState, updateDetails(ruleID, details)); expect(newState[ruleID].details).to.equal(details); }); + + it('can update status', () => { + const ruleID = 1; + const status = 'enabled'; + + const initialState = { + [ruleID]: { + id: ruleID, + queryID: 988, + status: 'disabled', + } + }; + + const newState = reducer(initialState, updateRuleStatusSuccess(ruleID, status)); + expect(newState[ruleID].status).to.equal(status); + }); }); diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js index 5fbf3f898..aabb10aaf 100644 --- a/ui/src/kapacitor/actions/view/index.js +++ b/ui/src/kapacitor/actions/view/index.js @@ -1,7 +1,12 @@ import uuid from 'node-uuid'; -import {getRules, getRule, deleteRule as deleteRuleAPI} from 'src/kapacitor/apis'; import {getKapacitor} from 'src/shared/apis'; import {publishNotification} from 'src/shared/actions/notifications'; +import { + getRules, + getRule, + deleteRule as deleteRuleAPI, + updateRuleStatus as updateRuleStatusAPI, +} from 'src/kapacitor/apis'; export function fetchRule(source, ruleID) { return (dispatch) => { @@ -126,6 +131,16 @@ export function deleteRuleSuccess(ruleID) { }; } +export function updateRuleStatusSuccess(ruleID, status) { + return { + type: 'UPDATE_RULE_STATUS_SUCCESS', + payload: { + ruleID, + status, + }, + }; +} + export function deleteRule(rule) { return (dispatch) => { deleteRuleAPI(rule).then(() => { @@ -136,3 +151,15 @@ export function deleteRule(rule) { }); }; } + +export function updateRuleStatus(rule, {status}) { + return (dispatch) => { + updateRuleStatusAPI(rule, status).then(() => { + dispatch(updateRuleStatusSuccess(rule.id, status)); + dispatch(publishNotification('success', `${rule.name} ${status} successfully`)); + }).catch(() => { + dispatch(updateRuleStatusSuccess(rule.id, status)); + dispatch(publishNotification('error', `${rule.name} could not be ${status}`)); + }); + }; +} diff --git a/ui/src/kapacitor/apis/index.js b/ui/src/kapacitor/apis/index.js index 8a7c80355..973f0b7f5 100644 --- a/ui/src/kapacitor/apis/index.js +++ b/ui/src/kapacitor/apis/index.js @@ -47,3 +47,11 @@ export function deleteRule(rule) { url: rule.links.self, }); } + +export function updateRuleStatus(rule, status) { + return AJAX({ + method: 'PATCH', + url: rule.links.self, + data: {status}, + }); +} diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.js b/ui/src/kapacitor/containers/KapacitorRulesPage.js index 293b5157c..eedac93a5 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.js +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.js @@ -6,27 +6,35 @@ import {getKapacitor} from 'src/shared/apis'; import * as kapacitorActionCreators from '../actions/view'; import NoKapacitorError from '../../shared/components/NoKapacitorError'; +const { + arrayOf, + func, + shape, + string, +} = PropTypes; + export const KapacitorRulesPage = React.createClass({ propTypes: { - source: PropTypes.shape({ - id: PropTypes.string.isRequired, - links: PropTypes.shape({ - proxy: PropTypes.string.isRequired, - self: PropTypes.string.isRequired, - kapacitors: PropTypes.string.isRequired, + source: shape({ + id: string.isRequired, + links: shape({ + proxy: string.isRequired, + self: string.isRequired, + kapacitors: string.isRequired, }), }), - rules: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string.isRequired, - trigger: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - alerts: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, + rules: arrayOf(shape({ + name: string.isRequired, + trigger: string.isRequired, + message: string.isRequired, + alerts: arrayOf(string.isRequired).isRequired, })).isRequired, - actions: PropTypes.shape({ - fetchRules: PropTypes.func.isRequired, - deleteRule: PropTypes.func.isRequired, + actions: shape({ + fetchRules: func.isRequired, + deleteRule: func.isRequired, + updateRuleStatus: func.isRequired, }).isRequired, - addFlashMessage: PropTypes.func, + addFlashMessage: func, }, getInitialState() { @@ -50,6 +58,11 @@ export const KapacitorRulesPage = React.createClass({ actions.deleteRule(rule); }, + handleRuleStatus(e, rule) { + const status = e.target.checked ? 'enabled' : 'disabled'; + this.props.actions.updateRuleStatus(rule, {status}); + }, + renderSubComponent() { const {source} = this.props; const {hasKapacitor, loading} = this.state; @@ -72,6 +85,7 @@ export const KapacitorRulesPage = React.createClass({ Trigger Message Alerts + Enabled @@ -129,6 +143,9 @@ export const KapacitorRulesPage = React.createClass({ {rule.trigger} {rule.message} {rule.alerts.join(', ')} + + this.enabled = r} checked={rule.status === "enabled"} onClick={(e) => this.handleRuleStatus(e, rule)} /> + ); diff --git a/ui/src/kapacitor/reducers/rules.js b/ui/src/kapacitor/reducers/rules.js index 68fb345aa..1700b9541 100644 --- a/ui/src/kapacitor/reducers/rules.js +++ b/ui/src/kapacitor/reducers/rules.js @@ -93,6 +93,14 @@ export default function rules(state = {}, action) { [ruleID]: {...state[ruleID], details}, }}; } + + case 'UPDATE_RULE_STATUS_SUCCESS': { + const {ruleID, status} = action.payload; + + return {...state, ...{ + [ruleID]: {...state[ruleID], status}, + }}; + } } return state; } From 1d9bb704148369ec96d17e9672d1f1e4be1c6e1c Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 10 Feb 2017 13:48:42 -0600 Subject: [PATCH 3/7] Add disable and enable to kapacitor alerts --- CHANGELOG.md | 1 + kapacitor/client.go | 38 ++++++++++++++ server/kapacitors.go | 121 ++++++++++++++++++++++++++++++++++++++++++- server/mux.go | 9 ++-- server/swagger.json | 8 +++ 5 files changed, 172 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e304e63..e45169ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ 1. [#838](https://github.com/influxdata/chronograf/issues/838): Add detail node to kapacitor alerts 2. [#853](https://github.com/influxdata/chronograf/issues/853): Updated builds to use yarn over npm install 3. [#860](https://github.com/influxdata/chronograf/issues/860): Add gzip encoding and caching of static assets to server + 4. [#847](https://github.com/influxdata/chronograf/issues/847): Enable and disable kapacitor alerts from alert manager ### Upcoming UI Improvements diff --git a/kapacitor/client.go b/kapacitor/client.go index f9edd7b59..d37a26e50 100644 --- a/kapacitor/client.go +++ b/kapacitor/client.go @@ -119,6 +119,44 @@ func (c *Client) Enable(ctx context.Context, href string) (*Task, error) { return c.updateStatus(ctx, href, client.Enabled) } +// AllStatus returns the status of all tasks in kapacitor +func (c *Client) AllStatus(ctx context.Context) (map[string]string, error) { + kapa, err := c.kapaClient(ctx) + if err != nil { + return nil, err + } + + // Only get the status, id and link section back + opts := &client.ListTasksOptions{ + Fields: []string{"status"}, + } + tasks, err := kapa.ListTasks(opts) + if err != nil { + return nil, err + } + + taskStatuses := map[string]string{} + for _, task := range tasks { + taskStatuses[task.ID] = task.Status.String() + } + + return taskStatuses, nil +} + +// Status returns the status of a task in kapacitor +func (c *Client) Status(ctx context.Context, href string) (string, error) { + kapa, err := c.kapaClient(ctx) + if err != nil { + return "", err + } + task, err := kapa.Task(client.Link{Href: href}, nil) + if err != nil { + return "", err + } + + return task.Status.String(), nil +} + // 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(ctx) diff --git a/server/kapacitors.go b/server/kapacitors.go index ff8bc59d2..798a8c08b 100644 --- a/server/kapacitors.go +++ b/server/kapacitors.go @@ -349,6 +349,7 @@ func (h *Service) KapacitorRulesPost(w http.ResponseWriter, r *http.Request) { Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.HrefOutput)), }, TICKScript: string(task.TICKScript), + Status: "enabled", } w.Header().Add("Location", res.Links.Self) @@ -364,6 +365,7 @@ type alertLinks struct { type alertResponse struct { chronograf.AlertRule TICKScript string `json:"tickscript"` + Status string `json:"status"` Links alertLinks `json:"links"` } @@ -438,6 +440,92 @@ func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) { Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.HrefOutput)), }, TICKScript: string(task.TICKScript), + Status: "enabled", + } + encodeJSON(w, http.StatusOK, res, h.Logger) +} + +type KapacitorStatus struct { + Status string `json:"status"` +} + +func (k *KapacitorStatus) Valid() error { + if k.Status == "enabled" || k.Status == "disabled" { + return nil + } + return fmt.Errorf("Invalid Kapacitor status: %s", k.Status) +} + +// KapacitorRulesStatus proxies PATCH to kapacitor to enable/disable tasks +func (h *Service) KapacitorRulesStatus(w http.ResponseWriter, r *http.Request) { + id, err := paramID("kid", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) + return + } + + srcID, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) + return + } + + ctx := r.Context() + srv, err := h.ServersStore.Get(ctx, id) + if err != nil || srv.SrcID != srcID { + notFound(w, id, h.Logger) + return + } + + tid := httprouter.GetParamFromContext(ctx, "tid") + c := kapa.Client{ + URL: srv.URL, + Username: srv.Username, + Password: srv.Password, + Ticker: &kapa.Alert{}, + } + var req KapacitorStatus + if err = json.NewDecoder(r.Body).Decode(&req); err != nil { + invalidJSON(w, h.Logger) + return + } + if err := req.Valid(); err != nil { + invalidData(w, err, h.Logger) + return + } + + // Check if the rule exists and is scoped correctly + alert, err := h.AlertRulesStore.Get(ctx, srcID, id, tid) + if err != nil { + if err == chronograf.ErrAlertNotFound { + notFound(w, id, h.Logger) + return + } + Error(w, http.StatusInternalServerError, err.Error(), h.Logger) + return + } + + var task *kapa.Task + if req.Status == "enabled" { + task, err = c.Enable(ctx, c.Href(tid)) + } else { + task, err = c.Disable(ctx, c.Href(tid)) + } + + if err != nil { + Error(w, http.StatusInternalServerError, err.Error(), h.Logger) + return + } + + res := alertResponse{ + AlertRule: alert, + Links: alertLinks{ + Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/rules/%s", srv.SrcID, srv.ID, task.ID), + Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.Href)), + Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.HrefOutput)), + }, + TICKScript: string(task.TICKScript), + Status: req.Status, } encodeJSON(w, http.StatusOK, res, h.Logger) } @@ -470,7 +558,18 @@ func (h *Service) KapacitorRulesGet(w http.ResponseWriter, r *http.Request) { } ticker := &kapa.Alert{} - c := kapa.Client{} + c := kapa.Client{ + URL: srv.URL, + Username: srv.Username, + Password: srv.Password, + Ticker: ticker, + } + statuses, err := c.AllStatus(ctx) + if err != nil { + Error(w, http.StatusInternalServerError, err.Error(), h.Logger) + return + } + res := allAlertsResponse{ Rules: []alertResponse{}, } @@ -481,6 +580,11 @@ func (h *Service) KapacitorRulesGet(w http.ResponseWriter, r *http.Request) { return } + status, ok := statuses[rule.ID] + // The defined rule is not actually in kapacitor + if !ok { + continue + } ar := alertResponse{ AlertRule: rule, Links: alertLinks{ @@ -489,6 +593,7 @@ func (h *Service) KapacitorRulesGet(w http.ResponseWriter, r *http.Request) { Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(c.HrefOutput(rule.ID))), }, TICKScript: string(tickscript), + Status: status, } res.Rules = append(res.Rules, ar) } @@ -532,13 +637,24 @@ func (h *Service) KapacitorRulesID(w http.ResponseWriter, r *http.Request) { } ticker := &kapa.Alert{} - c := kapa.Client{} + c := kapa.Client{ + URL: srv.URL, + Username: srv.Username, + Password: srv.Password, + Ticker: ticker, + } tickscript, err := ticker.Generate(rule) if err != nil { Error(w, http.StatusInternalServerError, err.Error(), h.Logger) return } + status, err := c.Status(ctx, c.Href(rule.ID)) + if err != nil { + Error(w, http.StatusInternalServerError, err.Error(), h.Logger) + return + } + res := alertResponse{ AlertRule: rule, Links: alertLinks{ @@ -547,6 +663,7 @@ func (h *Service) KapacitorRulesID(w http.ResponseWriter, r *http.Request) { Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(c.HrefOutput(rule.ID))), }, TICKScript: string(tickscript), + Status: status, } encodeJSON(w, http.StatusOK, res, h.Logger) } diff --git a/server/mux.go b/server/mux.go index 0c28a5302..5d818d9f4 100644 --- a/server/mux.go +++ b/server/mux.go @@ -42,10 +42,13 @@ func NewMux(opts MuxOpts, service Service) http.Handler { // Prefix any URLs found in the React assets with any configured basepath prefixedAssets := NewDefaultURLPrefixer(basepath, assets, opts.Logger) + // Compress the assets with gzip if an accepted encoding + compressed := gziphandler.GzipHandler(prefixedAssets) + // The react application handles all the routing if the server does not // know about the route. This means that we never have unknown // routes on the server. - router.NotFound = prefixedAssets + router.NotFound = compressed /* Documentation */ router.GET("/swagger.json", Spec()) @@ -80,6 +83,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesID) router.PUT("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesPut) + router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesStatus) router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesDelete) // Kapacitor Proxy @@ -121,8 +125,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { return Logger(opts.Logger, auth) } - compressed := gziphandler.GzipHandler(router) - logged := Logger(opts.Logger, compressed) + logged := Logger(opts.Logger, router) return logged } diff --git a/server/swagger.json b/server/swagger.json index 5fc59101c..1ff84b533 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -1851,6 +1851,14 @@ "type": "string", "description": "TICKscript representing this rule" }, + "status": { + "type": "string", + "description": "Represents if this rule is enabled or disabled in kapacitor", + "enum": [ + "enabled", + "disabled" + ] + }, "links": { "type": "object", "required": [ From c125c48efcba02961380054badc23aec64b20596 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 10 Feb 2017 13:57:12 -0600 Subject: [PATCH 4/7] Fix swagger JSON formatting --- server/swagger.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index 1ff84b533..a8f4ea64c 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -1851,14 +1851,14 @@ "type": "string", "description": "TICKscript representing this rule" }, - "status": { - "type": "string", - "description": "Represents if this rule is enabled or disabled in kapacitor", - "enum": [ - "enabled", - "disabled" - ] - }, + "status": { + "type": "string", + "description": "Represents if this rule is enabled or disabled in kapacitor", + "enum": [ + "enabled", + "disabled" + ] + }, "links": { "type": "object", "required": [ From 8aa7dec87f4f900efb30aa8f5caaadac3ea3b9b6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 10 Feb 2017 12:15:45 -0800 Subject: [PATCH 5/7] Optimistically update status state --- ui/src/kapacitor/actions/view/index.js | 1 - ui/src/kapacitor/containers/KapacitorRulesPage.js | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js index aabb10aaf..0e39c4446 100644 --- a/ui/src/kapacitor/actions/view/index.js +++ b/ui/src/kapacitor/actions/view/index.js @@ -155,7 +155,6 @@ export function deleteRule(rule) { export function updateRuleStatus(rule, {status}) { return (dispatch) => { updateRuleStatusAPI(rule, status).then(() => { - dispatch(updateRuleStatusSuccess(rule.id, status)); dispatch(publishNotification('success', `${rule.name} ${status} successfully`)); }).catch(() => { dispatch(updateRuleStatusSuccess(rule.id, status)); diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.js b/ui/src/kapacitor/containers/KapacitorRulesPage.js index eedac93a5..cd04df8f2 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.js +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.js @@ -59,8 +59,11 @@ export const KapacitorRulesPage = React.createClass({ }, handleRuleStatus(e, rule) { + const {actions} = this.props; const status = e.target.checked ? 'enabled' : 'disabled'; - this.props.actions.updateRuleStatus(rule, {status}); + + actions.updateRuleStatusSuccess(rule.id, status); + actions.updateRuleStatus(rule, {status}); }, renderSubComponent() { From 509b13d2d6d68af1a35a20c8d2e447428f789a06 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 10 Feb 2017 14:32:22 -0800 Subject: [PATCH 6/7] Style checkboxes on kapacitor rules table --- .../containers/KapacitorRulesPage.js | 5 ++- ui/src/style/theme/theme-dark.scss | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.js b/ui/src/kapacitor/containers/KapacitorRulesPage.js index cd04df8f2..3fe67d7eb 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.js +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.js @@ -147,7 +147,10 @@ export const KapacitorRulesPage = React.createClass({ {rule.message} {rule.alerts.join(', ')} - this.enabled = r} checked={rule.status === "enabled"} onClick={(e) => this.handleRuleStatus(e, rule)} /> +
+ this.enabled = r} checked={rule.status === "enabled"} onClick={(e) => this.handleRuleStatus(e, rule)} /> + +
diff --git a/ui/src/style/theme/theme-dark.scss b/ui/src/style/theme/theme-dark.scss index 80fccd25d..95958c724 100644 --- a/ui/src/style/theme/theme-dark.scss +++ b/ui/src/style/theme/theme-dark.scss @@ -611,4 +611,47 @@ $form-static-checkbox-size: 16px; } } } +} + +.dark-checkbox { + input { + position: absolute; + left: -9999px; + visibility: hidden; + } + label { + display: inline-block; + width: $form-static-checkbox-size; + height: $form-static-checkbox-size; + background-color: $g1-raven; + border-radius: $radius-small; + position: relative; + vertical-align: middle; + margin: 0; + transition: background-color 0.25s ease; + } + label:hover { + cursor: pointer; + background-color: $g2-kevlar; + } + label:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 6px; + height: 6px; + background-color: $c-pool; + border-radius: 50%; + transform: translate(-50%,-50%) scale(2,2); + opacity: 0; + z-index: 3; + transition: + opacity 0.25s ease, + transform 0.25s ease; + } + input:checked + label:after { + opacity: 1; + transform: translate(-50%,-50%) scale(1,1); + } } \ No newline at end of file From d46e86f4e2d258c19abb1a8d8cadb7075a9ad5dd Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 10 Feb 2017 14:35:58 -0800 Subject: [PATCH 7/7] Make Checkbox centered --- ui/src/kapacitor/containers/KapacitorRulesPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.js b/ui/src/kapacitor/containers/KapacitorRulesPage.js index 3fe67d7eb..25e3a9dde 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.js +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.js @@ -88,7 +88,7 @@ export const KapacitorRulesPage = React.createClass({ Trigger Message Alerts - Enabled + Enabled @@ -146,7 +146,7 @@ export const KapacitorRulesPage = React.createClass({ {rule.trigger} {rule.message} {rule.alerts.join(', ')} - +
this.enabled = r} checked={rule.status === "enabled"} onClick={(e) => this.handleRuleStatus(e, rule)} />