mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 11:44:26 -06:00
Merge branch 'master' into folder-to-redux
This commit is contained in:
commit
89ea47e7fb
@ -3,6 +3,7 @@
|
||||
### Minor
|
||||
|
||||
* **OAuth**: Allow oauth email attribute name to be configurable [#12986](https://github.com/grafana/grafana/issues/12986), thx [@bobmshannon](https://github.com/bobmshannon)
|
||||
* **Tags**: Default sort order for GetDashboardTags [#11681](https://github.com/grafana/grafana/pull/11681), thx [@Jonnymcc](https://github.com/Jonnymcc)
|
||||
|
||||
# 5.3.0 (unreleased)
|
||||
|
||||
|
3
Makefile
3
Makefile
@ -43,6 +43,3 @@ test: test-go test-js
|
||||
|
||||
run:
|
||||
./bin/grafana-server
|
||||
|
||||
protoc:
|
||||
protoc -I pkg/tsdb/models pkg/tsdb/models/*.proto --go_out=plugins=grpc:pkg/tsdb/models/.
|
||||
|
@ -174,6 +174,36 @@ allowed_organizations =
|
||||
allowed_organizations =
|
||||
```
|
||||
|
||||
## Set up OAuth2 with Centrify
|
||||
|
||||
1. Create a new Custom OpenID Connect application configuration in the Centrify dashboard.
|
||||
|
||||
2. Create a memorable unique Application ID, e.g. "grafana", "grafana_aws", etc.
|
||||
|
||||
3. Put in other basic configuration (name, description, logo, category)
|
||||
|
||||
4. On the Trust tab, generate a long password and put it into the OpenID Connect Client Secret field.
|
||||
|
||||
5. Put the URL to the front page of your Grafana instance into the "Resource Application URL" field.
|
||||
|
||||
6. Add an authorized Redirect URI like https://your-grafana-server/login/generic_oauth
|
||||
|
||||
7. Set up permissions, policies, etc. just like any other Centrify app
|
||||
|
||||
8. Configure Grafana as follows:
|
||||
|
||||
```bash
|
||||
[auth.generic_oauth]
|
||||
name = Centrify
|
||||
enabled = true
|
||||
allow_sign_up = true
|
||||
client_id = <OpenID Connect Client ID from Centrify>
|
||||
client_secret = <your generated OpenID Connect Client Sercret"
|
||||
scopes = openid email name
|
||||
auth_url = https://<your domain>.my.centrify.com/OAuth2/Authorize/<Application ID>
|
||||
token_url = https://<your domain>.my.centrify.com/OAuth2/Token/<Application ID>
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
|
@ -1,13 +1,8 @@
|
||||
|
||||
module.exports = {
|
||||
verbose: false,
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsConfigFile": "tsconfig.json"
|
||||
}
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
|
||||
"^.+\\.(ts|tsx)$": "ts-jest"
|
||||
},
|
||||
"moduleDirectories": ["node_modules", "public"],
|
||||
"roots": [
|
||||
|
11
package.json
11
package.json
@ -34,7 +34,7 @@
|
||||
"expect.js": "~0.2.0",
|
||||
"expose-loader": "^0.7.3",
|
||||
"file-loader": "^1.1.11",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.2",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
||||
"gaze": "^1.1.2",
|
||||
"glob": "~7.0.0",
|
||||
"grunt": "1.0.1",
|
||||
@ -56,7 +56,7 @@
|
||||
"html-webpack-harddisk-plugin": "^0.2.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^22.0.4",
|
||||
"jest": "^23.6.0",
|
||||
"lint-staged": "^6.0.0",
|
||||
"load-grunt-tasks": "3.5.2",
|
||||
"mini-css-extract-plugin": "^0.4.0",
|
||||
@ -80,12 +80,12 @@
|
||||
"style-loader": "^0.21.0",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "^0.1.36",
|
||||
"ts-jest": "^22.4.6",
|
||||
"ts-loader": "^4.3.0",
|
||||
"ts-jest": "^23.1.4",
|
||||
"ts-loader": "^5.1.0",
|
||||
"tslib": "^1.9.3",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-loader": "^3.5.3",
|
||||
"typescript": "^2.6.2",
|
||||
"typescript": "^3.0.3",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"webpack": "^4.8.0",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
@ -133,6 +133,7 @@
|
||||
"angular-native-dragdrop": "1.2.2",
|
||||
"angular-route": "1.6.6",
|
||||
"angular-sanitize": "1.6.6",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"baron": "^3.0.3",
|
||||
"brace": "^0.10.0",
|
||||
|
@ -295,7 +295,8 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
FROM dashboard
|
||||
INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
|
||||
WHERE dashboard.org_id=?
|
||||
GROUP BY term`
|
||||
GROUP BY term
|
||||
ORDER BY term`
|
||||
|
||||
query.Result = make([]*m.DashboardTagCloudItem, 0)
|
||||
sess := x.Sql(sql, query.OrgId)
|
||||
|
@ -466,6 +466,9 @@ func (e *CloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context,
|
||||
return nil, errors.New("invalid attribute path")
|
||||
}
|
||||
v = v.FieldByName(key)
|
||||
if !v.IsValid() {
|
||||
return nil, errors.New("invalid attribute path")
|
||||
}
|
||||
}
|
||||
if attr, ok := v.Interface().(*string); ok {
|
||||
data = *attr
|
||||
|
@ -6,10 +6,6 @@ export enum ActionTypes {
|
||||
|
||||
export type Action = UpdateNavIndexAction;
|
||||
|
||||
// this action is not used yet
|
||||
// kind of just a placeholder, will be need for dynamic pages
|
||||
// like datasource edit, teams edit page
|
||||
|
||||
export interface UpdateNavIndexAction {
|
||||
type: ActionTypes.UpdateNavIndex;
|
||||
payload: NavModelItem;
|
||||
|
@ -6,7 +6,6 @@ exports[`TeamPicker renders correctly 1`] = `
|
||||
>
|
||||
<div
|
||||
className="Select gf-form-input gf-form-input--form-dropdown is-clearable is-loading is-searchable Select--single"
|
||||
style={undefined}
|
||||
>
|
||||
<div
|
||||
className="Select-control"
|
||||
@ -15,7 +14,6 @@ exports[`TeamPicker renders correctly 1`] = `
|
||||
onTouchEnd={[Function]}
|
||||
onTouchMove={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={undefined}
|
||||
>
|
||||
<span
|
||||
className="Select-multi-value-wrapper"
|
||||
@ -36,14 +34,9 @@ exports[`TeamPicker renders correctly 1`] = `
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="react-select-2--value"
|
||||
aria-describedby={undefined}
|
||||
aria-expanded="false"
|
||||
aria-haspopup="false"
|
||||
aria-label={undefined}
|
||||
aria-labelledby={undefined}
|
||||
aria-owns=""
|
||||
className={undefined}
|
||||
id={undefined}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
@ -55,7 +48,6 @@ exports[`TeamPicker renders correctly 1`] = `
|
||||
"width": "5px",
|
||||
}
|
||||
}
|
||||
tabIndex={undefined}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
|
@ -6,7 +6,6 @@ exports[`UserPicker renders correctly 1`] = `
|
||||
>
|
||||
<div
|
||||
className="Select gf-form-input gf-form-input--form-dropdown is-clearable is-loading is-searchable Select--single"
|
||||
style={undefined}
|
||||
>
|
||||
<div
|
||||
className="Select-control"
|
||||
@ -15,7 +14,6 @@ exports[`UserPicker renders correctly 1`] = `
|
||||
onTouchEnd={[Function]}
|
||||
onTouchMove={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={undefined}
|
||||
>
|
||||
<span
|
||||
className="Select-multi-value-wrapper"
|
||||
@ -36,14 +34,9 @@ exports[`UserPicker renders correctly 1`] = `
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="react-select-2--value"
|
||||
aria-describedby={undefined}
|
||||
aria-expanded="false"
|
||||
aria-haspopup="false"
|
||||
aria-label={undefined}
|
||||
aria-labelledby={undefined}
|
||||
aria-owns=""
|
||||
className={undefined}
|
||||
id={undefined}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
@ -55,7 +48,6 @@ exports[`UserPicker renders correctly 1`] = `
|
||||
"width": "5px",
|
||||
}
|
||||
}
|
||||
tabIndex={undefined}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
|
@ -6,7 +6,7 @@ exports[`Render should render component 1`] = `
|
||||
>
|
||||
<a
|
||||
className="sidemenu-link"
|
||||
href="login?redirect=blank"
|
||||
href="login?redirect=%2F"
|
||||
target="_self"
|
||||
>
|
||||
<span
|
||||
@ -18,7 +18,7 @@ exports[`Render should render component 1`] = `
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="login?redirect=blank"
|
||||
href="login?redirect=%2F"
|
||||
target="_self"
|
||||
>
|
||||
<ul
|
||||
|
@ -66,7 +66,6 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
<a
|
||||
className="gf-tabs-link active"
|
||||
href="Admin"
|
||||
target={undefined}
|
||||
>
|
||||
<i
|
||||
className="icon"
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { AlertRuleApi, StoreState } from 'app/types';
|
||||
import { AlertRuleDTO, StoreState } from 'app/types';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadAlertRules = 'LOAD_ALERT_RULES',
|
||||
SetSearchQuery = 'SET_SEARCH_QUERY',
|
||||
SetSearchQuery = 'SET_ALERT_SEARCH_QUERY',
|
||||
}
|
||||
|
||||
export interface LoadAlertRulesAction {
|
||||
type: ActionTypes.LoadAlertRules;
|
||||
payload: AlertRuleApi[];
|
||||
payload: AlertRuleDTO[];
|
||||
}
|
||||
|
||||
export interface SetSearchQueryAction {
|
||||
@ -17,7 +17,7 @@ export interface SetSearchQueryAction {
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export const loadAlertRules = (rules: AlertRuleApi[]): LoadAlertRulesAction => ({
|
||||
export const loadAlertRules = (rules: AlertRuleDTO[]): LoadAlertRulesAction => ({
|
||||
type: ActionTypes.LoadAlertRules,
|
||||
payload: rules,
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { ActionTypes, Action } from './actions';
|
||||
import { alertRulesReducer, initialState } from './reducers';
|
||||
import { AlertRuleApi } from '../../../types';
|
||||
import { AlertRuleDTO } from 'app/types';
|
||||
|
||||
describe('Alert rules', () => {
|
||||
const payload: AlertRuleApi[] = [
|
||||
const payload: AlertRuleDTO[] = [
|
||||
{
|
||||
id: 2,
|
||||
dashboardId: 7,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import moment from 'moment';
|
||||
import { AlertRuleApi, AlertRule, AlertRulesState } from 'app/types';
|
||||
import { AlertRuleDTO, AlertRule, AlertRulesState } from 'app/types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
import alertDef from './alertDef';
|
||||
|
||||
@ -29,7 +29,7 @@ function convertToAlertRule(rule, state): AlertRule {
|
||||
export const alertRulesReducer = (state = initialState, action: Action): AlertRulesState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.LoadAlertRules: {
|
||||
const alertRules: AlertRuleApi[] = action.payload;
|
||||
const alertRules: AlertRuleDTO[] = action.payload;
|
||||
|
||||
const alertRulesViewModel: AlertRule[] = alertRules.map(rule => {
|
||||
return convertToAlertRule(rule, rule.state);
|
||||
|
@ -6,6 +6,8 @@ import { updateNavIndex, UpdateNavIndexAction } from 'app/core/actions';
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadFolder = 'LOAD_FOLDER',
|
||||
SetFolderTitle = 'SET_FOLDER_TITLE',
|
||||
SaveFolder = 'SAVE_FOLDER',
|
||||
}
|
||||
|
||||
export interface LoadFolderAction {
|
||||
@ -18,7 +20,17 @@ export const loadFolder = (folder: FolderDTO): LoadFolderAction => ({
|
||||
payload: folder,
|
||||
});
|
||||
|
||||
export type Action = LoadFolderAction;
|
||||
export interface SetFolderTitleAction {
|
||||
type: ActionTypes.SetFolderTitle;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export const setFolderTitle = (newTitle: string): SetFolderTitleAction => ({
|
||||
type: ActionTypes.SetFolderTitle,
|
||||
payload: newTitle,
|
||||
});
|
||||
|
||||
export type Action = LoadFolderAction | SetFolderTitleAction;
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action | UpdateNavIndexAction>;
|
||||
|
||||
|
@ -5,8 +5,10 @@ export const inititalState: FolderState = {
|
||||
uid: 'loading',
|
||||
id: -1,
|
||||
title: 'loading',
|
||||
url: '',
|
||||
canSave: false,
|
||||
hasChanged: false,
|
||||
version: 0,
|
||||
};
|
||||
|
||||
export const folderReducer = (state = inititalState, action: Action): FolderState => {
|
||||
|
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Props, TeamGroupSync } from './TeamGroupSync';
|
||||
import { TeamGroup } from '../../types';
|
||||
import { getMockTeamGroups } from './__mocks__/teamMocks';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
groups: [] as TeamGroup[],
|
||||
loadTeamGroups: jest.fn(),
|
||||
addTeamGroup: jest.fn(),
|
||||
removeTeamGroup: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const wrapper = shallow(<TeamGroupSync {...props} />);
|
||||
const instance = wrapper.instance() as TeamGroupSync;
|
||||
|
||||
return {
|
||||
wrapper,
|
||||
instance,
|
||||
};
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render groups table', () => {
|
||||
const { wrapper } = setup({
|
||||
groups: getMockTeamGroups(3),
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Functions', () => {
|
||||
it('should call add group', () => {
|
||||
const { instance } = setup();
|
||||
|
||||
instance.setState({ newGroupId: 'some/group' });
|
||||
const mockEvent = { preventDefault: jest.fn() };
|
||||
|
||||
instance.onAddGroup(mockEvent);
|
||||
|
||||
expect(instance.props.addTeamGroup).toHaveBeenCalledWith('some/group');
|
||||
});
|
||||
|
||||
it('should call remove group', () => {
|
||||
const { instance } = setup();
|
||||
|
||||
const mockGroup: TeamGroup = { teamId: 1, groupId: 'some/group' };
|
||||
|
||||
instance.onRemoveGroup(mockGroup);
|
||||
|
||||
expect(instance.props.removeTeamGroup).toHaveBeenCalledWith('some/group');
|
||||
});
|
||||
});
|
@ -38,11 +38,12 @@ export class TeamGroupSync extends PureComponent<Props, State> {
|
||||
this.setState({ isAdding: !this.state.isAdding });
|
||||
};
|
||||
|
||||
onNewGroupIdChanged = evt => {
|
||||
this.setState({ newGroupId: evt.target.value });
|
||||
onNewGroupIdChanged = event => {
|
||||
this.setState({ newGroupId: event.target.value });
|
||||
};
|
||||
|
||||
onAddGroup = () => {
|
||||
onAddGroup = event => {
|
||||
event.preventDefault();
|
||||
this.props.addTeamGroup(this.state.newGroupId);
|
||||
this.setState({ isAdding: false, newGroupId: '' });
|
||||
};
|
||||
@ -93,7 +94,7 @@ export class TeamGroupSync extends PureComponent<Props, State> {
|
||||
<i className="fa fa-close" />
|
||||
</button>
|
||||
<h5>Add External Group</h5>
|
||||
<div className="gf-form-inline">
|
||||
<form className="gf-form-inline" onSubmit={this.onAddGroup}>
|
||||
<div className="gf-form">
|
||||
<input
|
||||
type="text"
|
||||
@ -105,16 +106,11 @@ export class TeamGroupSync extends PureComponent<Props, State> {
|
||||
</div>
|
||||
|
||||
<div className="gf-form">
|
||||
<button
|
||||
className="btn btn-success gf-form-btn"
|
||||
onClick={this.onAddGroup}
|
||||
type="submit"
|
||||
disabled={!this.isNewGroupValid()}
|
||||
>
|
||||
<button className="btn btn-success gf-form-btn" type="submit" disabled={!this.isNewGroupValid()}>
|
||||
Add group
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</SlideDown>
|
||||
|
||||
|
@ -12,6 +12,7 @@ const setup = (propOverrides?: object) => {
|
||||
deleteTeam: jest.fn(),
|
||||
setSearchQuery: jest.fn(),
|
||||
searchQuery: '',
|
||||
teamsCount: 0,
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
@ -34,6 +35,7 @@ describe('Render', () => {
|
||||
it('should render teams table', () => {
|
||||
const { wrapper } = setup({
|
||||
teams: getMultipleMockTeams(5),
|
||||
teamsCount: 5,
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
@ -6,16 +6,17 @@ import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { NavModel, Team } from '../../types';
|
||||
import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
|
||||
import { getSearchQuery, getTeams } from './state/selectors';
|
||||
import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
teams: Team[];
|
||||
searchQuery: string;
|
||||
teamsCount: number;
|
||||
loadTeams: typeof loadTeams;
|
||||
deleteTeam: typeof deleteTeam;
|
||||
setSearchQuery: typeof setSearchQuery;
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
export class TeamList extends PureComponent<Props, any> {
|
||||
@ -125,13 +126,12 @@ export class TeamList extends PureComponent<Props, any> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navModel, teams } = this.props;
|
||||
const { navModel, teamsCount } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader model={navModel} />
|
||||
{teams.length > 0 && this.renderTeamList()}
|
||||
{teams.length === 0 && this.renderEmptyList()}
|
||||
{teamsCount > 0 ? this.renderTeamList() : this.renderEmptyList()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -142,6 +142,7 @@ function mapStateToProps(state) {
|
||||
navModel: getNavModel(state.navIndex, 'teams'),
|
||||
teams: getTeams(state.teams),
|
||||
searchQuery: getSearchQuery(state.teams),
|
||||
teamsCount: getTeamsCount(state.teams),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Team, TeamMember } from '../../../types';
|
||||
import { Team, TeamGroup, TeamMember } from '../../../types';
|
||||
|
||||
export const getMultipleMockTeams = (numberOfTeams: number): Team[] => {
|
||||
const teams: Team[] = [];
|
||||
@ -50,3 +50,16 @@ export const getMockTeamMember = (): TeamMember => {
|
||||
login: 'testUser',
|
||||
};
|
||||
};
|
||||
|
||||
export const getMockTeamGroups = (amount: number): TeamGroup[] => {
|
||||
const groups: TeamGroup[] = [];
|
||||
|
||||
for (let i = 1; i <= amount; i++) {
|
||||
groups.push({
|
||||
groupId: `group-${i}`,
|
||||
teamId: 1,
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
};
|
||||
|
@ -0,0 +1,281 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="page-action-bar"
|
||||
>
|
||||
<h3
|
||||
className="page-sub-heading"
|
||||
>
|
||||
External group sync
|
||||
</h3>
|
||||
<class_1
|
||||
className="page-sub-heading-icon"
|
||||
content="Sync LDAP or OAuth groups with your Grafana teams."
|
||||
placement="auto"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-question gicon--has-hover"
|
||||
/>
|
||||
</class_1>
|
||||
<div
|
||||
className="page-action-bar__spacer"
|
||||
/>
|
||||
</div>
|
||||
<Component
|
||||
in={false}
|
||||
>
|
||||
<div
|
||||
className="cta-form"
|
||||
>
|
||||
<button
|
||||
className="cta-form__close btn btn-transparent"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-close"
|
||||
/>
|
||||
</button>
|
||||
<h5>
|
||||
Add External Group
|
||||
</h5>
|
||||
<form
|
||||
className="gf-form-inline"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<input
|
||||
className="gf-form-input width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="cn=ops,ou=groups,dc=grafana,dc=org"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<button
|
||||
className="btn btn-success gf-form-btn"
|
||||
disabled={true}
|
||||
type="submit"
|
||||
>
|
||||
Add group
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Component>
|
||||
<div
|
||||
className="empty-list-cta"
|
||||
>
|
||||
<div
|
||||
className="empty-list-cta__title"
|
||||
>
|
||||
There are no external groups to sync with
|
||||
</div>
|
||||
<button
|
||||
className="empty-list-cta__button btn btn-xlarge btn-success"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-add-team"
|
||||
/>
|
||||
Add Group
|
||||
</button>
|
||||
<div
|
||||
className="empty-list-cta__pro-tip"
|
||||
>
|
||||
<i
|
||||
className="fa fa-rocket"
|
||||
/>
|
||||
|
||||
Sync LDAP or OAuth groups with your Grafana teams.
|
||||
<a
|
||||
className="text-link empty-list-cta__pro-tip-link"
|
||||
href="asd"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should render groups table 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="page-action-bar"
|
||||
>
|
||||
<h3
|
||||
className="page-sub-heading"
|
||||
>
|
||||
External group sync
|
||||
</h3>
|
||||
<class_1
|
||||
className="page-sub-heading-icon"
|
||||
content="Sync LDAP or OAuth groups with your Grafana teams."
|
||||
placement="auto"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-question gicon--has-hover"
|
||||
/>
|
||||
</class_1>
|
||||
<div
|
||||
className="page-action-bar__spacer"
|
||||
/>
|
||||
<button
|
||||
className="btn btn-success pull-right"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
Add group
|
||||
</button>
|
||||
</div>
|
||||
<Component
|
||||
in={false}
|
||||
>
|
||||
<div
|
||||
className="cta-form"
|
||||
>
|
||||
<button
|
||||
className="cta-form__close btn btn-transparent"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-close"
|
||||
/>
|
||||
</button>
|
||||
<h5>
|
||||
Add External Group
|
||||
</h5>
|
||||
<form
|
||||
className="gf-form-inline"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<input
|
||||
className="gf-form-input width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="cn=ops,ou=groups,dc=grafana,dc=org"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<button
|
||||
className="btn btn-success gf-form-btn"
|
||||
disabled={true}
|
||||
type="submit"
|
||||
>
|
||||
Add group
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Component>
|
||||
<div
|
||||
className="admin-list-table"
|
||||
>
|
||||
<table
|
||||
className="filter-table filter-table--hover form-inline"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
External Group ID
|
||||
</th>
|
||||
<th
|
||||
style={
|
||||
Object {
|
||||
"width": "1%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
key="group-1"
|
||||
>
|
||||
<td>
|
||||
group-1
|
||||
</td>
|
||||
<td
|
||||
style={
|
||||
Object {
|
||||
"width": "1%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<a
|
||||
className="btn btn-danger btn-mini"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-remove"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="group-2"
|
||||
>
|
||||
<td>
|
||||
group-2
|
||||
</td>
|
||||
<td
|
||||
style={
|
||||
Object {
|
||||
"width": "1%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<a
|
||||
className="btn btn-danger btn-mini"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-remove"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="group-3"
|
||||
>
|
||||
<td>
|
||||
group-3
|
||||
</td>
|
||||
<td
|
||||
style={
|
||||
Object {
|
||||
"width": "1%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<a
|
||||
className="btn btn-danger btn-mini"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-remove"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -8,70 +8,20 @@ exports[`Render should render component 1`] = `
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<div
|
||||
className="page-action-bar"
|
||||
>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<label
|
||||
className="gf-form--has-input-icon gf-form--grow"
|
||||
>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onChange={[Function]}
|
||||
placeholder="Search teams"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<i
|
||||
className="gf-form-input-icon fa fa-search"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="page-action-bar__spacer"
|
||||
/>
|
||||
<a
|
||||
className="btn btn-success"
|
||||
href="org/teams/new"
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
New team
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="admin-list-table"
|
||||
>
|
||||
<table
|
||||
className="filter-table filter-table--hover form-inline"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Email
|
||||
</th>
|
||||
<th>
|
||||
Members
|
||||
</th>
|
||||
<th
|
||||
style={
|
||||
<EmptyListCTA
|
||||
model={
|
||||
Object {
|
||||
"width": "1%",
|
||||
"buttonIcon": "fa fa-plus",
|
||||
"buttonLink": "org/teams/new",
|
||||
"buttonTitle": " New team",
|
||||
"proTip": "Assign folder and dashboard permissions to teams instead of users to ease administration.",
|
||||
"proTipLink": "",
|
||||
"proTipLinkTitle": "",
|
||||
"proTipTarget": "_blank",
|
||||
"title": "You haven't created any teams yet.",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody />
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -16,17 +16,7 @@ exports[`Render should render group sync page 1`] = `
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<TeamGroupSync
|
||||
team={
|
||||
Object {
|
||||
"avatarUrl": "some/url/",
|
||||
"email": "test@test.com",
|
||||
"id": 1,
|
||||
"memberCount": 1,
|
||||
"name": "test",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(TeamGroupSync) />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { NavModelItem, StoreState, Team, TeamGroup, TeamMember } from '../../../types';
|
||||
import { updateNavIndex } from '../../../core/actions';
|
||||
import { UpdateNavIndexAction } from '../../../core/actions/navModel';
|
||||
import { NavModelItem, StoreState, Team, TeamGroup, TeamMember } from 'app/types';
|
||||
import { updateNavIndex, UpdateNavIndexAction } from 'app/core/actions';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadTeams = 'LOAD_TEAMS',
|
||||
LoadTeam = 'LOAD_TEAM',
|
||||
SetSearchQuery = 'SET_SEARCH_QUERY',
|
||||
SetSearchMemberQuery = 'SET_SEARCH_MEMBER_QUERY',
|
||||
SetSearchQuery = 'SET_TEAM_SEARCH_QUERY',
|
||||
SetSearchMemberQuery = 'SET_TEAM_MEMBER_SEARCH_QUERY',
|
||||
LoadTeamMembers = 'TEAM_MEMBERS_LOADED',
|
||||
LoadTeamGroups = 'TEAM_GROUPS_LOADED',
|
||||
}
|
||||
@ -121,7 +120,7 @@ function buildNavModel(team: Team): NavModelItem {
|
||||
navModel.children.push({
|
||||
active: false,
|
||||
icon: 'fa fa-fw fa-refresh',
|
||||
id: 'team-settings',
|
||||
id: `team-groupsync-${team.id}`,
|
||||
text: 'External group sync',
|
||||
url: `org/teams/edit/${team.id}/groupsync`,
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Team, TeamGroup, TeamMember, TeamsState, TeamState } from '../../../types';
|
||||
import { Team, TeamGroup, TeamMember, TeamsState, TeamState } from 'app/types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
|
||||
export const initialTeamsState: TeamsState = { teams: [], searchQuery: '' };
|
||||
|
@ -1,14 +1,19 @@
|
||||
export const getSearchQuery = state => state.searchQuery;
|
||||
export const getSearchMemberQuery = state => state.searchMemberQuery;
|
||||
export const getTeamGroups = state => state.groups;
|
||||
import { Team, TeamsState, TeamState } from 'app/types';
|
||||
|
||||
export const getTeam = (state, currentTeamId) => {
|
||||
export const getSearchQuery = (state: TeamsState) => state.searchQuery;
|
||||
export const getSearchMemberQuery = (state: TeamState) => state.searchMemberQuery;
|
||||
export const getTeamGroups = (state: TeamState) => state.groups;
|
||||
export const getTeamsCount = (state: TeamsState) => state.teams.length;
|
||||
|
||||
export const getTeam = (state: TeamState, currentTeamId): Team | null => {
|
||||
if (state.team.id === parseInt(currentTeamId, 10)) {
|
||||
return state.team;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getTeams = state => {
|
||||
export const getTeams = (state: TeamsState) => {
|
||||
const regex = RegExp(state.searchQuery, 'i');
|
||||
|
||||
return state.teams.filter(team => {
|
||||
@ -16,7 +21,7 @@ export const getTeams = state => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getTeamMembers = state => {
|
||||
export const getTeamMembers = (state: TeamState) => {
|
||||
const regex = RegExp(state.searchMemberQuery, 'i');
|
||||
|
||||
return state.members.filter(member => {
|
||||
|
@ -4,9 +4,9 @@ import './ReactContainer';
|
||||
import ServerStats from 'app/features/admin/ServerStats';
|
||||
import AlertRuleList from 'app/features/alerting/AlertRuleList';
|
||||
import FolderPermissions from 'app/containers/ManageDashboards/FolderPermissions';
|
||||
import FolderSettingsPage from 'app/features/manage-dashboards/FolderSettingsPage';
|
||||
import TeamPages from 'app/features/teams/TeamPages';
|
||||
import TeamList from 'app/features/teams/TeamList';
|
||||
import FolderSettingsPage from 'app/features/manage-dashboards/FolderSettingsPage';
|
||||
|
||||
/** @ngInject */
|
||||
export function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { types, getEnv } from 'mobx-state-tree';
|
||||
import { NavItem } from './NavItem';
|
||||
import { Team } from '../TeamsStore/TeamsStore';
|
||||
|
||||
export const NavStore = types
|
||||
.model('NavStore', {
|
||||
@ -116,43 +115,4 @@ export const NavStore = types
|
||||
|
||||
self.main = NavItem.create(main);
|
||||
},
|
||||
|
||||
initTeamPage(team: Team, tab: string, isSyncEnabled: boolean) {
|
||||
const main = {
|
||||
img: team.avatarUrl,
|
||||
id: 'team-' + team.id,
|
||||
subTitle: 'Manage members & settings',
|
||||
url: '',
|
||||
text: team.name,
|
||||
breadcrumbs: [{ title: 'Teams', url: 'org/teams' }],
|
||||
children: [
|
||||
{
|
||||
active: tab === 'members',
|
||||
icon: 'gicon gicon-team',
|
||||
id: 'team-members',
|
||||
text: 'Members',
|
||||
url: `org/teams/edit/${team.id}/members`,
|
||||
},
|
||||
{
|
||||
active: tab === 'settings',
|
||||
icon: 'fa fa-fw fa-sliders',
|
||||
id: 'team-settings',
|
||||
text: 'Settings',
|
||||
url: `org/teams/edit/${team.id}/settings`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (isSyncEnabled) {
|
||||
main.children.splice(1, 0, {
|
||||
active: tab === 'groupsync',
|
||||
icon: 'fa fa-fw fa-refresh',
|
||||
id: 'team-settings',
|
||||
text: 'External group sync',
|
||||
url: `org/teams/edit/${team.id}/groupsync`,
|
||||
});
|
||||
}
|
||||
|
||||
self.main = NavItem.create(main);
|
||||
},
|
||||
}));
|
||||
|
@ -3,7 +3,6 @@ import { NavStore } from './../NavStore/NavStore';
|
||||
import { ViewStore } from './../ViewStore/ViewStore';
|
||||
import { FolderStore } from './../FolderStore/FolderStore';
|
||||
import { PermissionsStore } from './../PermissionsStore/PermissionsStore';
|
||||
import { TeamsStore } from './../TeamsStore/TeamsStore';
|
||||
|
||||
export const RootStore = types.model({
|
||||
nav: types.optional(NavStore, {}),
|
||||
@ -17,9 +16,6 @@ export const RootStore = types.model({
|
||||
routeParams: {},
|
||||
}),
|
||||
folder: types.optional(FolderStore, {}),
|
||||
teams: types.optional(TeamsStore, {
|
||||
map: {},
|
||||
}),
|
||||
});
|
||||
|
||||
type RootStoreType = typeof RootStore.Type;
|
||||
|
@ -1,156 +0,0 @@
|
||||
import { types, getEnv, flow } from 'mobx-state-tree';
|
||||
|
||||
export const TeamMemberModel = types.model('TeamMember', {
|
||||
userId: types.identifier(types.number),
|
||||
teamId: types.number,
|
||||
avatarUrl: types.string,
|
||||
email: types.string,
|
||||
login: types.string,
|
||||
});
|
||||
|
||||
type TeamMemberType = typeof TeamMemberModel.Type;
|
||||
export interface TeamMember extends TeamMemberType {}
|
||||
|
||||
export const TeamGroupModel = types.model('TeamGroup', {
|
||||
groupId: types.identifier(types.string),
|
||||
teamId: types.number,
|
||||
});
|
||||
|
||||
type TeamGroupType = typeof TeamGroupModel.Type;
|
||||
export interface TeamGroup extends TeamGroupType {}
|
||||
|
||||
export const TeamModel = types
|
||||
.model('Team', {
|
||||
id: types.identifier(types.number),
|
||||
name: types.string,
|
||||
avatarUrl: types.string,
|
||||
email: types.string,
|
||||
memberCount: types.number,
|
||||
search: types.optional(types.string, ''),
|
||||
members: types.optional(types.map(TeamMemberModel), {}),
|
||||
groups: types.optional(types.map(TeamGroupModel), {}),
|
||||
})
|
||||
.views(self => ({
|
||||
get filteredMembers(this: Team) {
|
||||
const members = this.members.values();
|
||||
const regex = new RegExp(self.search, 'i');
|
||||
return members.filter(member => {
|
||||
return regex.test(member.login) || regex.test(member.email);
|
||||
});
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
setName(name: string) {
|
||||
self.name = name;
|
||||
},
|
||||
|
||||
setEmail(email: string) {
|
||||
self.email = email;
|
||||
},
|
||||
|
||||
setSearchQuery(query: string) {
|
||||
self.search = query;
|
||||
},
|
||||
|
||||
update: flow(function* load() {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
|
||||
yield backendSrv.put(`/api/teams/${self.id}`, {
|
||||
name: self.name,
|
||||
email: self.email,
|
||||
});
|
||||
}),
|
||||
|
||||
loadMembers: flow(function* load() {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
const rsp = yield backendSrv.get(`/api/teams/${self.id}/members`);
|
||||
self.members.clear();
|
||||
|
||||
for (const member of rsp) {
|
||||
self.members.set(member.userId.toString(), TeamMemberModel.create(member));
|
||||
}
|
||||
}),
|
||||
|
||||
removeMember: flow(function* load(member: TeamMember) {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
yield backendSrv.delete(`/api/teams/${self.id}/members/${member.userId}`);
|
||||
// remove from store map
|
||||
self.members.delete(member.userId.toString());
|
||||
}),
|
||||
|
||||
addMember: flow(function* load(userId: number) {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
yield backendSrv.post(`/api/teams/${self.id}/members`, { userId: userId });
|
||||
}),
|
||||
|
||||
loadGroups: flow(function* load() {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
const rsp = yield backendSrv.get(`/api/teams/${self.id}/groups`);
|
||||
self.groups.clear();
|
||||
|
||||
for (const group of rsp) {
|
||||
self.groups.set(group.groupId, TeamGroupModel.create(group));
|
||||
}
|
||||
}),
|
||||
|
||||
addGroup: flow(function* load(groupId: string) {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
yield backendSrv.post(`/api/teams/${self.id}/groups`, { groupId: groupId });
|
||||
self.groups.set(
|
||||
groupId,
|
||||
TeamGroupModel.create({
|
||||
teamId: self.id,
|
||||
groupId: groupId,
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
removeGroup: flow(function* load(groupId: string) {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
yield backendSrv.delete(`/api/teams/${self.id}/groups/${groupId}`);
|
||||
self.groups.delete(groupId);
|
||||
}),
|
||||
}));
|
||||
|
||||
type TeamType = typeof TeamModel.Type;
|
||||
export interface Team extends TeamType {}
|
||||
|
||||
export const TeamsStore = types
|
||||
.model('TeamsStore', {
|
||||
map: types.map(TeamModel),
|
||||
search: types.optional(types.string, ''),
|
||||
})
|
||||
.views(self => ({
|
||||
get filteredTeams(this: any) {
|
||||
const teams = this.map.values();
|
||||
const regex = new RegExp(self.search, 'i');
|
||||
return teams.filter(team => {
|
||||
return regex.test(team.name);
|
||||
});
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
loadTeams: flow(function* load() {
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
const rsp = yield backendSrv.get('/api/teams/search/', { perpage: 50, page: 1 });
|
||||
self.map.clear();
|
||||
|
||||
for (const team of rsp.teams) {
|
||||
self.map.set(team.id.toString(), TeamModel.create(team));
|
||||
}
|
||||
}),
|
||||
|
||||
setSearchQuery(query: string) {
|
||||
self.search = query;
|
||||
},
|
||||
|
||||
loadById: flow(function* load(id: string) {
|
||||
if (self.map.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const backendSrv = getEnv(self).backendSrv;
|
||||
const team = yield backendSrv.get(`/api/teams/${id}`);
|
||||
self.map.set(id, TeamModel.create(team));
|
||||
}),
|
||||
}));
|
35
public/app/types/alerting.ts
Normal file
35
public/app/types/alerting.ts
Normal file
@ -0,0 +1,35 @@
|
||||
export interface AlertRuleDTO {
|
||||
id: number;
|
||||
dashboardId: number;
|
||||
dashboardUid: string;
|
||||
dashboardSlug: string;
|
||||
panelId: number;
|
||||
name: string;
|
||||
state: string;
|
||||
newStateDate: string;
|
||||
evalDate: string;
|
||||
evalData?: object;
|
||||
executionError: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface AlertRule {
|
||||
id: number;
|
||||
dashboardId: number;
|
||||
panelId: number;
|
||||
name: string;
|
||||
state: string;
|
||||
stateText: string;
|
||||
stateIcon: string;
|
||||
stateClass: string;
|
||||
stateAge: string;
|
||||
url: string;
|
||||
info?: string;
|
||||
executionError?: string;
|
||||
evalData?: { noData: boolean };
|
||||
}
|
||||
|
||||
export interface AlertRulesState {
|
||||
items: AlertRule[];
|
||||
searchQuery: string;
|
||||
}
|
@ -1,134 +1,28 @@
|
||||
import { Team, TeamsState, TeamState, TeamGroup, TeamMember } from './teams';
|
||||
import { AlertRuleDTO, AlertRule, AlertRulesState } from './alerting';
|
||||
import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location';
|
||||
import { NavModel, NavModelItem, NavIndex } from './navModel';
|
||||
import { FolderDTO, FolderState } from './dashboard';
|
||||
|
||||
export { FolderDTO, FolderState };
|
||||
|
||||
//
|
||||
// Location
|
||||
//
|
||||
|
||||
export interface LocationUpdate {
|
||||
path?: string;
|
||||
query?: UrlQueryMap;
|
||||
routeParams?: UrlQueryMap;
|
||||
}
|
||||
|
||||
export interface LocationState {
|
||||
url: string;
|
||||
path: string;
|
||||
query: UrlQueryMap;
|
||||
routeParams: UrlQueryMap;
|
||||
}
|
||||
|
||||
export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[];
|
||||
export type UrlQueryMap = { [s: string]: UrlQueryValue };
|
||||
|
||||
//
|
||||
// Alerting
|
||||
//
|
||||
|
||||
export interface AlertRuleApi {
|
||||
id: number;
|
||||
dashboardId: number;
|
||||
dashboardUid: string;
|
||||
dashboardSlug: string;
|
||||
panelId: number;
|
||||
name: string;
|
||||
state: string;
|
||||
newStateDate: string;
|
||||
evalDate: string;
|
||||
evalData?: object;
|
||||
executionError: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface AlertRule {
|
||||
id: number;
|
||||
dashboardId: number;
|
||||
panelId: number;
|
||||
name: string;
|
||||
state: string;
|
||||
stateText: string;
|
||||
stateIcon: string;
|
||||
stateClass: string;
|
||||
stateAge: string;
|
||||
url: string;
|
||||
info?: string;
|
||||
executionError?: string;
|
||||
evalData?: { noData: boolean };
|
||||
}
|
||||
|
||||
//
|
||||
// Teams
|
||||
//
|
||||
|
||||
export interface Team {
|
||||
id: number;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
email: string;
|
||||
memberCount: number;
|
||||
}
|
||||
|
||||
export interface TeamMember {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
avatarUrl: string;
|
||||
email: string;
|
||||
login: string;
|
||||
}
|
||||
|
||||
export interface TeamGroup {
|
||||
groupId: string;
|
||||
teamId: number;
|
||||
}
|
||||
|
||||
//
|
||||
// NavModel
|
||||
//
|
||||
|
||||
export interface NavModelItem {
|
||||
text: string;
|
||||
url: string;
|
||||
subTitle?: string;
|
||||
icon?: string;
|
||||
img?: string;
|
||||
id: string;
|
||||
active?: boolean;
|
||||
hideFromTabs?: boolean;
|
||||
divider?: boolean;
|
||||
children?: NavModelItem[];
|
||||
breadcrumbs?: Array<{ title: string; url: string }>;
|
||||
target?: string;
|
||||
parentItem?: NavModelItem;
|
||||
}
|
||||
|
||||
export interface NavModel {
|
||||
main: NavModelItem;
|
||||
node: NavModelItem;
|
||||
}
|
||||
|
||||
export type NavIndex = { [s: string]: NavModelItem };
|
||||
|
||||
//
|
||||
// Store
|
||||
//
|
||||
|
||||
export interface AlertRulesState {
|
||||
items: AlertRule[];
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
export interface TeamsState {
|
||||
teams: Team[];
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
export interface TeamState {
|
||||
team: Team;
|
||||
members: TeamMember[];
|
||||
groups: TeamGroup[];
|
||||
searchMemberQuery: string;
|
||||
}
|
||||
export {
|
||||
Team,
|
||||
TeamsState,
|
||||
TeamState,
|
||||
TeamGroup,
|
||||
TeamMember,
|
||||
AlertRuleDTO,
|
||||
AlertRule,
|
||||
AlertRulesState,
|
||||
LocationState,
|
||||
LocationUpdate,
|
||||
NavModel,
|
||||
NavModelItem,
|
||||
NavIndex,
|
||||
UrlQueryMap,
|
||||
UrlQueryValue,
|
||||
FolderDTO,
|
||||
FolderState,
|
||||
};
|
||||
|
||||
export interface StoreState {
|
||||
navIndex: NavIndex;
|
||||
@ -136,5 +30,4 @@ export interface StoreState {
|
||||
alertRules: AlertRulesState;
|
||||
teams: TeamsState;
|
||||
team: TeamState;
|
||||
folder: FolderState;
|
||||
}
|
||||
|
15
public/app/types/location.ts
Normal file
15
public/app/types/location.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export interface LocationUpdate {
|
||||
path?: string;
|
||||
query?: UrlQueryMap;
|
||||
routeParams?: UrlQueryMap;
|
||||
}
|
||||
|
||||
export interface LocationState {
|
||||
url: string;
|
||||
path: string;
|
||||
query: UrlQueryMap;
|
||||
routeParams: UrlQueryMap;
|
||||
}
|
||||
|
||||
export type UrlQueryValue = string | number | boolean | string[] | number[] | boolean[];
|
||||
export type UrlQueryMap = { [s: string]: UrlQueryValue };
|
22
public/app/types/navModel.ts
Normal file
22
public/app/types/navModel.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export interface NavModelItem {
|
||||
text: string;
|
||||
url: string;
|
||||
subTitle?: string;
|
||||
icon?: string;
|
||||
img?: string;
|
||||
id: string;
|
||||
active?: boolean;
|
||||
hideFromTabs?: boolean;
|
||||
divider?: boolean;
|
||||
children?: NavModelItem[];
|
||||
breadcrumbs?: Array<{ title: string; url: string }>;
|
||||
target?: string;
|
||||
parentItem?: NavModelItem;
|
||||
}
|
||||
|
||||
export interface NavModel {
|
||||
main: NavModelItem;
|
||||
node: NavModelItem;
|
||||
}
|
||||
|
||||
export type NavIndex = { [s: string]: NavModelItem };
|
32
public/app/types/teams.ts
Normal file
32
public/app/types/teams.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export interface Team {
|
||||
id: number;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
email: string;
|
||||
memberCount: number;
|
||||
}
|
||||
|
||||
export interface TeamMember {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
avatarUrl: string;
|
||||
email: string;
|
||||
login: string;
|
||||
}
|
||||
|
||||
export interface TeamGroup {
|
||||
groupId: string;
|
||||
teamId: number;
|
||||
}
|
||||
|
||||
export interface TeamsState {
|
||||
teams: Team[];
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
export interface TeamState {
|
||||
team: Team;
|
||||
members: TeamMember[];
|
||||
groups: TeamGroup[];
|
||||
searchMemberQuery: string;
|
||||
}
|
Loading…
Reference in New Issue
Block a user