diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 58ea56cc8a7..d11122016c4 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -278,7 +278,7 @@ func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response { } var response models.AlertStateType = models.AlertStatePending - pausedState := "un paused" + pausedState := "un-paused" if cmd.Paused { response = models.AlertStatePaused pausedState = "paused" @@ -287,7 +287,7 @@ func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response { result := map[string]interface{}{ "alertId": alertId, "state": response, - "message": "alert " + pausedState, + "message": "Alert " + pausedState, } return Json(200, result) diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index 73be7d774fd..a34f56066e5 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -88,7 +88,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error { params = append(params, query.PanelId) } - if len(query.State) > 0 && query.State[0] != "ALL" { + if len(query.State) > 0 && query.State[0] != "all" { sql.WriteString(` AND (`) for i, v := range query.State { if i > 0 { diff --git a/public/app/containers/AlertRuleList/AlertRuleList.tsx b/public/app/containers/AlertRuleList/AlertRuleList.tsx index b32ffed5461..ee72dd53b9d 100644 --- a/public/app/containers/AlertRuleList/AlertRuleList.tsx +++ b/public/app/containers/AlertRuleList/AlertRuleList.tsx @@ -2,49 +2,73 @@ import React from 'react'; import classNames from 'classnames'; import { inject, observer } from 'mobx-react'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; +import { IRootStore } from 'app/stores/RootStore'; +import { IAlertRule } from 'app/stores/AlertListStore'; +import appEvents from 'app/core/app_events'; -export interface IProps { - store: any; +export interface AlertRuleListProps { + store: IRootStore; } @inject('store') @observer -export class AlertRuleList extends React.Component { +export class AlertRuleList extends React.Component { + stateFilters = [ + { text: 'All', value: 'all' }, + { text: 'OK', value: 'ok' }, + { text: 'Not OK', value: 'not_ok' }, + { text: 'Alerting', value: 'alerting' }, + { text: 'No Data', value: 'no_data' }, + { text: 'Paused', value: 'paused' }, + ]; + constructor(props) { super(props); this.props.store.nav.load('alerting', 'alert-list'); - this.props.store.alerting.loadRules(); + this.props.store.alertList.loadRules(); } + onStateFilterChanged = evt => { + this.props.store.alertList.setStateFilter(evt.target.value); + this.props.store.alertList.loadRules(); + }; + + onOpenHowTo = () => { + appEvents.emit('show-modal', { + src: 'public/app/features/alerting/partials/alert_howto.html', + modalClass: 'confirm-modal', + model: {}, + }); + }; + render() { + const { nav, alertList } = this.props.store; + return (
- +
- + {this.stateFilters.map(AlertStateFilterOption)} +
-
    {this.props.store.alerting.rules.map(AlertRuleItem)}
+
    {alertList.rules.map(rule => )}
@@ -52,43 +76,68 @@ export class AlertRuleList extends React.Component { } } -function AlertRuleItem(rule) { - let stateClass = classNames({ - fa: true, - 'fa-play': rule.state === 'paused', - 'fa-pause': rule.state !== 'paused', - }); - - let ruleUrl = `dashboard/${rule.dashboardUri}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`; - +function AlertStateFilterOption({ text, value }) { return ( -
  • -
    -
    - -
    -
    -
    - -
    - - {rule.stateText} - - for {rule.stateAge} -
    - {rule.info &&
    {rule.info}
    } -
    -
    -
    -
  • + ); } + +export interface AlertRuleItemProps { + rule: IAlertRule; +} + +@observer +export class AlertRuleItem extends React.Component { + toggleState = () => { + this.props.rule.togglePaused(); + }; + + render() { + const { rule } = this.props; + + let stateClass = classNames({ + fa: true, + 'fa-play': rule.isPaused, + 'fa-pause': !rule.isPaused, + }); + + let ruleUrl = `dashboard/${rule.dashboardUri}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`; + + return ( +
  • +
    +
    + +
    +
    +
    + +
    + + {rule.stateText} + + for {rule.stateAge} +
    + {rule.info &&
    {rule.info}
    } +
    +
    +
    +
  • + ); + } +} diff --git a/public/app/stores/AlertListStore.ts b/public/app/stores/AlertListStore.ts new file mode 100644 index 00000000000..a72341b47be --- /dev/null +++ b/public/app/stores/AlertListStore.ts @@ -0,0 +1,85 @@ +import { types, getEnv, flow } from 'mobx-state-tree'; +import moment from 'moment'; +import alertDef from 'app/features/alerting/alert_def'; + +function setStateFields(rule, state) { + let stateModel = alertDef.getStateDisplayModel(state); + rule.state = state; + rule.stateText = stateModel.text; + rule.stateIcon = stateModel.iconClass; + rule.stateClass = stateModel.stateClass; + rule.stateAge = moment(rule.newStateDate) + .fromNow() + .replace(' ago', ''); +} + +export const AlertRule = types + .model('AlertRule', { + id: types.identifier(types.number), + dashboardId: types.number, + panelId: types.number, + name: types.string, + state: types.string, + stateText: types.string, + stateIcon: types.string, + stateClass: types.string, + stateAge: types.string, + info: types.optional(types.string, ''), + dashboardUri: types.string, + }) + .views(self => ({ + get isPaused() { + return self.state === 'paused'; + }, + })) + .actions(self => ({ + /** + * will toggle alert rule paused state + */ + togglePaused: flow(function* togglePaused() { + let backendSrv = getEnv(self).backendSrv; + + var payload = { paused: self.isPaused }; + let res = yield backendSrv.post(`/api/alerts/${self.id}/pause`, payload); + setStateFields(self, res.state); + self.info = ''; + }), + })); + +type IAlertRuleType = typeof AlertRule.Type; +export interface IAlertRule extends IAlertRuleType {} + +export const AlertListStore = types + .model('AlertListStore', { + rules: types.array(AlertRule), + stateFilter: types.optional(types.string, 'all'), + }) + .actions(self => ({ + setStateFilter: function(state) { + self.stateFilter = state; + }, + + loadRules: flow(function* load() { + let backendSrv = getEnv(self).backendSrv; + + let filters = { state: self.stateFilter }; + + let rules = yield backendSrv.get('/api/alerts', filters); + + self.rules.clear(); + + for (let rule of rules) { + setStateFields(rule, rule.state); + + if (rule.executionError) { + rule.info = 'Execution Error: ' + rule.executionError; + } + + if (rule.evalData && rule.evalData.noData) { + rule.info = 'Query returned no data'; + } + + self.rules.push(AlertRule.create(rule)); + } + }), + })); diff --git a/public/app/stores/AlertingStore.ts b/public/app/stores/AlertingStore.ts deleted file mode 100644 index f364da58cd0..00000000000 --- a/public/app/stores/AlertingStore.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { types, getEnv, flow } from 'mobx-state-tree'; -import moment from 'moment'; -import alertDef from 'app/features/alerting/alert_def'; - -export const AlertRule = types.model('AlertRule', { - id: types.identifier(types.number), - dashboardId: types.number, - panelId: types.number, - name: types.string, - state: types.string, - stateText: types.string, - stateIcon: types.string, - stateClass: types.string, - stateAge: types.string, - info: types.optional(types.string, ''), - dashboardUri: types.string, -}); - -export const AlertingStore = types - .model('AlertingStore', { - rules: types.array(AlertRule), - }) - .actions(self => ({ - loadRules: flow(function* load() { - let backendSrv = getEnv(self).backendSrv; - - let rules = yield backendSrv.get('/api/alerts'); - - self.rules.clear(); - - for (let rule of rules) { - let stateModel = alertDef.getStateDisplayModel(rule.state); - rule.stateText = stateModel.text; - rule.stateIcon = stateModel.iconClass; - rule.stateClass = stateModel.stateClass; - rule.stateAge = moment(rule.newStateDate) - .fromNow() - .replace(' ago', ''); - - if (rule.executionError) { - rule.info = 'Execution Error: ' + rule.executionError; - } - - if (rule.evalData && rule.evalData.noData) { - rule.info = 'Query returned no data'; - } - - self.rules.push(AlertRule.create(rule)); - } - }), - })); diff --git a/public/app/stores/RootStore.ts b/public/app/stores/RootStore.ts index a275a12c2ff..729faf7b533 100644 --- a/public/app/stores/RootStore.ts +++ b/public/app/stores/RootStore.ts @@ -2,7 +2,7 @@ import { types } from 'mobx-state-tree'; import { SearchStore } from './SearchStore'; import { ServerStatsStore } from './ServerStatsStore'; import { NavStore } from './NavStore'; -import { AlertingStore } from './AlertingStore'; +import { AlertListStore } from './AlertListStore'; export const RootStore = types.model({ search: types.optional(SearchStore, { @@ -12,7 +12,7 @@ export const RootStore = types.model({ stats: [], }), nav: types.optional(NavStore, {}), - alerting: types.optional(AlertingStore, { + alertList: types.optional(AlertListStore, { rules: [], }), });