User Permissions - initial implementation (#1046)
* initial checkin of component permissions * initial implementaiton of user permissions for devices, device groups, rules, jobs, and alarms * review feedback * fix nit in readme
This commit is contained in:
Родитель
3a4c5bb933
Коммит
5f784514e0
|
@ -28,6 +28,9 @@
|
|||
"retryFailure": "Oops, we got a temporary error from the service but were unable to recover. Try again later.",
|
||||
"unknown": "An unknown error occurred. {{message}}"
|
||||
},
|
||||
"protected": {
|
||||
"permissionDenied": "Permission {{permission}} is denied."
|
||||
},
|
||||
"settingsFlyout": {
|
||||
"title": "System settings",
|
||||
"version": "Version {{version}}",
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
import React from 'react';
|
||||
|
||||
import { IoTHubManagerService } from 'services';
|
||||
import { Btn } from 'components/shared';
|
||||
import { permissions } from 'services/models';
|
||||
import { Btn, Protected } from 'components/shared';
|
||||
import { svgs, LinkedComponent } from 'utilities';
|
||||
import Flyout from 'components/shared/flyout';
|
||||
import DeviceGroupForm from './views/deviceGroupForm';
|
||||
|
@ -69,7 +70,9 @@ export class ManageDeviceGroups extends LinkedComponent {
|
|||
this.state.addNewDeviceGroup || !!this.state.selectedDeviceGroup
|
||||
? <DeviceGroupForm {...this.props} {...this.state} cancel={this.closeForm} />
|
||||
: <div>
|
||||
<Protected permission={permissions.createDeviceGroups}>
|
||||
<Btn className="add-btn" svg={svgs.plus} onClick={this.toggleNewFilter}>{t('deviceGroupsFlyout.create')}</Btn>
|
||||
</Protected>
|
||||
{deviceGroups.length > 0 && <DeviceGroups {...this.props} onEditDeviceGroup={this.onEditDeviceGroup} />}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { permissions } from 'services/models';
|
||||
import { svgs, LinkedComponent, Validator } from 'utilities';
|
||||
import {
|
||||
AjaxError,
|
||||
|
@ -10,7 +11,8 @@ import {
|
|||
FormControl,
|
||||
FormGroup,
|
||||
FormLabel,
|
||||
Indicator
|
||||
Indicator,
|
||||
Protected
|
||||
} from 'components/shared';
|
||||
import { ConfigService } from 'services';
|
||||
import {
|
||||
|
@ -266,21 +268,25 @@ class DeviceGroupForm extends LinkedComponent {
|
|||
}
|
||||
{ this.state.isPending && <Indicator pattern="bar" size="medium" />}
|
||||
<BtnToolbar>
|
||||
<Protected permission={permissions.updateDeviceGroups}>
|
||||
<Btn
|
||||
primary
|
||||
disabled={!this.formIsValid() || conditionsHaveErrors || this.state.isPending}
|
||||
type="submit">
|
||||
{t('deviceGroupsFlyout.save')}
|
||||
</Btn>
|
||||
</Protected>
|
||||
<Btn svg={svgs.cancelX} onClick={this.props.cancel}>{t('deviceGroupsFlyout.cancel')}</Btn>
|
||||
{
|
||||
// Don't show delete btn if it is a new group or the group is currently active
|
||||
this.state.isEdit &&
|
||||
<Protected permission={permissions.deleteDeviceGroups}>
|
||||
<Btn svg={svgs.trash}
|
||||
onClick={this.deleteDeviceGroup}
|
||||
disabled={this.props.activeDeviceGroupId === this.state.id || this.state.isPending}>
|
||||
{t('deviceGroupsFlyout.conditions.delete')}
|
||||
</Btn>
|
||||
</Protected>
|
||||
}
|
||||
</BtnToolbar>
|
||||
{ this.state.error && <AjaxError t={t} error={this.state.error} /> }
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { permissions } from 'services/models';
|
||||
import { DevicesGrid } from './devicesGrid';
|
||||
import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown';
|
||||
import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn';
|
||||
import { AjaxError, Btn, RefreshBar, PageContent, ContextMenu, SearchInput } from 'components/shared';
|
||||
import {
|
||||
AjaxError,
|
||||
Btn,
|
||||
ContextMenu,
|
||||
PageContent,
|
||||
Protected,
|
||||
RefreshBar,
|
||||
SearchInput
|
||||
} from 'components/shared';
|
||||
import { DeviceNewContainer } from './flyouts/deviceNew';
|
||||
import { SIMManagementContainer } from './flyouts/SIMManagement';
|
||||
import { svgs } from 'utilities';
|
||||
|
@ -64,9 +74,15 @@ export class Devices extends Component {
|
|||
<DeviceGroupDropdown />
|
||||
<SearchInput onChange={this.searchOnChange} placeholder={t('devices.searchPlaceholder')} />
|
||||
{ this.state.contextBtns }
|
||||
<Protected permission={permissions.updateSIMManagement}>
|
||||
<Btn svg={svgs.simmanagement} onClick={this.openSIMManagement}>{t('devices.flyouts.SIMManagement.title')}</Btn>
|
||||
</Protected>
|
||||
<Protected permission={permissions.createDevices}>
|
||||
<Btn svg={svgs.plus} onClick={this.openNewDeviceFlyout}>{t('devices.flyouts.new.contextMenuName')}</Btn>
|
||||
</Protected>
|
||||
<Protected permission={permissions.updateDeviceGroups}>
|
||||
<ManageDeviceGroupsBtn />
|
||||
</Protected>
|
||||
</ContextMenu>,
|
||||
<PageContent className="devices-container" key="page-content">
|
||||
<RefreshBar refresh={fetchDevices} time={lastUpdated} isPending={isPending} t={t} />
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Btn, PcsGrid } from 'components/shared';
|
||||
import { permissions } from 'services/models';
|
||||
import { Btn, PcsGrid, Protected } from 'components/shared';
|
||||
import { deviceColumnDefs, defaultDeviceGridProps } from './devicesGridConfig';
|
||||
import { DeviceDeleteContainer } from '../flyouts/deviceDelete';
|
||||
import { DeviceJobsContainer } from '../flyouts/deviceJobs';
|
||||
|
@ -39,8 +40,12 @@ export class DevicesGrid extends Component {
|
|||
];
|
||||
|
||||
this.contextBtns = [
|
||||
<Btn key="jobs" svg={svgs.reconfigure} onClick={this.openFlyout('jobs')}>{props.t('devices.flyouts.jobs.title')}</Btn>,
|
||||
<Protected permission={permissions.createJobs}>
|
||||
<Btn key="jobs" svg={svgs.reconfigure} onClick={this.openFlyout('jobs')}>{props.t('devices.flyouts.jobs.title')}</Btn>
|
||||
</Protected>,
|
||||
<Protected permission={permissions.deleteDevices}>
|
||||
<Btn key="delete" svg={svgs.trash} onClick={this.openFlyout('delete')}>{props.t('devices.flyouts.delete.title')}</Btn>
|
||||
</Protected>
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ import React from 'react';
|
|||
import { Trans } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { permissions } from 'services/models';
|
||||
import { LinkedComponent } from 'utilities';
|
||||
import { FormControl } from 'components/shared';
|
||||
import { FormControl, Protected } from 'components/shared';
|
||||
import Flyout from 'components/shared/flyout';
|
||||
|
||||
import './SIMManagement.css';
|
||||
|
@ -47,6 +48,7 @@ export class SIMManagement extends LinkedComponent {
|
|||
<Flyout.CloseBtn onClick={onClose} />
|
||||
</Flyout.Header>
|
||||
<Flyout.Content className="sim-management-container">
|
||||
<Protected permission={permissions.updateSIMManagement}>
|
||||
<div className="sim-management-selector">
|
||||
<div className="sim-management-label-selector">{t(`devices.flyouts.SIMManagement.provider`)}</div>
|
||||
<div className="sim-management-dropdown">
|
||||
|
@ -74,6 +76,7 @@ export class SIMManagement extends LinkedComponent {
|
|||
</Section.Content>
|
||||
</Section.Container>
|
||||
}
|
||||
</Protected>
|
||||
</Flyout.Content>
|
||||
</Flyout.Container>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import update from 'immutability-helper';
|
|||
|
||||
import { IoTHubManagerService } from 'services';
|
||||
import { svgs } from 'utilities';
|
||||
import { permissions } from 'services/models';
|
||||
import {
|
||||
AjaxError,
|
||||
Btn,
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
FlyoutCloseBtn,
|
||||
FlyoutContent,
|
||||
Indicator,
|
||||
Protected,
|
||||
SectionDesc,
|
||||
SectionHeader,
|
||||
SummaryBody,
|
||||
|
@ -131,6 +133,7 @@ export class DeviceDelete extends Component {
|
|||
<FlyoutCloseBtn onClick={onClose} />
|
||||
</FlyoutHeader>
|
||||
<FlyoutContent>
|
||||
<Protected permission={permissions.deleteDevices}>
|
||||
<form className="device-delete-container" onSubmit={this.deleteDevices}>
|
||||
<div className="device-delete-header">{t('devices.flyouts.delete.header')}</div>
|
||||
<div className="device-delete-descr">{t('devices.flyouts.delete.description')}</div>
|
||||
|
@ -172,6 +175,7 @@ export class DeviceDelete extends Component {
|
|||
</BtnToolbar>
|
||||
}
|
||||
</form>
|
||||
</Protected>
|
||||
</FlyoutContent>
|
||||
</Flyout>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { LinkedComponent } from 'utilities';
|
||||
|
||||
import { permissions } from 'services/models';
|
||||
import {
|
||||
Flyout,
|
||||
FlyoutHeader,
|
||||
|
@ -13,6 +13,7 @@ import {
|
|||
ErrorMsg,
|
||||
FormGroup,
|
||||
FormLabel,
|
||||
Protected,
|
||||
Radio
|
||||
} from 'components/shared';
|
||||
import {
|
||||
|
@ -64,6 +65,7 @@ export class DeviceJobs extends LinkedComponent {
|
|||
<FlyoutCloseBtn onClick={onClose} />
|
||||
</FlyoutHeader>
|
||||
<FlyoutContent>
|
||||
<Protected permission={permissions.createJobs}>
|
||||
<div className="device-jobs-container">
|
||||
{
|
||||
devices.length === 0 &&
|
||||
|
@ -95,6 +97,7 @@ export class DeviceJobs extends LinkedComponent {
|
|||
]
|
||||
}
|
||||
</div>
|
||||
</Protected>
|
||||
</FlyoutContent>
|
||||
</Flyout>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||
import update from 'immutability-helper';
|
||||
|
||||
import { DeviceSimulationService, IoTHubManagerService } from 'services';
|
||||
import { authenticationTypeOptions, toNewDeviceRequestModel } from 'services/models';
|
||||
import { authenticationTypeOptions, permissions, toNewDeviceRequestModel } from 'services/models';
|
||||
import {
|
||||
copyToClipboard,
|
||||
int,
|
||||
|
@ -28,6 +28,7 @@ import {
|
|||
FormLabel,
|
||||
FormSection,
|
||||
Indicator,
|
||||
Protected,
|
||||
Radio,
|
||||
SectionDesc,
|
||||
SectionHeader,
|
||||
|
@ -330,6 +331,7 @@ export class DeviceNew extends LinkedComponent {
|
|||
<FlyoutCloseBtn onClick={onClose} />
|
||||
</FlyoutHeader>
|
||||
<FlyoutContent>
|
||||
<Protected permission={permissions.createDevices}>
|
||||
<form className="devices-new-container" onSubmit={this.apply}>
|
||||
<div className="devices-new-content">
|
||||
<FormGroup>
|
||||
|
@ -431,6 +433,7 @@ export class DeviceNew extends LinkedComponent {
|
|||
]
|
||||
}
|
||||
</form>
|
||||
</Protected>
|
||||
</FlyoutContent>
|
||||
</Flyout>
|
||||
);
|
||||
|
|
|
@ -5,8 +5,17 @@ import { Trans } from 'react-i18next';
|
|||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import Config from 'app.config';
|
||||
import { permissions } from 'services/models';
|
||||
import { RulesGrid } from 'components/pages/rules/rulesGrid';
|
||||
import { AjaxError, Btn, PageContent, ContextMenu, RefreshBar, Indicator } from 'components/shared';
|
||||
import {
|
||||
AjaxError,
|
||||
Btn,
|
||||
ContextMenu,
|
||||
Indicator,
|
||||
PageContent,
|
||||
Protected,
|
||||
RefreshBar
|
||||
} from 'components/shared';
|
||||
import { svgs, joinClasses, renderUndefined } from 'utilities';
|
||||
import { DevicesGrid } from 'components/pages/devices/devicesGrid';
|
||||
import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown';
|
||||
|
@ -194,15 +203,21 @@ export class RuleDetails extends Component {
|
|||
const alertContextBtns =
|
||||
selectedRows.length > 0
|
||||
? [
|
||||
<Protected permission={permissions.updateAlarms}>
|
||||
<Btn svg={svgs.closeAlert} onClick={this.closeAlerts} key="close">
|
||||
<Trans i18nKey="maintenance.close">Close</Trans>
|
||||
</Btn>,
|
||||
</Btn>
|
||||
</Protected>,
|
||||
<Protected permission={permissions.updateAlarms}>
|
||||
<Btn svg={svgs.ackAlert} onClick={this.ackAlerts} key="ack">
|
||||
<Trans i18nKey="maintenance.acknowledge">Acknowledge</Trans>
|
||||
</Btn>,
|
||||
</Btn>
|
||||
</Protected>,
|
||||
<Protected permission={permissions.deleteAlarms}>
|
||||
<Btn svg={svgs.trash} onClick={this.deleteAlerts} key="delete">
|
||||
<Trans i18nKey="maintenance.delete">Delete</Trans>
|
||||
</Btn>
|
||||
</Protected>
|
||||
]
|
||||
: null;
|
||||
this.setState({
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import React from 'react';
|
||||
import { permissions } from 'services/models';
|
||||
import { Protected, ProtectedError } from 'components/shared';
|
||||
import { RuleEditorContainer } from './ruleEditor';
|
||||
import Flyout from 'components/shared/flyout';
|
||||
|
||||
|
@ -11,7 +13,17 @@ export const EditRuleFlyout = ({ t, onClose, rule }) => (
|
|||
<Flyout.CloseBtn onClick={onClose} />
|
||||
</Flyout.Header>
|
||||
<Flyout.Content>
|
||||
<RuleEditorContainer onClose={onClose} rule={rule} />
|
||||
<Protected permission={permissions.updateRules}>{
|
||||
(hasPermission, permission) =>
|
||||
hasPermission
|
||||
? <RuleEditorContainer onClose={onClose} rule={rule} />
|
||||
:
|
||||
<div>
|
||||
<ProtectedError t={t} permission={permission} />
|
||||
<p>A read-only view will be added soon as part of another PBI.</p>
|
||||
</div>
|
||||
}
|
||||
</Protected>
|
||||
</Flyout.Content>
|
||||
</Flyout.Container>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import React from 'react';
|
||||
import { permissions } from 'services/models';
|
||||
import { Protected } from 'components/shared';
|
||||
import { RuleEditorContainer } from './ruleEditor';
|
||||
import Flyout from 'components/shared/flyout';
|
||||
|
||||
|
@ -11,7 +13,9 @@ export const NewRuleFlyout = ({ t, onClose }) => (
|
|||
<Flyout.CloseBtn onClick={onClose} />
|
||||
</Flyout.Header>
|
||||
<Flyout.Content>
|
||||
<Protected permission={permissions.createRules}>
|
||||
<RuleEditorContainer onClose={onClose} />
|
||||
</Protected>
|
||||
</Flyout.Content>
|
||||
</Flyout.Container>
|
||||
);
|
||||
|
|
|
@ -4,10 +4,12 @@ import React, { Component } from 'react';
|
|||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { permissions } from 'services/models';
|
||||
import {
|
||||
AjaxError,
|
||||
Btn,
|
||||
BtnToolbar,
|
||||
Protected,
|
||||
Svg
|
||||
} from 'components/shared';
|
||||
import { svgs } from 'utilities';
|
||||
|
@ -112,6 +114,7 @@ export class DeleteRule extends Component {
|
|||
<Flyout.CloseBtn onClick={onClose} />
|
||||
</Flyout.Header>
|
||||
<Flyout.Content>
|
||||
<Protected permission={permissions.deleteRules}>
|
||||
<form onSubmit={this.deleteRule} className="delete-rule-flyout-container">
|
||||
{rule && <RuleSummary rule={rule} isPending={isPending} completedSuccessfully={completedSuccessfully} t={t} className="rule-details"/>}
|
||||
{error && <AjaxError className="rule-delete-error" t={t} error={error} />}
|
||||
|
@ -121,6 +124,7 @@ export class DeleteRule extends Component {
|
|||
: this.renderButtons())
|
||||
}
|
||||
</form>
|
||||
</Protected>
|
||||
</Flyout.Content>
|
||||
</Flyout.Container>
|
||||
);
|
||||
|
|
|
@ -7,11 +7,12 @@ import {
|
|||
AjaxError,
|
||||
Btn,
|
||||
BtnToolbar,
|
||||
Protected,
|
||||
ToggleBtn
|
||||
} from 'components/shared';
|
||||
import { svgs } from 'utilities';
|
||||
import { TelemetryService } from 'services';
|
||||
import { toNewRuleRequestModel } from 'services/models';
|
||||
import { permissions, toNewRuleRequestModel } from 'services/models';
|
||||
import Flyout from 'components/shared/flyout';
|
||||
|
||||
import './ruleStatus.css';
|
||||
|
@ -84,6 +85,7 @@ export class RuleStatus extends Component {
|
|||
<Flyout.CloseBtn onClick={onClose} />
|
||||
</Flyout.Header>
|
||||
<Flyout.Content>
|
||||
<Protected permission={permissions.updateRules}>
|
||||
<form onSubmit={this.changeRuleStatus} className="disable-rule-flyout-container">
|
||||
<div className="padded-top-bottom">
|
||||
<ToggleBtn
|
||||
|
@ -106,6 +108,7 @@ export class RuleStatus extends Component {
|
|||
</BtnToolbar>
|
||||
}
|
||||
</form>
|
||||
</Protected>
|
||||
</Flyout.Content>
|
||||
</Flyout.Container>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { permissions } from 'services/models';
|
||||
import { RulesGrid } from './rulesGrid';
|
||||
import { DeviceGroupDropdownContainer as DeviceGroupDropdown } from 'components/app/deviceGroupDropdown';
|
||||
import { ManageDeviceGroupsBtnContainer as ManageDeviceGroupsBtn } from 'components/app/manageDeviceGroupsBtn';
|
||||
import { AjaxError, Btn, RefreshBar, PageContent, ContextMenu, SearchInput } from 'components/shared';
|
||||
import {
|
||||
AjaxError,
|
||||
Btn,
|
||||
ContextMenu,
|
||||
PageContent,
|
||||
Protected,
|
||||
RefreshBar,
|
||||
SearchInput
|
||||
} from 'components/shared';
|
||||
import { NewRuleFlyout } from './flyouts';
|
||||
import { svgs } from 'utilities';
|
||||
|
||||
|
@ -77,7 +86,9 @@ export class Rules extends Component {
|
|||
<DeviceGroupDropdown />
|
||||
<SearchInput onChange={this.searchOnChange} placeholder={t('rules.searchPlaceholder')} />
|
||||
{this.state.contextBtns}
|
||||
<Btn svg={svgs.plus} onClick={this.openNewRuleFlyout}>New rule</Btn>
|
||||
<Protected permission={permissions.createRules}>
|
||||
<Btn svg={svgs.plus} onClick={this.openNewRuleFlyout}>{t('rules.flyouts.newRule')}</Btn>
|
||||
</Protected>
|
||||
<ManageDeviceGroupsBtn />
|
||||
</ContextMenu>,
|
||||
<PageContent className="rules-container" key="page-content">
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import { Btn, PcsGrid } from 'components/shared';
|
||||
import { permissions } from 'services/models';
|
||||
import { Btn, PcsGrid, Protected } from 'components/shared';
|
||||
import { rulesColumnDefs, defaultRulesGridProps } from './rulesGridConfig';
|
||||
import { checkboxColumn } from 'components/shared/pcsGrid/pcsGridConfig';
|
||||
import { isFunc, translateColumnDefs, svgs } from 'utilities';
|
||||
|
@ -41,25 +42,35 @@ export class RulesGrid extends Component {
|
|||
|
||||
this.contextBtns = {
|
||||
disable:
|
||||
<Protected permission={permissions.updateRules}>
|
||||
<Btn key="disable" className="rule-status-btn" svg={svgs.disableToggle} onClick={this.openStatusFlyout}>
|
||||
<Trans i18nKey="rules.flyouts.disable">Disable</Trans>
|
||||
</Btn>,
|
||||
</Btn>
|
||||
</Protected>,
|
||||
enable:
|
||||
<Protected permission={permissions.updateRules}>
|
||||
<Btn key="enable" className="rule-status-btn enabled" svg={svgs.enableToggle} onClick={this.openStatusFlyout}>
|
||||
<Trans i18nKey="rules.flyouts.enable">Enable</Trans>
|
||||
</Btn>,
|
||||
</Btn>
|
||||
</Protected>,
|
||||
changeStatus:
|
||||
<Protected permission={permissions.updateRules}>
|
||||
<Btn key="changeStatus" className="rule-status-btn" svg={svgs.changeStatus} onClick={this.openStatusFlyout}>
|
||||
<Trans i18nKey="rules.flyouts.changeStatus">Change status</Trans>
|
||||
</Btn>,
|
||||
</Btn>
|
||||
</Protected>,
|
||||
edit:
|
||||
<Protected permission={permissions.updateRules}>
|
||||
<Btn key="edit" svg={svgs.edit} onClick={this.openEditRuleFlyout}>
|
||||
{props.t('rules.flyouts.edit')}
|
||||
</Btn>,
|
||||
</Btn>
|
||||
</Protected>,
|
||||
delete:
|
||||
<Protected permission={permissions.deleteRules}>
|
||||
<Btn key="delete" svg={svgs.trash} onClick={this.openDeleteFlyout}>
|
||||
<Trans i18nKey="rules.flyouts.delete">Delete</Trans>
|
||||
</Btn>
|
||||
</Protected>
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,5 +9,6 @@ export * from './forms';
|
|||
export * from './indicator/indicator';
|
||||
export * from './pageContent/pageContent';
|
||||
export * from './pcsGrid/pcsGrid';
|
||||
export * from './protected'
|
||||
export * from './refreshBar/refreshBar';
|
||||
export * from './svg/svg';
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
Protected Components
|
||||
=================================
|
||||
|
||||
These components are intended to enforce user permissions in the UI layer.
|
||||
|
||||
The individual permissions are defined with the [models for the auth microservice](../../../services/models/authModels.js).
|
||||
|
||||
|
||||
## Using the Protected component
|
||||
|
||||
React components allow you to pass functions as children instead of pure JSX. So the Protected component can have two use cases
|
||||
enabling it to be simple, declarative, highly customizable, and hide the permission logic from the rest of the app.
|
||||
|
||||
### Use case 1:
|
||||
|
||||
Pass pure JSX as the children will either render or not render the children based on the permission status.
|
||||
|
||||
<Protected permissions={permissions.deleteDevices}>
|
||||
<Btn>Protected Button</Btn>
|
||||
</Protected>
|
||||
|
||||
### Use case 2:
|
||||
|
||||
Pass a function as the children. In this case, the Protected component will pass a boolean indicating if the user has the required permission or not.
|
||||
|
||||
<Protected permissions={permissions.deleteDevices}>
|
||||
{
|
||||
(hasPermission) => hasPermission
|
||||
? <FormControl link={this.userEmails}>
|
||||
: <span>You don't have permission to edit user emails :(</span>
|
||||
}
|
||||
</Protected>
|
||||
|
||||
Additionally, a parameter containing the permission will also be passed. See below for an example of its use with the ProtectedError component.
|
||||
|
||||
## Using the optional ProtectedError component
|
||||
|
||||
If you'd like to show a default message instead of simply hiding the children, use case 2 above along with the ProtectedError component can assist.
|
||||
|
||||
<Protected permissions={permissions.deleteDevices}>
|
||||
{
|
||||
(hasPermission, permission) => hasPermission
|
||||
? <FormControl link={this.userEmails}>
|
||||
: <ProtectedError t={t} permission={permission} />
|
||||
}
|
||||
</Protected>
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
export * from './protected.container';
|
||||
export * from './protectedError';
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { getUserPermissions } from 'store/reducers/appReducer';
|
||||
import { ProtectedImpl } from './protected.impl';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
userPermissions: getUserPermissions(state)
|
||||
});
|
||||
|
||||
export const Protected = connect(mapStateToProps, null)(ProtectedImpl);
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import { Component } from 'react';
|
||||
import { isFunc } from 'utilities';
|
||||
|
||||
export class ProtectedImpl extends Component {
|
||||
userHasPermission() {
|
||||
const { permission, userPermissions } = this.props;
|
||||
return userPermissions.has(permission);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, permission } = this.props;
|
||||
const hasPermission = this.userHasPermission();
|
||||
if (isFunc(children)) {
|
||||
return children(hasPermission, permission);
|
||||
}
|
||||
return hasPermission ? children : null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ErrorMsg } from '../forms';
|
||||
import { joinClasses } from 'utilities';
|
||||
|
||||
export const ProtectedError = ({ permission, t, className }) => (
|
||||
<ErrorMsg className={joinClasses('protected-error', className)}>{t('protected.permissionDenied', { permission })}</ErrorMsg>
|
||||
);
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
export const permissions = {
|
||||
createDeviceGroups: 'CreateDeviceGroups',
|
||||
deleteDeviceGroups: 'DeleteDeviceGroups',
|
||||
updateDeviceGroups: 'UpdateDeviceGroups',
|
||||
|
||||
createDevices: 'CreateDevices',
|
||||
deleteDevices: 'DeleteDevices',
|
||||
updateDevices: 'UpdateDevices',
|
||||
|
||||
createRules: 'CreateRules',
|
||||
deleteRules: 'DeleteRules',
|
||||
updateRules: 'UpdateRules',
|
||||
|
||||
deleteAlarms: 'DeleteAlarms',
|
||||
updateAlarms: 'UpdateAlarms',
|
||||
|
||||
createJobs: 'CreateJobs',
|
||||
|
||||
updateSIMManagement: 'UpdateSIMManagement'
|
||||
};
|
|
@ -3,6 +3,7 @@
|
|||
// Exports models
|
||||
|
||||
export * from './ajaxModels';
|
||||
export * from './authModels';
|
||||
export * from './deviceSimulationModels';
|
||||
export * from './iotHubManagerModels';
|
||||
export * from './telemetryModels';
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ConfigService, GitHubService } from 'services';
|
|||
import { schema, normalize } from 'normalizr';
|
||||
import { createSelector } from 'reselect';
|
||||
import update from 'immutability-helper';
|
||||
import { permissions } from 'services/models';
|
||||
import {
|
||||
createAction,
|
||||
createReducerScenario,
|
||||
|
@ -121,7 +122,29 @@ const initialState = {
|
|||
isDefaultLogo: true,
|
||||
azureMapsKey: '',
|
||||
deviceGroupFlyoutIsOpen: false,
|
||||
timeInterval: 'PT1H'
|
||||
timeInterval: 'PT1H',
|
||||
|
||||
//TODO: Get this from the server. This is just hardcoded test data for now.
|
||||
userPermissions: new Set([
|
||||
permissions.createDeviceGroups,
|
||||
permissions.deleteDeviceGroups,
|
||||
permissions.updateDeviceGroups,
|
||||
|
||||
permissions.createDevices,
|
||||
permissions.deleteDevices,
|
||||
permissions.updateDevices,
|
||||
|
||||
permissions.createRules,
|
||||
permissions.deleteRules,
|
||||
permissions.updateRules,
|
||||
|
||||
permissions.deleteAlarms,
|
||||
permissions.updateAlarms,
|
||||
|
||||
permissions.createJobs,
|
||||
|
||||
permissions.updateSIMManagement
|
||||
])
|
||||
};
|
||||
|
||||
const updateDeviceGroupsReducer = (state, { payload, fromAction }) => {
|
||||
|
@ -247,4 +270,6 @@ export const getLogoPendingStatus = state =>
|
|||
getPending(getAppReducer(state), epics.actionTypes.fetchLogo);
|
||||
|
||||
export const getTimeInterval = state => getAppReducer(state).timeInterval;
|
||||
|
||||
export const getUserPermissions = state => getAppReducer(state).userPermissions;
|
||||
// ========================= Selectors - END
|
||||
|
|
Загрузка…
Ссылка в новой задаче