mirror of https://github.com/grafana/grafana.git
tech: url and query mobx store so now react components and containers can read and modify url path and query via mobx store
This commit is contained in:
parent
029317ed18
commit
8f50795a94
|
|
@ -25,12 +25,16 @@ export class AlertRuleList extends React.Component<AlertRuleListProps, any> {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.props.store.nav.load('alerting', 'alert-list');
|
||||
this.props.store.alertList.loadRules();
|
||||
const store = this.props.store;
|
||||
|
||||
store.nav.load('alerting', 'alert-list');
|
||||
store.alertList.setStateFilter(store.view.query.get('state') || 'all');
|
||||
store.alertList.loadRules();
|
||||
}
|
||||
|
||||
onStateFilterChanged = evt => {
|
||||
this.props.store.alertList.setStateFilter(evt.target.value);
|
||||
this.props.store.view.updateQuery({ state: evt.target.value });
|
||||
this.props.store.alertList.loadRules();
|
||||
};
|
||||
|
||||
|
|
@ -54,7 +58,7 @@ export class AlertRuleList extends React.Component<AlertRuleListProps, any> {
|
|||
<label className="gf-form-label">Filter by state</label>
|
||||
|
||||
<div className="gf-form-select-wrapper width-13">
|
||||
<select className="gf-form-input" onChange={this.onStateFilterChanged}>
|
||||
<select className="gf-form-input" onChange={this.onStateFilterChanged} value={alertList.stateFilter}>
|
||||
{this.stateFilters.map(AlertStateFilterOption)}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,19 +10,18 @@ import { createStore } from 'app/stores/store';
|
|||
|
||||
export class GrafanaCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, globalEventSrv, backendSrv) {
|
||||
constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, bridgeSrv, backendSrv) {
|
||||
createStore(backendSrv);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.contextSrv = contextSrv;
|
||||
|
||||
$rootScope.appSubUrl = config.appSubUrl;
|
||||
$scope.appSubUrl = config.appSubUrl;
|
||||
$scope._ = _;
|
||||
|
||||
profiler.init(config, $rootScope);
|
||||
alertSrv.init();
|
||||
utilSrv.init();
|
||||
globalEventSrv.init();
|
||||
bridgeSrv.init();
|
||||
|
||||
$scope.dashAlerts = alertSrv;
|
||||
};
|
||||
|
|
@ -54,7 +53,7 @@ export class GrafanaCtrl {
|
|||
}
|
||||
|
||||
/** @ngInject */
|
||||
export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScope) {
|
||||
export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScope, $location) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: GrafanaCtrl,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ define([
|
|||
'./segment_srv',
|
||||
'./backend_srv',
|
||||
'./dynamic_directive_srv',
|
||||
'./global_event_srv'
|
||||
'./bridge_srv'
|
||||
],
|
||||
function () {});
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import coreModule from 'app/core/core_module';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import config from 'app/core/config';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { store } from 'app/stores/store';
|
||||
import { reaction } from 'mobx';
|
||||
|
||||
// This service is for registering global events.
|
||||
// Good for communication react > angular and vice verse
|
||||
export class GlobalEventSrv {
|
||||
// Services that handles angular -> mobx store sync & other react <-> angular sync
|
||||
export class BridgeSrv {
|
||||
private appSubUrl;
|
||||
private fullPageReloadRoutes;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private $location, private $timeout, private $window) {
|
||||
constructor(private $location, private $timeout, private $window, private $rootScope) {
|
||||
this.appSubUrl = config.appSubUrl;
|
||||
this.fullPageReloadRoutes = ['/logout'];
|
||||
}
|
||||
|
|
@ -25,6 +26,31 @@ export class GlobalEventSrv {
|
|||
}
|
||||
|
||||
init() {
|
||||
this.$rootScope.$on('$routeUpdate', (evt, data) => {
|
||||
let angularUrl = this.$location.url();
|
||||
if (store.view.currentUrl !== angularUrl) {
|
||||
store.view.updatePathAndQuery(this.$location.path(), this.$location.search());
|
||||
}
|
||||
});
|
||||
|
||||
this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
|
||||
let angularUrl = this.$location.url();
|
||||
if (store.view.currentUrl !== angularUrl) {
|
||||
store.view.updatePathAndQuery(this.$location.path(), this.$location.search());
|
||||
}
|
||||
});
|
||||
|
||||
reaction(
|
||||
() => store.view.currentUrl,
|
||||
currentUrl => {
|
||||
let angularUrl = this.$location.url();
|
||||
if (angularUrl !== currentUrl) {
|
||||
this.$location.url(currentUrl);
|
||||
console.log('store updating angular $location.url', currentUrl);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
appEvents.on('location-change', payload => {
|
||||
const urlWithoutBase = this.stripBaseFromUrl(payload.href);
|
||||
if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) {
|
||||
|
|
@ -40,4 +66,4 @@ export class GlobalEventSrv {
|
|||
}
|
||||
}
|
||||
|
||||
coreModule.service('globalEventSrv', GlobalEventSrv);
|
||||
coreModule.service('bridgeSrv', BridgeSrv);
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { GlobalEventSrv } from 'app/core/services/global_event_srv';
|
||||
import { beforeEach } from 'test/lib/common';
|
||||
import { GlobalEventSrv } from 'app/core/services/bridge_srv';
|
||||
|
||||
jest.mock('app/core/config', () => {
|
||||
return {
|
||||
|
|
@ -7,7 +6,7 @@ jest.mock('app/core/config', () => {
|
|||
};
|
||||
});
|
||||
|
||||
describe('GlobalEventSrv', () => {
|
||||
describe('BridgeSrv', () => {
|
||||
let searchSrv;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { coreModule, appEvents } from 'app/core/core';
|
||||
import alertDef from './alert_def';
|
||||
|
||||
export class AlertListCtrl {
|
||||
alerts: any;
|
||||
stateFilters = [
|
||||
{ text: 'All', value: null },
|
||||
{ text: 'OK', value: 'ok' },
|
||||
{ text: 'Not OK', value: 'not_ok' },
|
||||
{ text: 'Alerting', value: 'alerting' },
|
||||
{ text: 'No Data', value: 'no_data' },
|
||||
{ text: 'Paused', value: 'paused' },
|
||||
];
|
||||
filters = {
|
||||
state: 'ALL',
|
||||
};
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $location, navModelSrv) {
|
||||
this.navModel = navModelSrv.getNav('alerting', 'alert-list', 0);
|
||||
|
||||
var params = $location.search();
|
||||
this.filters.state = params.state || null;
|
||||
this.loadAlerts();
|
||||
}
|
||||
|
||||
filtersChanged() {
|
||||
this.$location.search(this.filters);
|
||||
}
|
||||
|
||||
loadAlerts() {
|
||||
this.backendSrv.get('/api/alerts', this.filters).then(result => {
|
||||
this.alerts = _.map(result, alert => {
|
||||
alert.stateModel = alertDef.getStateDisplayModel(alert.state);
|
||||
alert.newStateDateAgo = moment(alert.newStateDate)
|
||||
.fromNow()
|
||||
.replace(' ago', '');
|
||||
if (alert.evalData && alert.evalData.no_data) {
|
||||
alert.no_data = true;
|
||||
}
|
||||
return alert;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pauseAlertRule(alertId: any) {
|
||||
var alert = _.find(this.alerts, { id: alertId });
|
||||
|
||||
var payload = {
|
||||
paused: alert.state !== 'paused',
|
||||
};
|
||||
|
||||
this.backendSrv.post(`/api/alerts/${alert.id}/pause`, payload).then(result => {
|
||||
alert.state = result.state;
|
||||
alert.stateModel = alertDef.getStateDisplayModel(result.state);
|
||||
});
|
||||
}
|
||||
|
||||
openHowTo() {
|
||||
appEvents.emit('show-modal', {
|
||||
src: 'public/app/features/alerting/partials/alert_howto.html',
|
||||
modalClass: 'confirm-modal',
|
||||
model: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.controller('AlertListCtrl', AlertListCtrl);
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
import './alert_list_ctrl';
|
||||
import './notifications_list_ctrl';
|
||||
import './notification_edit_ctrl';
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
<page-header model="ctrl.navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
|
||||
<div class="page-action-bar">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Filter by state</label>
|
||||
<div class="gf-form-select-wrapper width-13">
|
||||
<select class="gf-form-input" ng-model="ctrl.filters.state" ng-options="f.value as f.text for f in ctrl.stateFilters" ng-change="ctrl.filtersChanged()">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-action-bar__spacer">
|
||||
</div>
|
||||
|
||||
<a class="btn btn-secondary" ng-click="ctrl.openHowTo()">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
How to add an alert
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<section class="card-section card-list-layout-list">
|
||||
|
||||
<ol class="card-list" >
|
||||
<li class="card-item-wrapper" ng-repeat="alert in ctrl.alerts">
|
||||
<div class="card-item card-item--alert">
|
||||
<div class="card-item-header">
|
||||
<div class="card-item-type">
|
||||
<a class="card-item-cog" bs-tooltip="'Pausing an alert rule prevents it from executing'" ng-click="ctrl.pauseAlertRule(alert.id)">
|
||||
<i ng-show="alert.state !== 'paused'" class="fa fa-pause"></i>
|
||||
<i ng-show="alert.state === 'paused'" class="fa fa-play"></i>
|
||||
</a>
|
||||
<a class="card-item-cog" href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert" bs-tooltip="'Edit alert rule'">
|
||||
<i class="icon-gf icon-gf-settings"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-item-body">
|
||||
<div class="card-item-details">
|
||||
<div class="card-item-name">
|
||||
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
|
||||
{{alert.name}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-item-sub-name">
|
||||
<span class="alert-list-item-state {{alert.stateModel.stateClass}}">
|
||||
<i class="{{alert.stateModel.iconClass}}"></i>
|
||||
{{alert.stateModel.text}} <span class="small muted" ng-show="alert.no_data">(due to no data)</span>
|
||||
</span> for {{alert.newStateDateAgo}}
|
||||
</div>
|
||||
<div class="small muted" ng-show="alert.executionError !== ''">
|
||||
Error: "{{alert.executionError}}"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -13,7 +13,7 @@ function WrapInProvider(store, Component, props) {
|
|||
}
|
||||
|
||||
/** @ngInject */
|
||||
export function reactContainer($route) {
|
||||
export function reactContainer($route, $location) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '',
|
||||
|
|
|
|||
|
|
@ -226,8 +226,9 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||
controller: 'AlertListCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/alerting/list2', {
|
||||
.when('/alerting/list', {
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => AlertRuleList,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,14 +61,12 @@ export const AlertListStore = types
|
|||
|
||||
loadRules: flow(function* load() {
|
||||
let backendSrv = getEnv(self).backendSrv;
|
||||
|
||||
let filters = { state: self.stateFilter };
|
||||
|
||||
let rules = yield backendSrv.get('/api/alerts', filters);
|
||||
let apiRules = yield backendSrv.get('/api/alerts', filters);
|
||||
|
||||
self.rules.clear();
|
||||
|
||||
for (let rule of rules) {
|
||||
for (let rule of apiRules) {
|
||||
setStateFields(rule, rule.state);
|
||||
|
||||
if (rule.executionError) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { SearchStore } from './SearchStore';
|
|||
import { ServerStatsStore } from './ServerStatsStore';
|
||||
import { NavStore } from './NavStore';
|
||||
import { AlertListStore } from './AlertListStore';
|
||||
import { ViewStore } from './ViewStore';
|
||||
|
||||
export const RootStore = types.model({
|
||||
search: types.optional(SearchStore, {
|
||||
|
|
@ -15,6 +16,10 @@ export const RootStore = types.model({
|
|||
alertList: types.optional(AlertListStore, {
|
||||
rules: [],
|
||||
}),
|
||||
view: types.optional(ViewStore, {
|
||||
path: '',
|
||||
query: {},
|
||||
}),
|
||||
});
|
||||
|
||||
type IRootStoreType = typeof RootStore.Type;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { types } from 'mobx-state-tree';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
|
||||
export const ViewStore = types
|
||||
.model({
|
||||
path: types.string,
|
||||
query: types.map(types.string),
|
||||
})
|
||||
.views(self => ({
|
||||
get currentUrl() {
|
||||
let path = self.path;
|
||||
if (self.query.size) {
|
||||
path += '?' + $.param(self.query.toJS());
|
||||
}
|
||||
return path;
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
updatePathAndQuery(path: string, query: any) {
|
||||
self.path = path;
|
||||
self.query.clear();
|
||||
|
||||
for (let key of _.keys(query)) {
|
||||
self.query.set(key, query[key]);
|
||||
}
|
||||
},
|
||||
|
||||
updateQuery(query: any) {
|
||||
self.query.clear();
|
||||
for (let key of _.keys(query)) {
|
||||
self.query.set(key, query[key]);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
|
@ -11,4 +11,6 @@ export function createStore(backendSrv) {
|
|||
navTree: config.bootData.navTree,
|
||||
}
|
||||
);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue