Commit d35eca33 by Torkel Ödegaard

folder permissions in redux

parent f360b618
import React, { Component } from 'react';
import { UserPicker, User } from 'app/core/components/Picker/UserPicker';
import { TeamPicker, Team } from 'app/core/components/Picker/TeamPicker';
import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker';
import {
dashboardPermissionLevels,
dashboardAclTargets,
AclTarget,
PermissionLevel,
NewDashboardAclItem,
} from 'app/types/acl';
export interface Props {
onAddPermission: (item: NewDashboardAclItem) => void;
onCancel: () => void;
}
class AddPermissions extends Component<Props, NewDashboardAclItem> {
constructor(props) {
super(props);
this.state = this.getCleanState();
}
getCleanState() {
return {
userId: 0,
teamId: 0,
role: '',
type: AclTarget.Team,
permission: PermissionLevel.View,
};
}
onTypeChanged = evt => {
this.setState({ type: evt.target.value as AclTarget });
};
onUserSelected = (user: User) => {
this.setState({
userId: user ? user.id : 0,
teamId: 0,
});
};
onTeamSelected = (team: Team) => {
this.setState({
userId: 0,
teamId: team ? team.id : 0,
});
};
onPermissionChanged = (permission: OptionWithDescription) => {
this.setState({ permission: permission.value });
};
onSubmit = async evt => {
evt.preventDefault();
await this.props.onAddPermission(this.state);
this.setState(this.getCleanState());
};
isValid() {
switch (this.state.type) {
case AclTarget.Team:
return this.state.teamId > 0;
case AclTarget.User:
return this.state.userId > 0;
}
return true;
}
render() {
const { onCancel } = this.props;
const newItem = this.state;
const pickerClassName = 'width-20';
const isValid = this.isValid();
return (
<div className="gf-form-inline cta-form">
<button className="cta-form__close btn btn-transparent" onClick={onCancel}>
<i className="fa fa-close" />
</button>
<form name="addPermission" onSubmit={this.onSubmit}>
<h5>Add Permission For</h5>
<div className="gf-form-inline">
<div className="gf-form">
<div className="gf-form-select-wrapper">
<select className="gf-form-input gf-size-auto" value={newItem.type} onChange={this.onTypeChanged}>
{dashboardAclTargets.map((option, idx) => {
return (
<option key={idx} value={option.value}>
{option.text}
</option>
);
})}
</select>
</div>
</div>
{newItem.type === AclTarget.User ? (
<div className="gf-form">
<UserPicker
onSelected={this.onUserSelected}
value={newItem.userId.toString()}
className={pickerClassName}
/>
</div>
) : null}
{newItem.type === AclTarget.Team ? (
<div className="gf-form">
<TeamPicker
onSelected={this.onTeamSelected}
value={newItem.teamId.toString()}
className={pickerClassName}
/>
</div>
) : null}
<div className="gf-form">
<DescriptionPicker
optionsWithDesc={dashboardPermissionLevels}
onSelected={this.onPermissionChanged}
value={newItem.permission}
disabled={false}
className={'gf-form-input--form-dropdown-right'}
/>
</div>
<div className="gf-form">
<button data-save-permission className="btn btn-success" type="submit" disabled={!isValid}>
Save
</button>
</div>
</div>
</form>
</div>
);
}
}
export default AddPermissions;
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PermissionsListItem from './PermissionListItem'; import PermissionsListItem from './PermissionListItem';
import DisabledPermissionsListItem from './DisabledPermissionListItem'; import DisabledPermissionsListItem from './DisabledPermissionListItem';
import { DashboardAcl, FolderInfo } from 'app/types'; import { FolderInfo } from 'app/types';
import { DashboardAcl } from 'app/types/acl';
export interface Props { export interface Props {
items: DashboardAcl[]; items: DashboardAcl[];
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker'; import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
import { dashboardPermissionLevels } from 'app/types/acl'; import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
import { DashboardAcl, FolderInfo, PermissionLevel } from 'app/types'; import { FolderInfo } from 'app/types';
const setClassNameHelper = inherited => { const setClassNameHelper = inherited => {
return inherited ? 'gf-form-disabled' : ''; return inherited ? 'gf-form-disabled' : '';
......
import React, { Component } from 'react'; import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PageHeader from 'app/core/components/PageHeader/PageHeader'; import PageHeader from 'app/core/components/PageHeader/PageHeader';
import Permissions from 'app/core/components/Permissions/Permissions';
import Tooltip from 'app/core/components/Tooltip/Tooltip'; import Tooltip from 'app/core/components/Tooltip/Tooltip';
import PermissionsInfo from 'app/core/components/Permissions/PermissionsInfo';
import AddPermissions from 'app/core/components/Permissions/AddPermissions';
import SlideDown from 'app/core/components/Animations/SlideDown'; import SlideDown from 'app/core/components/Animations/SlideDown';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { NavModel, StoreState, FolderState, DashboardAcl, PermissionLevel } from 'app/types'; import { NavModel, StoreState, FolderState } from 'app/types';
import { getFolderByUid, getFolderPermissions, updateFolderPermission, removeFolderPermission } from './state/actions'; import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
import {
getFolderByUid,
getFolderPermissions,
updateFolderPermission,
removeFolderPermission,
addFolderPermission,
} from './state/actions';
import { getLoadingNav } from './state/navModel'; import { getLoadingNav } from './state/navModel';
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 PermissionsInfo from 'app/core/components/Permissions/PermissionsInfo';
export interface Props { export interface Props {
navModel: NavModel; navModel: NavModel;
...@@ -21,13 +27,14 @@ export interface Props { ...@@ -21,13 +27,14 @@ export interface Props {
getFolderPermissions: typeof getFolderPermissions; getFolderPermissions: typeof getFolderPermissions;
updateFolderPermission: typeof updateFolderPermission; updateFolderPermission: typeof updateFolderPermission;
removeFolderPermission: typeof removeFolderPermission; removeFolderPermission: typeof removeFolderPermission;
addFolderPermission: typeof addFolderPermission;
} }
export interface State { export interface State {
isAdding: boolean; isAdding: boolean;
} }
export class FolderPermissions extends Component<Props, State> { export class FolderPermissions extends PureComponent<Props, State> {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -53,6 +60,14 @@ export class FolderPermissions extends Component<Props, State> { ...@@ -53,6 +60,14 @@ export class FolderPermissions extends Component<Props, State> {
this.props.updateFolderPermission(item, level); this.props.updateFolderPermission(item, level);
}; };
onAddPermission = (newItem: NewDashboardAclItem) => {
return this.props.addFolderPermission(newItem);
};
onCancelAddPermission = () => {
this.setState({ isAdding: false });
};
render() { render() {
const { navModel, folder } = this.props; const { navModel, folder } = this.props;
const { isAdding } = this.state; const { isAdding } = this.state;
...@@ -61,8 +76,7 @@ export class FolderPermissions extends Component<Props, State> { ...@@ -61,8 +76,7 @@ export class FolderPermissions extends Component<Props, State> {
return <PageHeader model={navModel} />; return <PageHeader model={navModel} />;
} }
const dashboardId = folder.id; const folderInfo = { title: folder.title, url: folder.url, id: folder.id };
const folderInfo = { title: folder.tile, url: folder.url, id: folder.id };
return ( return (
<div> <div>
...@@ -78,6 +92,9 @@ export class FolderPermissions extends Component<Props, State> { ...@@ -78,6 +92,9 @@ export class FolderPermissions extends Component<Props, State> {
<i className="fa fa-plus" /> Add Permission <i className="fa fa-plus" /> Add Permission
</button> </button>
</div> </div>
<SlideDown in={isAdding}>
<AddPermission onAddPermission={this.onAddPermission} onCancel={this.onCancelAddPermission} />
</SlideDown>
<PermissionList <PermissionList
items={folder.permissions} items={folder.permissions}
onRemoveItem={this.onRemoveItem} onRemoveItem={this.onRemoveItem}
...@@ -105,6 +122,7 @@ const mapDispatchToProps = { ...@@ -105,6 +122,7 @@ const mapDispatchToProps = {
getFolderPermissions, getFolderPermissions,
updateFolderPermission, updateFolderPermission,
removeFolderPermission, removeFolderPermission,
addFolderPermission,
}; };
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(FolderPermissions)); export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(FolderPermissions));
...@@ -15,6 +15,7 @@ const setup = (propOverrides?: object) => { ...@@ -15,6 +15,7 @@ const setup = (propOverrides?: object) => {
url: 'url', url: 'url',
hasChanged: false, hasChanged: false,
version: 1, version: 1,
permissions: [],
}, },
getFolderByUid: jest.fn(), getFolderByUid: jest.fn(),
setFolderTitle: jest.fn(), setFolderTitle: jest.fn(),
......
import { getBackendSrv } from 'app/core/services/backend_srv'; import { getBackendSrv } from 'app/core/services/backend_srv';
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { ThunkAction } from 'redux-thunk'; import { ThunkAction } from 'redux-thunk';
import { FolderDTO, FolderState } from 'app/types';
import { import {
FolderDTO,
FolderState,
DashboardAcl, DashboardAcl,
DashboardAclDTO, DashboardAclDTO,
PermissionLevel, PermissionLevel,
DashboardAclUpdateDTO, DashboardAclUpdateDTO,
} from 'app/types'; NewDashboardAclItem,
} from 'app/types/acl';
import { updateNavIndex, updateLocation } from 'app/core/actions'; import { updateNavIndex, updateLocation } from 'app/core/actions';
import { buildNavModel } from './navModel'; import { buildNavModel } from './navModel';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
...@@ -140,3 +141,27 @@ export function removeFolderPermission(itemToDelete: DashboardAcl): ThunkResult< ...@@ -140,3 +141,27 @@ export function removeFolderPermission(itemToDelete: DashboardAcl): ThunkResult<
await dispatch(getFolderPermissions(folder.uid)); await dispatch(getFolderPermissions(folder.uid));
}; };
} }
export function addFolderPermission(newItem: NewDashboardAclItem): ThunkResult<void> {
return async (dispatch, getStore) => {
const folder = getStore().folder;
const itemsToUpdate = [];
for (const item of folder.permissions) {
if (item.inherited) {
continue;
}
itemsToUpdate.push(toUpdateItem(item));
}
itemsToUpdate.push({
userId: newItem.userId,
teamId: newItem.teamId,
role: item.role,
permission: item.permission,
});
await getBackendSrv().post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate });
await dispatch(getFolderPermissions(folder.uid));
};
}
import { FolderState, DashboardAcl, DashboardAclDTO } from 'app/types'; import { FolderState } from 'app/types';
import { DashboardAcl, DashboardAclDTO } from 'app/types/acl';
import { Action, ActionTypes } from './actions'; import { Action, ActionTypes } from './actions';
export const inititalState: FolderState = { export const inititalState: FolderState = {
......
...@@ -43,12 +43,39 @@ export interface DashboardPermissionInfo { ...@@ -43,12 +43,39 @@ export interface DashboardPermissionInfo {
description: string; description: string;
} }
export interface NewDashboardAclItem {
teamId: number;
userId: number;
role: string;
permission: PermissionLevel;
type: AclTarget;
}
export enum PermissionLevel { export enum PermissionLevel {
View = 1, View = 1,
Edit = 2, Edit = 2,
Admin = 4, Admin = 4,
} }
export enum AclTarget {
Team = 'team',
User = 'user',
Viewer = 'viewer',
Editor = 'editor',
}
export interface AclTargetInfo {
value: AclTarget;
text: string;
}
export const dashboardAclTargets: AclTargetInfo[] = [
{ value: AclTarget.Team, text: 'Team' },
{ value: AclTarget.User, text: 'User' },
{ value: AclTarget.Viewer, text: 'Everyone With Viewer Role' },
{ value: AclTarget.Editor, text: 'Everyone With Editor Role' },
];
export const dashboardPermissionLevels: DashboardPermissionInfo[] = [ export const dashboardPermissionLevels: DashboardPermissionInfo[] = [
{ value: PermissionLevel.View, label: 'View', description: 'Can view dashboards.' }, { value: PermissionLevel.View, label: 'View', description: 'Can view dashboards.' },
{ value: PermissionLevel.Edit, label: 'Edit', description: 'Can add, edit and delete dashboards.' }, { value: PermissionLevel.Edit, label: 'Edit', description: 'Can add, edit and delete dashboards.' },
......
...@@ -3,7 +3,6 @@ import { AlertRuleDTO, AlertRule, AlertRulesState } from './alerting'; ...@@ -3,7 +3,6 @@ import { AlertRuleDTO, AlertRule, AlertRulesState } from './alerting';
import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location'; import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location';
import { NavModel, NavModelItem, NavIndex } from './navModel'; import { NavModel, NavModelItem, NavIndex } from './navModel';
import { FolderDTO, FolderState, FolderInfo } from './folder'; import { FolderDTO, FolderState, FolderInfo } from './folder';
import { DashboardAcl, DashboardAclDTO, PermissionLevel, DashboardAclUpdateDTO } from './acl';
export { export {
Team, Team,
...@@ -24,10 +23,6 @@ export { ...@@ -24,10 +23,6 @@ export {
FolderDTO, FolderDTO,
FolderState, FolderState,
FolderInfo, FolderInfo,
DashboardAcl,
DashboardAclDTO,
DashboardAclUpdateDTO,
PermissionLevel,
}; };
export interface StoreState { export interface StoreState {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment