فهرست منبع

folder permissions in redux

Torkel Ödegaard 7 سال پیش
والد
کامیت
d35eca333f

+ 142 - 0
public/app/core/components/PermissionList/AddPermission.tsx

@@ -0,0 +1,142 @@
+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;

+ 2 - 1
public/app/core/components/PermissionList/PermissionList.tsx

@@ -1,7 +1,8 @@
 import React, { PureComponent } from 'react';
 import PermissionsListItem from './PermissionListItem';
 import DisabledPermissionsListItem from './DisabledPermissionListItem';
-import { DashboardAcl, FolderInfo } from 'app/types';
+import { FolderInfo } from 'app/types';
+import { DashboardAcl } from 'app/types/acl';
 
 export interface Props {
   items: DashboardAcl[];

+ 2 - 2
public/app/core/components/PermissionList/PermissionListItem.tsx

@@ -1,7 +1,7 @@
 import React, { PureComponent } from 'react';
 import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker';
-import { dashboardPermissionLevels } from 'app/types/acl';
-import { DashboardAcl, FolderInfo, PermissionLevel } from 'app/types';
+import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
+import { FolderInfo } from 'app/types';
 
 const setClassNameHelper = inherited => {
   return inherited ? 'gf-form-disabled' : '';

+ 27 - 9
public/app/features/folders/FolderPermissions.tsx

@@ -1,17 +1,23 @@
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
 import { hot } from 'react-hot-loader';
 import { connect } from 'react-redux';
 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 PermissionsInfo from 'app/core/components/Permissions/PermissionsInfo';
-import AddPermissions from 'app/core/components/Permissions/AddPermissions';
 import SlideDown from 'app/core/components/Animations/SlideDown';
 import { getNavModel } from 'app/core/selectors/navModel';
-import { NavModel, StoreState, FolderState, DashboardAcl, PermissionLevel } from 'app/types';
-import { getFolderByUid, getFolderPermissions, updateFolderPermission, removeFolderPermission } from './state/actions';
+import { NavModel, StoreState, FolderState } from 'app/types';
+import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
+import {
+  getFolderByUid,
+  getFolderPermissions,
+  updateFolderPermission,
+  removeFolderPermission,
+  addFolderPermission,
+} from './state/actions';
 import { getLoadingNav } from './state/navModel';
 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 {
   navModel: NavModel;
@@ -21,13 +27,14 @@ export interface Props {
   getFolderPermissions: typeof getFolderPermissions;
   updateFolderPermission: typeof updateFolderPermission;
   removeFolderPermission: typeof removeFolderPermission;
+  addFolderPermission: typeof addFolderPermission;
 }
 
 export interface State {
   isAdding: boolean;
 }
 
-export class FolderPermissions extends Component<Props, State> {
+export class FolderPermissions extends PureComponent<Props, State> {
   constructor(props) {
     super(props);
 
@@ -53,6 +60,14 @@ export class FolderPermissions extends Component<Props, State> {
     this.props.updateFolderPermission(item, level);
   };
 
+  onAddPermission = (newItem: NewDashboardAclItem) => {
+    return this.props.addFolderPermission(newItem);
+  };
+
+  onCancelAddPermission = () => {
+    this.setState({ isAdding: false });
+  };
+
   render() {
     const { navModel, folder } = this.props;
     const { isAdding } = this.state;
@@ -61,8 +76,7 @@ export class FolderPermissions extends Component<Props, State> {
       return <PageHeader model={navModel} />;
     }
 
-    const dashboardId = folder.id;
-    const folderInfo = { title: folder.tile, url: folder.url, id: folder.id };
+    const folderInfo = { title: folder.title, url: folder.url, id: folder.id };
 
     return (
       <div>
@@ -78,6 +92,9 @@ export class FolderPermissions extends Component<Props, State> {
               <i className="fa fa-plus" /> Add Permission
             </button>
           </div>
+          <SlideDown in={isAdding}>
+            <AddPermission onAddPermission={this.onAddPermission} onCancel={this.onCancelAddPermission} />
+          </SlideDown>
           <PermissionList
             items={folder.permissions}
             onRemoveItem={this.onRemoveItem}
@@ -105,6 +122,7 @@ const mapDispatchToProps = {
   getFolderPermissions,
   updateFolderPermission,
   removeFolderPermission,
+  addFolderPermission,
 };
 
 export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(FolderPermissions));

+ 1 - 0
public/app/features/folders/FolderSettingsPage.test.tsx

@@ -15,6 +15,7 @@ const setup = (propOverrides?: object) => {
       url: 'url',
       hasChanged: false,
       version: 1,
+      permissions: [],
     },
     getFolderByUid: jest.fn(),
     setFolderTitle: jest.fn(),

+ 28 - 3
public/app/features/folders/state/actions.ts

@@ -1,14 +1,15 @@
 import { getBackendSrv } from 'app/core/services/backend_srv';
 import { StoreState } from 'app/types';
 import { ThunkAction } from 'redux-thunk';
+import { FolderDTO, FolderState } from 'app/types';
 import {
-  FolderDTO,
-  FolderState,
   DashboardAcl,
   DashboardAclDTO,
   PermissionLevel,
   DashboardAclUpdateDTO,
-} from 'app/types';
+  NewDashboardAclItem,
+} from 'app/types/acl';
+
 import { updateNavIndex, updateLocation } from 'app/core/actions';
 import { buildNavModel } from './navModel';
 import appEvents from 'app/core/app_events';
@@ -140,3 +141,27 @@ export function removeFolderPermission(itemToDelete: DashboardAcl): ThunkResult<
     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));
+  };
+}

+ 2 - 1
public/app/features/folders/state/reducers.ts

@@ -1,4 +1,5 @@
-import { FolderState, DashboardAcl, DashboardAclDTO } from 'app/types';
+import { FolderState } from 'app/types';
+import { DashboardAcl, DashboardAclDTO } from 'app/types/acl';
 import { Action, ActionTypes } from './actions';
 
 export const inititalState: FolderState = {

+ 27 - 0
public/app/types/acl.ts

@@ -43,12 +43,39 @@ export interface DashboardPermissionInfo {
   description: string;
 }
 
+export interface NewDashboardAclItem {
+  teamId: number;
+  userId: number;
+  role: string;
+  permission: PermissionLevel;
+  type: AclTarget;
+}
+
 export enum PermissionLevel {
   View = 1,
   Edit = 2,
   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[] = [
   { value: PermissionLevel.View, label: 'View', description: 'Can view dashboards.' },
   { value: PermissionLevel.Edit, label: 'Edit', description: 'Can add, edit and delete dashboards.' },

+ 0 - 5
public/app/types/index.ts

@@ -3,7 +3,6 @@ import { AlertRuleDTO, AlertRule, AlertRulesState } from './alerting';
 import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location';
 import { NavModel, NavModelItem, NavIndex } from './navModel';
 import { FolderDTO, FolderState, FolderInfo } from './folder';
-import { DashboardAcl, DashboardAclDTO, PermissionLevel, DashboardAclUpdateDTO } from './acl';
 
 export {
   Team,
@@ -24,10 +23,6 @@ export {
   FolderDTO,
   FolderState,
   FolderInfo,
-  DashboardAcl,
-  DashboardAclDTO,
-  DashboardAclUpdateDTO,
-  PermissionLevel,
 };
 
 export interface StoreState {