From d71ae7bd4df509e831fe411d88a129c50b494399 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 9 Oct 2018 16:05:40 +0200 Subject: [PATCH] Redone with DataSourcePermissions --- .../datasources/AddDataSourcePermissions.tsx | 128 ++++++++++++++++++ .../datasources/DataSourcePermissions.tsx | 78 +++++++++-- .../app/features/datasources/state/actions.ts | 39 +++++- .../features/datasources/state/reducers.ts | 6 +- public/app/types/acl.ts | 5 + public/app/types/datasources.ts | 14 ++ public/app/types/index.ts | 3 +- 7 files changed, 255 insertions(+), 18 deletions(-) create mode 100644 public/app/features/datasources/AddDataSourcePermissions.tsx diff --git a/public/app/features/datasources/AddDataSourcePermissions.tsx b/public/app/features/datasources/AddDataSourcePermissions.tsx new file mode 100644 index 00000000000..42277f761aa --- /dev/null +++ b/public/app/features/datasources/AddDataSourcePermissions.tsx @@ -0,0 +1,128 @@ +import React, { PureComponent } from 'react'; +import { UserPicker } from 'app/core/components/Picker/UserPicker'; +import { Team, TeamPicker } from 'app/core/components/Picker/TeamPicker'; +import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; +import { AclTarget, DataSourcePermissionLevel } from 'app/types/acl'; +import { User } from 'app/types'; + +export interface Props { + onAddPermission: (state) => void; + onCancel: () => void; +} + +interface State { + userId: number; + teamId: number; + type: AclTarget; + permission: DataSourcePermissionLevel; +} + +export class AddDataSourcePermissions extends PureComponent { + cleanState = () => ({ + userId: 0, + teamId: 0, + type: AclTarget.Team, + permission: DataSourcePermissionLevel.Query, + }); + + state = this.cleanState(); + + isValid() { + switch (this.state.type) { + case AclTarget.Team: + return this.state.teamId > 0; + case AclTarget.User: + return this.state.userId > 0; + } + return true; + } + + onTeamSelected = (team: Team) => { + this.setState({ teamId: team ? team.id : 0 }); + }; + + onUserSelected = (user: User) => { + this.setState({ userId: user ? user.id : 0 }); + }; + + onPermissionChanged = (permission: OptionWithDescription) => { + this.setState({ permission: permission.value }); + }; + + onTypeChanged = event => { + const type = event.target.value as AclTarget; + + this.setState({ type: type, userId: 0, teamId: 0 }); + }; + + onSubmit = async event => { + event.preventDefault(); + + await this.props.onAddPermission(this.state); + this.setState(this.cleanState()); + }; + + render() { + const { onCancel } = this.props; + const { type, teamId, userId, permission } = this.state; + + const pickerClassName = 'width-20'; + const aclTargets = [{ value: AclTarget.Team, text: 'Team' }, { value: AclTarget.User, text: 'User' }]; + const permissionLevels = [ + { value: DataSourcePermissionLevel.Query, label: 'Query', description: 'Can query data source.' }, + ]; + + return ( +
+ +
+
Add Permission For
+
+
+
+ +
+
+ {type === AclTarget.User && ( +
+ +
+ )} + + {type === AclTarget.Team && ( +
+ +
+ )} +
+ +
+
+ +
+
+
+
+ ); + } +} + +export default AddDataSourcePermissions; diff --git a/public/app/features/datasources/DataSourcePermissions.tsx b/public/app/features/datasources/DataSourcePermissions.tsx index 5a878485b42..e01a3561647 100644 --- a/public/app/features/datasources/DataSourcePermissions.tsx +++ b/public/app/features/datasources/DataSourcePermissions.tsx @@ -1,10 +1,20 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import SlideDown from '../../core/components/Animations/SlideDown'; -import AddPermissions from '../../core/components/PermissionList/AddPermission'; -import { AclTarget, AclTargetInfo } from 'app/types/acl'; +import AddDataSourcePermissions from './AddDataSourcePermissions'; +import { AclTarget } from 'app/types/acl'; +import { addDataSourcePermission, loadDataSourcePermissions, removeDataSourcePermission } from './state/actions'; +import { DashboardAcl, DataSourcePermission } from 'app/types'; +import { getRouteParamsId } from '../../core/selectors/location'; +import PermissionList from '../../core/components/PermissionList/PermissionList'; -export interface Props {} +export interface Props { + dataSourcePermissions: DataSourcePermission[]; + pageId: number; + addDataSourcePermission: typeof addDataSourcePermission; + loadDataSourcePermissions: typeof loadDataSourcePermissions; + removeDataSourcePermission: typeof removeDataSourcePermission; +} interface State { isAdding: boolean; @@ -15,13 +25,42 @@ export class DataSourcePermissions extends PureComponent { isAdding: false, }; + componentDidMount() { + this.fetchDataSourcePermissions(); + } + + async fetchDataSourcePermissions() { + const { pageId, loadDataSourcePermissions } = this.props; + + return await loadDataSourcePermissions(pageId); + } + onOpenAddPermissions = () => { this.setState({ isAdding: true, }); }; - onAddPermission = () => {}; + onAddPermission = state => { + const { pageId, addDataSourcePermission } = this.props; + const data = { + permission: state.permission, + userId: 0, + teamId: 0, + }; + + if (state.type === AclTarget.Team) { + data.teamId = state.teamId; + } else if (state.team === AclTarget.User) { + data.userId = state.userId; + } + + addDataSourcePermission(pageId, data); + }; + + onRemovePermission = (item: DashboardAcl) => { + this.props.removeDataSourcePermission(1, 1); + }; onCancelAddPermission = () => { this.setState({ @@ -30,13 +69,9 @@ export class DataSourcePermissions extends PureComponent { }; render() { + const { dataSourcePermissions } = this.props; const { isAdding } = this.state; - const dashboardAclTargets: AclTargetInfo[] = [ - { value: AclTarget.Team, text: 'Team' }, - { value: AclTarget.User, text: 'User' }, - ]; - return (
@@ -47,20 +82,33 @@ export class DataSourcePermissions extends PureComponent {
- this.onAddPermission(state)} onCancel={this.onCancelAddPermission} /> + {}} + isFetching={false} + />
); } } function mapStateToProps(state) { - return {}; + return { + pageId: getRouteParamsId(state.location), + dataSourcePermissions: state.dataSources.dataSourcePermissions, + }; } -export default connect(mapStateToProps)(DataSourcePermissions); +const mapDispatchToProps = { + addDataSourcePermission, + loadDataSourcePermissions, + removeDataSourcePermission, +}; + +export default connect(mapStateToProps, mapDispatchToProps)(DataSourcePermissions); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index bb8fce8424a..ebd7c860c32 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -5,12 +5,14 @@ import { LayoutMode } from '../../../core/components/LayoutSelector/LayoutSelect import { updateLocation, updateNavIndex, UpdateNavIndexAction } from '../../../core/actions'; import { UpdateLocationAction } from '../../../core/actions/location'; import { buildNavModel } from './navModel'; +import { DataSourcePermission } from '../../../types/datasources'; export enum ActionTypes { LoadDataSources = 'LOAD_DATA_SOURCES', LoadDataSourceTypes = 'LOAD_DATA_SOURCE_TYPES', LoadDataSource = 'LOAD_DATA_SOURCE', LoadDataSourceMeta = 'LOAD_DATA_SOURCE_META', + LoadDataSourcePermissions = 'LOAD_DATA_SOURCE_PERMISSIONS', SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY', SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE', SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY', @@ -51,6 +53,11 @@ export interface LoadDataSourceMetaAction { payload: Plugin; } +export interface LoadDataSourcePermissionsAction { + type: ActionTypes.LoadDataSourcePermissions; + payload: DataSourcePermission[]; +} + const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ type: ActionTypes.LoadDataSources, payload: dataSources, @@ -71,6 +78,13 @@ const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadDataSourceTypesAc payload: dataSourceTypes, }); +const dataSourcePermissionsLoaded = ( + dataSourcePermissions: DataSourcePermission[] +): LoadDataSourcePermissionsAction => ({ + type: ActionTypes.LoadDataSourcePermissions, + payload: dataSourcePermissions, +}); + export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({ type: ActionTypes.SetDataSourcesSearchQuery, payload: searchQuery, @@ -95,7 +109,8 @@ export type Action = | SetDataSourceTypeSearchQueryAction | LoadDataSourceAction | UpdateNavIndexAction - | LoadDataSourceMetaAction; + | LoadDataSourceMetaAction + | LoadDataSourcePermissionsAction; type ThunkResult = ThunkAction; @@ -145,6 +160,28 @@ export function loadDataSourceTypes(): ThunkResult { }; } +export function loadDataSourcePermissions(id: number): ThunkResult { + return async dispatch => { + const response = await getBackendSrv().get(`/api/datasources/${id}/permissions`); + dispatch(dataSourcePermissionsLoaded(response.permissions)); + }; +} + +export function addDataSourcePermission(id: number, data: object): ThunkResult { + return async dispatch => { + await getBackendSrv().post(`/api/datasources/${id}/permissions`, data); + + dispatch(loadDataSourcePermissions(id)); + }; +} + +export function removeDataSourcePermission(id: number, permissionId: number): ThunkResult { + return async dispatch => { + await getBackendSrv().delete(`/api/datasources/${id}/permissions/${permissionId}`); + dispatch(loadDataSourcePermissions(id)); + }; +} + export function nameExits(dataSources, name) { return ( dataSources.filter(dataSource => { diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 051966c81be..0793e03b4d8 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -1,4 +1,4 @@ -import { DataSource, DataSourcesState, Plugin } from 'app/types'; +import { DataSource, DataSourcePermission, DataSourcesState, Plugin } from 'app/types'; import { Action, ActionTypes } from './actions'; import { LayoutModes } from '../../../core/components/LayoutSelector/LayoutSelector'; @@ -11,6 +11,7 @@ const initialState: DataSourcesState = { dataSourceTypes: [] as Plugin[], dataSourceTypeSearchQuery: '', dataSourceMeta: {} as Plugin, + dataSourcePermissions: [] as DataSourcePermission[], }; export const dataSourcesReducer = (state = initialState, action: Action): DataSourcesState => { @@ -35,6 +36,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo case ActionTypes.LoadDataSourceMeta: return { ...state, dataSourceMeta: action.payload }; + + case ActionTypes.LoadDataSourcePermissions: + return { ...state, dataSourcePermissions: action.payload }; } return state; diff --git a/public/app/types/acl.ts b/public/app/types/acl.ts index fa5ace388c4..3580b72769b 100644 --- a/public/app/types/acl.ts +++ b/public/app/types/acl.ts @@ -61,6 +61,11 @@ export enum PermissionLevel { Admin = 4, } +export enum DataSourcePermissionLevel { + Query = 1, + Admin = 2, +} + export enum AclTarget { Team = 'Team', User = 'User', diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 95c754faa6b..7abd96ce27f 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -1,6 +1,19 @@ import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector'; import { Plugin } from './plugins'; +export interface DataSourcePermission { + id: number; + datasourceId: number; + userId: number; + userLogin: string; + userEmail: string; + userAvatarUrl: string; + permission: number; + permissionName: string; + created: string; + updated: string; +} + export interface DataSource { id: number; orgId: number; @@ -27,4 +40,5 @@ export interface DataSourcesState { dataSourceTypes: Plugin[]; dataSource: DataSource; dataSourceMeta: Plugin; + dataSourcePermissions: DataSourcePermission[]; } diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 26f15d582ac..f7a52ec8b6d 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -7,7 +7,7 @@ import { DashboardState } from './dashboard'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { Invitee, OrgUser, User, UsersState } from './user'; -import { DataSource, DataSourcesState } from './datasources'; +import { DataSource, DataSourcePermission, DataSourcesState } from './datasources'; import { PluginMeta, Plugin, PluginsState } from './plugins'; export { @@ -41,6 +41,7 @@ export { Plugin, PluginsState, DataSourcesState, + DataSourcePermission, Invitee, OrgUser, User,