diff --git a/package.json b/package.json index 06a83ecac18..06382bd7fbf 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "typescript": "^2.6.2", "webpack": "^3.10.0", "webpack-bundle-analyzer": "^2.9.0", - "webpack-cleanup-plugin": "^0.5.1", + "webpack-cleanup-plugin": "^0.5.1", "webpack-merge": "^4.1.0", "zone.js": "^0.7.2" }, @@ -140,6 +140,7 @@ "lodash": "^4.17.4", "mobx": "^3.4.1", "mobx-react": "^4.3.5", + "mobx-react-devtools": "^4.2.15", "mobx-state-tree": "^1.3.1", "moment": "^2.18.1", "mousetrap": "^1.6.0", @@ -148,8 +149,8 @@ "react": "^16.2.0", "react-dom": "^16.2.0", "react-grid-layout": "^0.16.2", - "react-popper": "^0.7.5", "react-highlight-words": "^0.10.0", + "react-popper": "^0.7.5", "react-select": "^1.1.0", "react-sizeme": "^2.3.6", "remarkable": "^1.7.1", diff --git a/public/app/core/components/Permissions/Permissions.tsx b/public/app/core/components/Permissions/Permissions.tsx index 01b0ffc7be9..0109bd5ddf6 100644 --- a/public/app/core/components/Permissions/Permissions.tsx +++ b/public/app/core/components/Permissions/Permissions.tsx @@ -1,6 +1,10 @@ import React, { Component } from 'react'; import PermissionsList from './PermissionsList'; import _ from 'lodash'; +import DevTools from 'mobx-react-devtools'; +import { inject, observer } from 'mobx-react'; +import { Provider } from 'mobx-react'; +import { store } from 'app/stores/store'; export interface DashboardAcl { id?: number; @@ -24,12 +28,25 @@ export interface IProps { error: any; newType: any; aclTypes: any; - typeChanged: any; backendSrv: any; dashboardId: number; + permissions: any; } class Permissions extends Component { + render() { + return ( + + + + ); + } +} + +@inject('permissions') +@observer +class PermissionsInner extends Component { + // TODO Remove Inner from Name when we get access via ReactContainer dashboardId: any; meta: any; items: DashboardAcl[]; @@ -50,58 +67,34 @@ class Permissions extends Component { constructor(props) { super(props); - this.dashboardId = this.props.dashboardId; - this.backendSrv = this.props.backendSrv; + + const { dashboardId, backendSrv, permissions } = this.props; + + this.dashboardId = dashboardId; + this.backendSrv = backendSrv; this.permissionChanged = this.permissionChanged.bind(this); - console.log('this.setState', this.setState); + this.typeChanged = this.typeChanged.bind(this); + this.removeItem = this.removeItem.bind(this); + permissions.load(this.dashboardId); this.state = { - items: [], - newType: '', + newType: 'Group', canUpdate: false, error: '', }; } - componentWillMount() { - this.getAcl(this.props.dashboardId); - } - - getAcl(dashboardId: number) { - return this.backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`).then(result => { - console.log('this', this.setState); - const items = result.map(this.prepareViewModel.bind(this)); - // this.items = _.map(result, this.prepareViewModel.bind(this)); - this.setState(prevState => { - return { - ...prevState, - items: this.sortItems(items), - }; - }); - }); - } - sortItems(items) { return _.orderBy(items, ['sortRank', 'sortName'], ['desc', 'asc']); } - permissionChanged() { - this.setState(prevState => { - return { - ...prevState, - canUpdate: true, - }; - }); + permissionChanged(evt) { + // TODO } removeItem(index) { - this.setState(prevState => { - return { - ...prevState, - items: this.state.items.splice(index, 1), - canUpdate: true, - }; - }); + const { permissions } = this.props; + permissions.removeStoreItem(index); } update() { @@ -168,29 +161,91 @@ class Permissions extends Component { return item; } - // componentWillUpdate(nextProps, nextState) { - // console.log('nextProps', nextProps); - // console.log('nextState', nextState); - // } + isDuplicate(origItem, newItem) { + if (origItem.inherited) { + return false; + } - // componentWillReceiveProps(nextProps) { - // console.log('nextPropzzzz', nextProps); - // } + return ( + (origItem.role && newItem.role && origItem.role === newItem.role) || + (origItem.userId && newItem.userId && origItem.userId === newItem.userId) || + (origItem.teamId && newItem.teamId && origItem.teamId === newItem.teamId) + ); + } + + isValid(item) { + const dupe = _.find(this.items, it => { + return this.isDuplicate(it, item); + }); + + if (dupe) { + this.error = this.duplicateError; + return false; + } + + return true; + } + + addNewItem(item) { + if (!this.isValid(item)) { + return; + } + this.error = ''; + + item.dashboardId = this.dashboardId; + + let newItems = this.state.items; + newItems.push(this.prepareViewModel(item)); + + this.setState(prevState => { + return { + ...prevState, + items: this.sortItems(newItems), + canUpdate: true, + }; + }); + } + + resetNewType() { + this.setState(prevState => { + return { + newType: 'Group', + }; + }); + } + + typeChanged(evt) { + const { value } = evt.target; + this.setState(prevState => { + return { + ...prevState, + newType: value, + }; + }); + } + + typeChanged___() { + const { newType } = this.state; + if (newType === 'Viewer' || newType === 'Editor') { + this.addNewItem({ permission: 1, role: newType }); + this.resetNewType(); + this.setState(prevState => { + return { + ...prevState, + canUpdate: true, + }; + }); + } + } render() { - const { error, newType, aclTypes, typeChanged } = this.props; - - const { items, canUpdate } = this.state; - - const handleTypeChange = () => { - typeChanged(); - }; + const { error, aclTypes, permissions } = this.props; + const { newType } = this.state; return (
- asd {
- {aclTypes.map((option, idx) => { return (
{newType === 'User' ? (
+ {' '} + User picker
) : null} {newType === 'Group' ? (
+ {' '} + Team picker
) : null} @@ -243,11 +302,12 @@ class Permissions extends Component { ) : null}
-
asd3 +
); } diff --git a/public/app/core/components/Permissions/PermissionsList.tsx b/public/app/core/components/Permissions/PermissionsList.tsx index 13c208b305e..995653dd263 100644 --- a/public/app/core/components/Permissions/PermissionsList.tsx +++ b/public/app/core/components/Permissions/PermissionsList.tsx @@ -9,18 +9,8 @@ export interface IProps { } class PermissionsList extends Component { - // componentWillUpdate(nextProps, nextState) { - // console.log('nextProps', nextProps); - // console.log('nextState', nextState); - // } - - // componentWillReceiveProps(nextProps) { - // console.log('nextPropzzzz', nextProps); - // } - render() { const { permissions, permissionsOptions, removeItem, permissionChanged } = this.props; - return ( diff --git a/public/app/core/components/Permissions/PermissionsListItem.tsx b/public/app/core/components/Permissions/PermissionsListItem.tsx index 7b77f47e488..e06553e13bd 100644 --- a/public/app/core/components/Permissions/PermissionsListItem.tsx +++ b/public/app/core/components/Permissions/PermissionsListItem.tsx @@ -12,7 +12,7 @@ export default ({ item, permissionsOptions, removeItem, permissionChanged, itemI const handleChangePermission = evt => { evt.preventDefault(); - permissionChanged(); + permissionChanged(itemIndex, evt.target.value); }; return ( diff --git a/public/app/features/dashboard/acl/acl.html b/public/app/features/dashboard/acl/acl.html index c0aee4d7f7f..7631680b864 100644 --- a/public/app/features/dashboard/acl/acl.html +++ b/public/app/features/dashboard/acl/acl.html @@ -6,21 +6,6 @@ dashboardId="ctrl.dashboard.id" backendSrv="ctrl.backendSrv" /> - - - - -
diff --git a/public/app/stores/PermissionsStore/PermissionsStore.ts b/public/app/stores/PermissionsStore/PermissionsStore.ts new file mode 100644 index 00000000000..82193e92593 --- /dev/null +++ b/public/app/stores/PermissionsStore/PermissionsStore.ts @@ -0,0 +1,74 @@ +import { types, getEnv, flow } from 'mobx-state-tree'; +import { PermissionsStoreItem } from './PermissionsStoreItem'; + +export const PermissionsStore = types + .model('PermissionsStore', { + fetching: types.boolean, + canUpdate: types.boolean, + items: types.optional(types.array(PermissionsStoreItem), []), + originalItems: types.optional(types.array(PermissionsStoreItem), []), + }) + // .views(self => ({ + // canUpdate: () => { + // const itemsSnapshot = getSnapshot(self.items); + // const originalItemsSnapshot = getSnapshot(self.originalItems); + // console.log('itemsSnapshot', itemsSnapshot); + // console.log('editItemsSnapshot', originalItemsSnapshot); + // return true; + // } + // })) + .actions(self => ({ + load: flow(function* load(dashboardId: number) { + self.fetching = true; + const backendSrv = getEnv(self).backendSrv; + const res = yield backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`); + const items = prepareServerResponse(res, dashboardId); + self.items = items; + self.originalItems = items; + self.fetching = false; + }), + addStoreItem: () => { + self.canUpdate = true; + }, + removeStoreItem: idx => { + self.items.splice(idx, 1); + self.canUpdate = true; + }, + })); + +const prepareServerResponse = (response, dashboardId: number) => { + return response.map(item => { + // TODO: this.meta + // item.inherited = !this.meta.isFolder && this.dashboardId !== item.dashboardId; + item.inherited = dashboardId !== item.dashboardId; + item.sortRank = 0; + if (item.userId > 0) { + item.icon = 'fa fa-fw fa-user'; + // item.nameHtml = this.$sce.trustAsHtml(item.userLogin); + item.nameHtml = item.userLogin; + item.sortName = item.userLogin; + item.sortRank = 10; + } else if (item.teamId > 0) { + item.icon = 'fa fa-fw fa-users'; + // item.nameHtml = this.$sce.trustAsHtml(item.team); + item.nameHtml = item.team; + item.sortName = item.team; + item.sortRank = 20; + } else if (item.role) { + item.icon = 'fa fa-fw fa-street-view'; + // item.nameHtml = this.$sce.trustAsHtml(`Everyone with ${item.role} Role`); + item.nameHtml = `Everyone with ${item.role} Role`; + item.sortName = item.role; + item.sortRank = 30; + if (item.role === 'Viewer') { + item.sortRank += 1; + } + } + + if (item.inherited) { + item.sortRank += 100; + } + + return item; + }); +}; diff --git a/public/app/stores/PermissionsStore/PermissionsStoreItem.ts b/public/app/stores/PermissionsStore/PermissionsStoreItem.ts new file mode 100644 index 00000000000..1f418913086 --- /dev/null +++ b/public/app/stores/PermissionsStore/PermissionsStoreItem.ts @@ -0,0 +1,25 @@ +import { types } from 'mobx-state-tree'; + +export const PermissionsStoreItem = types + .model('PermissionsStoreItem', { + dashboardId: types.number, + id: types.number, + permission: types.number, + permissionName: types.string, + role: types.maybe(types.string), + team: types.string, + teamId: types.number, + userEmail: types.string, + userId: types.number, + userLogin: types.string, + inherited: types.maybe(types.boolean), + sortRank: types.maybe(types.number), + icon: types.maybe(types.string), + nameHtml: types.maybe(types.string), + sortName: types.maybe(types.string), + }) + .actions(self => ({ + updateRole: role => { + self.role = role; + }, + })); diff --git a/public/app/stores/RootStore/RootStore.ts b/public/app/stores/RootStore/RootStore.ts index 87709464bbc..c1da118057f 100644 --- a/public/app/stores/RootStore/RootStore.ts +++ b/public/app/stores/RootStore/RootStore.ts @@ -5,6 +5,7 @@ import { NavStore } from './../NavStore/NavStore'; import { AlertListStore } from './../AlertListStore/AlertListStore'; import { ViewStore } from './../ViewStore/ViewStore'; import { FolderStore } from './../FolderStore/FolderStore'; +import { PermissionsStore } from './../PermissionsStore/PermissionsStore'; export const RootStore = types.model({ search: types.optional(SearchStore, { @@ -17,6 +18,11 @@ export const RootStore = types.model({ alertList: types.optional(AlertListStore, { rules: [], }), + permissions: types.optional(PermissionsStore, { + fetching: false, + canUpdate: false, + items: [], + }), view: types.optional(ViewStore, { path: '', query: {}, diff --git a/yarn.lock b/yarn.lock index ce4f2bc3ab2..5bb00023d7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6467,6 +6467,10 @@ mkdirp@0.5.1, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi dependencies: minimist "0.0.8" +mobx-react-devtools@^4.2.15: + version "4.2.15" + resolved "https://registry.yarnpkg.com/mobx-react-devtools/-/mobx-react-devtools-4.2.15.tgz#881c038fb83db4dffd1e72bbaf5374d26b2fdebb" + mobx-react@^4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-4.3.5.tgz#76853f2f2ef4a6f960c374bcd9f01e875929c04c"