mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch '13739/alert-to-react'
This commit is contained in:
28
public/app/core/actions/appNotification.ts
Normal file
28
public/app/core/actions/appNotification.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { AppNotification } from 'app/types/';
|
||||||
|
|
||||||
|
export enum ActionTypes {
|
||||||
|
AddAppNotification = 'ADD_APP_NOTIFICATION',
|
||||||
|
ClearAppNotification = 'CLEAR_APP_NOTIFICATION',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddAppNotificationAction {
|
||||||
|
type: ActionTypes.AddAppNotification;
|
||||||
|
payload: AppNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClearAppNotificationAction {
|
||||||
|
type: ActionTypes.ClearAppNotification;
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Action = AddAppNotificationAction | ClearAppNotificationAction;
|
||||||
|
|
||||||
|
export const clearAppNotification = (appNotificationId: number) => ({
|
||||||
|
type: ActionTypes.ClearAppNotification,
|
||||||
|
payload: appNotificationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notifyApp = (appNotification: AppNotification) => ({
|
||||||
|
type: ActionTypes.AddAppNotification,
|
||||||
|
payload: appNotification,
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { updateLocation } from './location';
|
import { updateLocation } from './location';
|
||||||
import { updateNavIndex, UpdateNavIndexAction } from './navModel';
|
import { updateNavIndex, UpdateNavIndexAction } from './navModel';
|
||||||
|
import { notifyApp, clearAppNotification } from './appNotification';
|
||||||
|
|
||||||
export { updateLocation, updateNavIndex, UpdateNavIndexAction };
|
export { updateLocation, updateNavIndex, UpdateNavIndexAction, notifyApp, clearAppNotification };
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
|
|||||||
import { SearchResult } from './components/search/SearchResult';
|
import { SearchResult } from './components/search/SearchResult';
|
||||||
import { TagFilter } from './components/TagFilter/TagFilter';
|
import { TagFilter } from './components/TagFilter/TagFilter';
|
||||||
import { SideMenu } from './components/sidemenu/SideMenu';
|
import { SideMenu } from './components/sidemenu/SideMenu';
|
||||||
|
import AppNotificationList from './components/AppNotifications/AppNotificationList';
|
||||||
|
|
||||||
export function registerAngularDirectives() {
|
export function registerAngularDirectives() {
|
||||||
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
|
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
|
||||||
react2AngularDirective('sidemenu', SideMenu, []);
|
react2AngularDirective('sidemenu', SideMenu, []);
|
||||||
|
react2AngularDirective('appNotificationsList', AppNotificationList, []);
|
||||||
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
|
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
|
||||||
react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
|
react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
|
||||||
react2AngularDirective('searchResult', SearchResult, []);
|
react2AngularDirective('searchResult', SearchResult, []);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { AppNotification } from 'app/types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
appNotification: AppNotification;
|
||||||
|
onClearNotification: (id) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AppNotificationItem extends Component<Props> {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return this.props.appNotification.id !== nextProps.appNotification.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { appNotification, onClearNotification } = this.props;
|
||||||
|
setTimeout(() => {
|
||||||
|
onClearNotification(appNotification.id);
|
||||||
|
}, appNotification.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { appNotification, onClearNotification } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={`alert-${appNotification.severity} alert`}>
|
||||||
|
<div className="alert-icon">
|
||||||
|
<i className={appNotification.icon} />
|
||||||
|
</div>
|
||||||
|
<div className="alert-body">
|
||||||
|
<div className="alert-title">{appNotification.title}</div>
|
||||||
|
<div className="alert-text">{appNotification.text}</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" className="alert-close" onClick={() => onClearNotification(appNotification.id)}>
|
||||||
|
<i className="fa fa fa-remove" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
|
import AppNotificationItem from './AppNotificationItem';
|
||||||
|
import { notifyApp, clearAppNotification } from 'app/core/actions';
|
||||||
|
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||||
|
import { AppNotification, StoreState } from 'app/types';
|
||||||
|
import {
|
||||||
|
createErrorNotification,
|
||||||
|
createSuccessNotification,
|
||||||
|
createWarningNotification,
|
||||||
|
} from '../../copy/appNotification';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
appNotifications: AppNotification[];
|
||||||
|
notifyApp: typeof notifyApp;
|
||||||
|
clearAppNotification: typeof clearAppNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AppNotificationList extends PureComponent<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
const { notifyApp } = this.props;
|
||||||
|
|
||||||
|
appEvents.on('alert-warning', options => notifyApp(createWarningNotification(options[0], options[1])));
|
||||||
|
appEvents.on('alert-success', options => notifyApp(createSuccessNotification(options[0], options[1])));
|
||||||
|
appEvents.on('alert-error', options => notifyApp(createErrorNotification(options[0], options[1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClearAppNotification = id => {
|
||||||
|
this.props.clearAppNotification(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { appNotifications } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{appNotifications.map((appNotification, index) => {
|
||||||
|
return (
|
||||||
|
<AppNotificationItem
|
||||||
|
key={`${appNotification.id}-${index}`}
|
||||||
|
appNotification={appNotification}
|
||||||
|
onClearNotification={id => this.onClearAppNotification(id)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StoreState) => ({
|
||||||
|
appNotifications: state.appNotifications.appNotifications,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
notifyApp,
|
||||||
|
clearAppNotification,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connectWithStore(AppNotificationList, mapStateToProps, mapDispatchToProps);
|
||||||
46
public/app/core/copy/appNotification.ts
Normal file
46
public/app/core/copy/appNotification.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { AppNotification, AppNotificationSeverity, AppNotificationTimeout } from 'app/types';
|
||||||
|
|
||||||
|
const defaultSuccessNotification: AppNotification = {
|
||||||
|
title: '',
|
||||||
|
text: '',
|
||||||
|
severity: AppNotificationSeverity.Success,
|
||||||
|
icon: 'fa fa-check',
|
||||||
|
timeout: AppNotificationTimeout.Success,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultWarningNotification: AppNotification = {
|
||||||
|
title: '',
|
||||||
|
text: '',
|
||||||
|
severity: AppNotificationSeverity.Warning,
|
||||||
|
icon: 'fa fa-exclamation',
|
||||||
|
timeout: AppNotificationTimeout.Warning,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultErrorNotification: AppNotification = {
|
||||||
|
title: '',
|
||||||
|
text: '',
|
||||||
|
severity: AppNotificationSeverity.Error,
|
||||||
|
icon: 'fa fa-exclamation-triangle',
|
||||||
|
timeout: AppNotificationTimeout.Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSuccessNotification = (title: string, text?: string): AppNotification => ({
|
||||||
|
...defaultSuccessNotification,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
id: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createErrorNotification = (title: string, text?: string): AppNotification => ({
|
||||||
|
...defaultErrorNotification,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
id: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createWarningNotification = (title: string, text?: string): AppNotification => ({
|
||||||
|
...defaultWarningNotification,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
id: Date.now(),
|
||||||
|
});
|
||||||
51
public/app/core/reducers/appNotification.test.ts
Normal file
51
public/app/core/reducers/appNotification.test.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { appNotificationsReducer } from './appNotification';
|
||||||
|
import { ActionTypes } from '../actions/appNotification';
|
||||||
|
import { AppNotificationSeverity, AppNotificationTimeout } from 'app/types/';
|
||||||
|
|
||||||
|
describe('clear alert', () => {
|
||||||
|
it('should filter alert', () => {
|
||||||
|
const id1 = 1540301236048;
|
||||||
|
const id2 = 1540301248293;
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
appNotifications: [
|
||||||
|
{
|
||||||
|
id: id1,
|
||||||
|
severity: AppNotificationSeverity.Success,
|
||||||
|
icon: 'success',
|
||||||
|
title: 'test',
|
||||||
|
text: 'test alert',
|
||||||
|
timeout: AppNotificationTimeout.Success,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: id2,
|
||||||
|
severity: AppNotificationSeverity.Warning,
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'test2',
|
||||||
|
text: 'test alert fail 2',
|
||||||
|
timeout: AppNotificationTimeout.Warning,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = appNotificationsReducer(initialState, {
|
||||||
|
type: ActionTypes.ClearAppNotification,
|
||||||
|
payload: id2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedResult = {
|
||||||
|
appNotifications: [
|
||||||
|
{
|
||||||
|
id: id1,
|
||||||
|
severity: AppNotificationSeverity.Success,
|
||||||
|
icon: 'success',
|
||||||
|
title: 'test',
|
||||||
|
text: 'test alert',
|
||||||
|
timeout: AppNotificationTimeout.Success,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
19
public/app/core/reducers/appNotification.ts
Normal file
19
public/app/core/reducers/appNotification.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { AppNotification, AppNotificationsState } from 'app/types/';
|
||||||
|
import { Action, ActionTypes } from '../actions/appNotification';
|
||||||
|
|
||||||
|
export const initialState: AppNotificationsState = {
|
||||||
|
appNotifications: [] as AppNotification[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appNotificationsReducer = (state = initialState, action: Action): AppNotificationsState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.AddAppNotification:
|
||||||
|
return { ...state, appNotifications: state.appNotifications.concat([action.payload]) };
|
||||||
|
case ActionTypes.ClearAppNotification:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
appNotifications: state.appNotifications.filter(appNotification => appNotification.id !== action.payload),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { navIndexReducer as navIndex } from './navModel';
|
import { navIndexReducer as navIndex } from './navModel';
|
||||||
import { locationReducer as location } from './location';
|
import { locationReducer as location } from './location';
|
||||||
|
import { appNotificationsReducer as appNotifications } from './appNotification';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
navIndex,
|
navIndex,
|
||||||
location,
|
location,
|
||||||
|
appNotifications,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,100 +1,12 @@
|
|||||||
import angular from 'angular';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
|
||||||
|
|
||||||
export class AlertSrv {
|
export class AlertSrv {
|
||||||
list: any[];
|
constructor() {}
|
||||||
|
|
||||||
/** @ngInject */
|
set() {
|
||||||
constructor(private $timeout, private $rootScope) {
|
console.log('old depricated alert srv being used');
|
||||||
this.list = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.$rootScope.onAppEvent(
|
|
||||||
'alert-error',
|
|
||||||
(e, alert) => {
|
|
||||||
this.set(alert[0], alert[1], 'error', 12000);
|
|
||||||
},
|
|
||||||
this.$rootScope
|
|
||||||
);
|
|
||||||
|
|
||||||
this.$rootScope.onAppEvent(
|
|
||||||
'alert-warning',
|
|
||||||
(e, alert) => {
|
|
||||||
this.set(alert[0], alert[1], 'warning', 5000);
|
|
||||||
},
|
|
||||||
this.$rootScope
|
|
||||||
);
|
|
||||||
|
|
||||||
this.$rootScope.onAppEvent(
|
|
||||||
'alert-success',
|
|
||||||
(e, alert) => {
|
|
||||||
this.set(alert[0], alert[1], 'success', 3000);
|
|
||||||
},
|
|
||||||
this.$rootScope
|
|
||||||
);
|
|
||||||
|
|
||||||
appEvents.on('alert-warning', options => this.set(options[0], options[1], 'warning', 5000));
|
|
||||||
appEvents.on('alert-success', options => this.set(options[0], options[1], 'success', 3000));
|
|
||||||
appEvents.on('alert-error', options => this.set(options[0], options[1], 'error', 7000));
|
|
||||||
}
|
|
||||||
|
|
||||||
getIconForSeverity(severity) {
|
|
||||||
switch (severity) {
|
|
||||||
case 'success':
|
|
||||||
return 'fa fa-check';
|
|
||||||
case 'error':
|
|
||||||
return 'fa fa-exclamation-triangle';
|
|
||||||
default:
|
|
||||||
return 'fa fa-exclamation';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set(title, text, severity, timeout) {
|
|
||||||
if (_.isObject(text)) {
|
|
||||||
console.log('alert error', text);
|
|
||||||
if (text.statusText) {
|
|
||||||
text = `HTTP Error (${text.status}) ${text.statusText}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newAlert = {
|
|
||||||
title: title || '',
|
|
||||||
text: text || '',
|
|
||||||
severity: severity || 'info',
|
|
||||||
icon: this.getIconForSeverity(severity),
|
|
||||||
};
|
|
||||||
|
|
||||||
const newAlertJson = angular.toJson(newAlert);
|
|
||||||
|
|
||||||
// remove same alert if it already exists
|
|
||||||
_.remove(this.list, value => {
|
|
||||||
return angular.toJson(value) === newAlertJson;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.list.push(newAlert);
|
|
||||||
if (timeout > 0) {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.list = _.without(this.list, newAlert);
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.$rootScope.$$phase) {
|
|
||||||
this.$rootScope.$digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
return newAlert;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(alert) {
|
|
||||||
this.list = _.without(this.list, alert);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAll() {
|
|
||||||
this.list = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is just added to not break old plugins that might be using it
|
||||||
coreModule.service('alertSrv', AlertSrv);
|
coreModule.service('alertSrv', AlertSrv);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class BackendSrv {
|
|||||||
private noBackendCache: boolean;
|
private noBackendCache: boolean;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $http, private alertSrv, private $q, private $timeout, private contextSrv) {}
|
constructor(private $http, private $q, private $timeout, private contextSrv) {}
|
||||||
|
|
||||||
get(url, params?) {
|
get(url, params?) {
|
||||||
return this.request({ method: 'GET', url: url, params: params });
|
return this.request({ method: 'GET', url: url, params: params });
|
||||||
@@ -49,14 +49,14 @@ export class BackendSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (err.status === 422) {
|
if (err.status === 422) {
|
||||||
this.alertSrv.set('Validation failed', data.message, 'warning', 4000);
|
appEvents.emit('alert-warning', ['Validation failed', data.message]);
|
||||||
throw data;
|
throw data;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.severity = 'error';
|
let severity = 'error';
|
||||||
|
|
||||||
if (err.status < 500) {
|
if (err.status < 500) {
|
||||||
data.severity = 'warning';
|
severity = 'warning';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
@@ -66,7 +66,8 @@ export class BackendSrv {
|
|||||||
description = message;
|
description = message;
|
||||||
message = 'Error';
|
message = 'Error';
|
||||||
}
|
}
|
||||||
this.alertSrv.set(message, description, data.severity, 10000);
|
|
||||||
|
appEvents.emit('alert-' + severity, [message, description]);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw data;
|
throw data;
|
||||||
@@ -93,7 +94,7 @@ export class BackendSrv {
|
|||||||
if (options.method !== 'GET') {
|
if (options.method !== 'GET') {
|
||||||
if (results && results.data.message) {
|
if (results && results.data.message) {
|
||||||
if (options.showSuccessAlert !== false) {
|
if (options.showSuccessAlert !== false) {
|
||||||
this.alertSrv.set(results.data.message, '', 'success', 3000);
|
appEvents.emit('alert-success', [results.data.message]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe('backend_srv', () => {
|
|||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const _backendSrv = new BackendSrv(_httpBackend, {}, {}, {}, {});
|
const _backendSrv = new BackendSrv(_httpBackend, {}, {}, {});
|
||||||
|
|
||||||
describe('when handling errors', () => {
|
describe('when handling errors', () => {
|
||||||
it('should return the http status code', async () => {
|
it('should return the http status code', async () => {
|
||||||
|
|||||||
11
public/app/core/utils/connectWithReduxStore.tsx
Normal file
11
public/app/core/utils/connectWithReduxStore.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { store } from '../../store/configureStore';
|
||||||
|
|
||||||
|
export function connectWithStore(WrappedComponent, ...args) {
|
||||||
|
const ConnectedWrappedComponent = connect(...args)(WrappedComponent);
|
||||||
|
|
||||||
|
return props => {
|
||||||
|
return <ConnectedWrappedComponent {...props} store={store} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Tooltip from 'app/core/components/Tooltip/Tooltip';
|
import Tooltip from 'app/core/components/Tooltip/Tooltip';
|
||||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||||
import { StoreState, FolderInfo } from 'app/types';
|
import { StoreState, FolderInfo } from 'app/types';
|
||||||
@@ -13,7 +12,7 @@ import {
|
|||||||
import PermissionList from 'app/core/components/PermissionList/PermissionList';
|
import PermissionList from 'app/core/components/PermissionList/PermissionList';
|
||||||
import AddPermission from 'app/core/components/PermissionList/AddPermission';
|
import AddPermission from 'app/core/components/PermissionList/AddPermission';
|
||||||
import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo';
|
import PermissionsInfo from 'app/core/components/PermissionList/PermissionsInfo';
|
||||||
import { store } from 'app/store/configureStore';
|
import { connectWithStore } from '../../../core/utils/connectWithReduxStore';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
dashboardId: number;
|
dashboardId: number;
|
||||||
@@ -95,13 +94,6 @@ export class DashboardPermissions extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectWithStore(WrappedComponent, ...args) {
|
|
||||||
const ConnectedWrappedComponent = connect(...args)(WrappedComponent);
|
|
||||||
return props => {
|
|
||||||
return <ConnectedWrappedComponent {...props} store={store} />;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: StoreState) => ({
|
const mapStateToProps = (state: StoreState) => ({
|
||||||
permissions: state.dashboard.permissions,
|
permissions: state.dashboard.permissions,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const template = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function uploadDashboardDirective(timer, alertSrv, $location) {
|
function uploadDashboardDirective(timer, $location) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
template: template,
|
template: template,
|
||||||
@@ -59,7 +59,7 @@ function uploadDashboardDirective(timer, alertSrv, $location) {
|
|||||||
// Something
|
// Something
|
||||||
elem[0].addEventListener('change', file_selected, false);
|
elem[0].addEventListener('change', file_selected, false);
|
||||||
} else {
|
} else {
|
||||||
alertSrv.set('Oops', 'Sorry, the HTML5 File APIs are not fully supported in this browser.', 'error');
|
appEvents.emit('alert-error', ['Oops', 'The HTML5 File APIs are not fully supported in this browser']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export class GrafanaCtrl {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
$scope,
|
$scope,
|
||||||
alertSrv,
|
|
||||||
utilSrv,
|
utilSrv,
|
||||||
$rootScope,
|
$rootScope,
|
||||||
$controller,
|
$controller,
|
||||||
@@ -41,11 +40,8 @@ export class GrafanaCtrl {
|
|||||||
$scope._ = _;
|
$scope._ = _;
|
||||||
|
|
||||||
profiler.init(config, $rootScope);
|
profiler.init(config, $rootScope);
|
||||||
alertSrv.init();
|
|
||||||
utilSrv.init();
|
utilSrv.init();
|
||||||
bridgeSrv.init();
|
bridgeSrv.init();
|
||||||
|
|
||||||
$scope.dashAlerts = alertSrv;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.colors = colors;
|
$rootScope.colors = colors;
|
||||||
|
|||||||
25
public/app/types/appNotifications.ts
Normal file
25
public/app/types/appNotifications.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export interface AppNotification {
|
||||||
|
id?: number;
|
||||||
|
severity: AppNotificationSeverity;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
timeout: AppNotificationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AppNotificationSeverity {
|
||||||
|
Success = 'success',
|
||||||
|
Warning = 'warning',
|
||||||
|
Error = 'error',
|
||||||
|
Info = 'info',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AppNotificationTimeout {
|
||||||
|
Warning = 5000,
|
||||||
|
Success = 3000,
|
||||||
|
Error = 7000,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppNotificationsState {
|
||||||
|
appNotifications: AppNotification[];
|
||||||
|
}
|
||||||
@@ -22,6 +22,12 @@ import {
|
|||||||
} from './series';
|
} from './series';
|
||||||
import { PanelProps } from './panel';
|
import { PanelProps } from './panel';
|
||||||
import { PluginDashboard, PluginMeta, Plugin, PluginsState } from './plugins';
|
import { PluginDashboard, PluginMeta, Plugin, PluginsState } from './plugins';
|
||||||
|
import {
|
||||||
|
AppNotification,
|
||||||
|
AppNotificationSeverity,
|
||||||
|
AppNotificationsState,
|
||||||
|
AppNotificationTimeout,
|
||||||
|
} from './appNotifications';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Team,
|
Team,
|
||||||
@@ -70,6 +76,10 @@ export {
|
|||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
DataQueryOptions,
|
DataQueryOptions,
|
||||||
PluginDashboard,
|
PluginDashboard,
|
||||||
|
AppNotification,
|
||||||
|
AppNotificationsState,
|
||||||
|
AppNotificationSeverity,
|
||||||
|
AppNotificationTimeout,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
@@ -82,4 +92,5 @@ export interface StoreState {
|
|||||||
dashboard: DashboardState;
|
dashboard: DashboardState;
|
||||||
dataSources: DataSourcesState;
|
dataSources: DataSourcesState;
|
||||||
users: UsersState;
|
users: UsersState;
|
||||||
|
appNotifications: AppNotificationsState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
padding: 1.25rem 2rem 1.25rem 1.5rem;
|
padding: 1.25rem 2rem 1.25rem 1.5rem;
|
||||||
margin-bottom: $line-height-base;
|
margin-bottom: $panel-margin / 2;
|
||||||
text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
|
text-shadow: 0 2px 0 rgba(255, 255, 255, 0.5);
|
||||||
background: $alert-error-bg;
|
background: $alert-error-bg;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: $white;
|
color: $white;
|
||||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 2px;
|
border-radius: $border-radius;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,21 +200,8 @@
|
|||||||
|
|
||||||
<grafana-app class="grafana-app" ng-cloak>
|
<grafana-app class="grafana-app" ng-cloak>
|
||||||
<sidemenu class="sidemenu"></sidemenu>
|
<sidemenu class="sidemenu"></sidemenu>
|
||||||
|
<app-notifications-list class="page-alert-list"></app-notifications-list>
|
||||||
|
|
||||||
<div class="page-alert-list">
|
|
||||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
|
|
||||||
<div class="alert-icon">
|
|
||||||
<i class="{{alert.icon}}"></i>
|
|
||||||
</div>
|
|
||||||
<div class="alert-body">
|
|
||||||
<div class="alert-title">{{alert.title}}</div>
|
|
||||||
<div class="alert-text" ng-bind='alert.text'></div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="alert-close" ng-click="dashAlerts.clear(alert)">
|
|
||||||
<i class="fa fa fa-remove"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main-view">
|
<div class="main-view">
|
||||||
<div class="scroll-canvas" page-scrollbar>
|
<div class="scroll-canvas" page-scrollbar>
|
||||||
|
|||||||
Reference in New Issue
Block a user