Roles and Permissions UI (#333)
* feat(ui) - roles with projectId and ui improvements * feat(ui) - roles fixes * feat(ui) - roles fixes * feat(ui) - roles menu item icon change
This commit is contained in:
parent
b90d2a25f9
commit
4eaee22d30
16 changed files with 254 additions and 96 deletions
|
|
@ -102,7 +102,7 @@ class Router extends React.Component {
|
|||
this.props.fetchTenants();
|
||||
}
|
||||
|
||||
if (!prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login() && this.state.destinationPath !== '/') {
|
||||
if (this.state.destinationPath && !prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login() && this.state.destinationPath !== '/') {
|
||||
this.props.history.push(this.state.destinationPath);
|
||||
this.setState({ destinationPath: null });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const LIMIT_WARNING = 'You have reached users limit.';
|
|||
generateInviteLink,
|
||||
fetchRoles
|
||||
})
|
||||
@withPageTitle('Users - OpenReplay Preferences')
|
||||
@withPageTitle('Team - OpenReplay Preferences')
|
||||
class ManageUsers extends React.PureComponent {
|
||||
state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Checkbox } from 'UI'
|
|||
import { connect } from 'react-redux'
|
||||
import { withRequest } from 'HOCs'
|
||||
import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config'
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
function Notifications(props) {
|
||||
const { config } = props;
|
||||
|
|
@ -42,4 +43,4 @@ function Notifications(props) {
|
|||
|
||||
export default connect(state => ({
|
||||
config: state.getIn(['config', 'options'])
|
||||
}), { fetchConfig, editConfig, saveConfig })(Notifications)
|
||||
}), { fetchConfig, editConfig, saveConfig })(withPageTitle('Notifications - OpenReplay Preferences')(Notifications));
|
||||
|
|
|
|||
|
|
@ -68,25 +68,25 @@ function PreferencesMenu({ activeTab, appearance, history, isEnterprise }) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.MANAGE_USERS }
|
||||
title="Users"
|
||||
iconName="users"
|
||||
onClick={() => setTab(CLIENT_TABS.MANAGE_USERS) }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ isEnterprise && (
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.MANAGE_ROLES }
|
||||
title="Roles"
|
||||
iconName="shield-lock"
|
||||
title="Roles & Access"
|
||||
iconName="diagram-3"
|
||||
onClick={() => setTab(CLIENT_TABS.MANAGE_ROLES) }
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.MANAGE_USERS }
|
||||
title="Team"
|
||||
iconName="users"
|
||||
onClick={() => setTab(CLIENT_TABS.MANAGE_USERS) }
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { init, edit, fetchList, remove as deleteRole, resetErrors } from 'Duck/r
|
|||
import RoleItem from './components/RoleItem'
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import { toast } from 'react-toastify';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
interface Props {
|
||||
loading: boolean
|
||||
|
|
@ -20,11 +21,12 @@ interface Props {
|
|||
account: any,
|
||||
permissionsMap: any,
|
||||
removeErrors: any,
|
||||
resetErrors: () => void
|
||||
resetErrors: () => void,
|
||||
projectsMap: any,
|
||||
}
|
||||
|
||||
function Roles(props: Props) {
|
||||
const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, removeErrors } = props
|
||||
const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, projectsMap, removeErrors } = props
|
||||
const [showModal, setShowmModal] = useState(false)
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
|
||||
|
|
@ -72,16 +74,16 @@ function Roles(props: Props) {
|
|||
<React.Fragment>
|
||||
<Loader loading={ loading }>
|
||||
<SlideModal
|
||||
title={ instance.exists() ? "Edit Role" : "Add Role" }
|
||||
title={ instance.exists() ? "Edit Role" : "Create Role" }
|
||||
size="small"
|
||||
isDisplayed={showModal }
|
||||
content={ showModal && <RoleForm closeModal={closeModal}/> }
|
||||
content={ showModal && <RoleForm closeModal={closeModal} permissionsMap={permissionsMap} deleteHandler={deleteHandler} /> }
|
||||
onClose={ closeModal }
|
||||
/>
|
||||
<div className={ stl.wrapper }>
|
||||
<div className={ cn(stl.tabHeader, 'flex items-center') }>
|
||||
<div className="flex items-center mr-auto">
|
||||
<h3 className={ cn(stl.tabTitle, "text-2xl") }>Manage Roles and Permissions</h3>
|
||||
<h3 className={ cn(stl.tabTitle, "text-2xl") }>Roles and Access</h3>
|
||||
<Popup
|
||||
trigger={
|
||||
<div>
|
||||
|
|
@ -111,11 +113,17 @@ function Roles(props: Props) {
|
|||
icon
|
||||
>
|
||||
<div className={''}>
|
||||
<div className={cn(stl.wrapper, 'flex items-start py-3 border-b px-3 pr-20')}>
|
||||
<div className="flex" style={{ width: '20%'}}>Title</div>
|
||||
<div className="flex" style={{ width: '30%'}}>Project Access</div>
|
||||
<div className="flex" style={{ width: '50%'}}>Feature Access</div>
|
||||
</div>
|
||||
{roles.map(role => (
|
||||
<RoleItem
|
||||
role={role}
|
||||
isAdmin={isAdmin}
|
||||
permissions={permissionsMap}
|
||||
projects={projectsMap}
|
||||
editHandler={editHandler}
|
||||
deleteHandler={deleteHandler}
|
||||
/>
|
||||
|
|
@ -132,14 +140,20 @@ export default connect(state => {
|
|||
const permissions = state.getIn(['roles', 'permissions'])
|
||||
const permissionsMap = {}
|
||||
permissions.forEach(p => {
|
||||
permissionsMap[p.value] = p.name
|
||||
permissionsMap[p.value] = p.text
|
||||
});
|
||||
const projects = state.getIn([ 'site', 'list' ])
|
||||
return {
|
||||
instance: state.getIn(['roles', 'instance']) || null,
|
||||
permissionsMap: permissionsMap,
|
||||
roles: state.getIn(['roles', 'list']),
|
||||
removeErrors: state.getIn(['roles', 'removeRequest', 'errors']),
|
||||
loading: state.getIn(['roles', 'fetchRequest', 'loading']),
|
||||
account: state.getIn([ 'user', 'account' ])
|
||||
account: state.getIn([ 'user', 'account' ]),
|
||||
projectsMap: projects.reduce((acc, p) => {
|
||||
acc[ p.get('id') ] = p.get('name')
|
||||
return acc
|
||||
}
|
||||
, {}),
|
||||
}
|
||||
}, { init, edit, fetchList, deleteRole, resetErrors })(Roles)
|
||||
}, { init, edit, fetchList, deleteRole, resetErrors })(withPageTitle('Roles & Access - OpenReplay Preferences')(Roles))
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react'
|
|||
import { connect } from 'react-redux'
|
||||
import stl from './roleForm.css'
|
||||
import { save, edit } from 'Duck/roles'
|
||||
import { Input, Button, Checkbox } from 'UI'
|
||||
import { Input, Button, Checkbox, Dropdown, Icon } from 'UI'
|
||||
|
||||
interface Permission {
|
||||
name: string,
|
||||
|
|
@ -16,9 +16,14 @@ interface Props {
|
|||
closeModal: (toastMessage?: string) => void,
|
||||
saving: boolean,
|
||||
permissions: Array<Permission>[]
|
||||
projectOptions: Array<any>[],
|
||||
permissionsMap: any,
|
||||
projectsMap: any,
|
||||
deleteHandler: (id: any) => Promise<void>,
|
||||
}
|
||||
|
||||
const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props) => {
|
||||
const RoleForm = (props: Props) => {
|
||||
const { role, edit, save, closeModal, saving, permissions, projectOptions, permissionsMap, projectsMap } = props
|
||||
let focusElement = useRef<any>(null)
|
||||
const _save = () => {
|
||||
save(role).then(() => {
|
||||
|
|
@ -28,13 +33,33 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
|
||||
const write = ({ target: { value, name } }) => edit({ [ name ]: value })
|
||||
|
||||
const onChangeOption = (e) => {
|
||||
const onChangePermissions = (e) => {
|
||||
const { permissions } = role
|
||||
const index = permissions.indexOf(e)
|
||||
const _perms = permissions.contains(e) ? permissions.remove(index) : permissions.push(e)
|
||||
edit({ permissions: _perms })
|
||||
}
|
||||
|
||||
const onChangeProjects = (e) => {
|
||||
const { projects } = role
|
||||
const index = projects.indexOf(e)
|
||||
const _projects = index === -1 ? projects.push(e) : projects.remove(index)
|
||||
edit({ projects: _projects })
|
||||
}
|
||||
|
||||
const writeOption = (e, { name, value }) => {
|
||||
if (name === 'permissions') {
|
||||
onChangePermissions(value)
|
||||
} else if (name === 'projects') {
|
||||
onChangeProjects(value)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAllProjects = () => {
|
||||
const { allProjects } = role
|
||||
edit({ allProjects: !allProjects })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
focusElement && focusElement.current && focusElement.current.focus()
|
||||
}, [])
|
||||
|
|
@ -42,8 +67,8 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
return (
|
||||
<div className={ stl.form }>
|
||||
<form onSubmit={ _save } >
|
||||
<div className={ stl.formGroup }>
|
||||
<label>{ 'Name' }</label>
|
||||
<div className="form-group">
|
||||
<label>{ 'Title' }</label>
|
||||
<Input
|
||||
ref={ focusElement }
|
||||
name="name"
|
||||
|
|
@ -51,22 +76,77 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
onChange={ write }
|
||||
className={ stl.input }
|
||||
id="name-field"
|
||||
placeholder="Ex. Admin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{ permissions.map((permission: any, index) => (
|
||||
<div key={ index } className={ stl.formGroup }>
|
||||
<Checkbox
|
||||
name="permissions"
|
||||
className="font-medium"
|
||||
type="checkbox"
|
||||
checked={ role.permissions.contains(permission.value) }
|
||||
onClick={ () => onChangeOption(permission.value) }
|
||||
label={permission.name}
|
||||
/>
|
||||
<div className="form-group flex flex-col">
|
||||
<label>{ 'Project Access' }</label>
|
||||
|
||||
<div className="flex my-3">
|
||||
<Checkbox
|
||||
name="allProjects"
|
||||
className="font-medium"
|
||||
type="checkbox"
|
||||
checked={ role.allProjects }
|
||||
onClick={toggleAllProjects}
|
||||
label={''}
|
||||
/>
|
||||
<div className="cursor-pointer" onClick={toggleAllProjects}>
|
||||
<div>All Projects</div>
|
||||
<span className="text-xs text-gray-600">
|
||||
(Uncheck to select specific projects)
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{ !role.allProjects && (
|
||||
<>
|
||||
<Dropdown
|
||||
search
|
||||
className="fluid"
|
||||
placeholder="Select"
|
||||
selection
|
||||
options={ projectOptions }
|
||||
name="projects"
|
||||
value={null}
|
||||
onChange={ writeOption }
|
||||
id="change-dropdown"
|
||||
selectOnBlur={false}
|
||||
selectOnNavigation={false}
|
||||
/>
|
||||
{ role.projects.size > 0 && (
|
||||
<div className="flex flex-row items-start flex-wrap mt-4">
|
||||
{ role.projects.map(p => (
|
||||
OptionLabel(projectsMap, p, onChangeProjects)
|
||||
)) }
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group flex flex-col">
|
||||
<label>{ 'Capability Access' }</label>
|
||||
<Dropdown
|
||||
search
|
||||
className="fluid"
|
||||
placeholder="Select"
|
||||
selection
|
||||
options={ permissions }
|
||||
name="permissions"
|
||||
value={null}
|
||||
onChange={ writeOption }
|
||||
id="change-dropdown"
|
||||
selectOnBlur={false}
|
||||
selectOnNavigation={false}
|
||||
/>
|
||||
{ role.permissions.size > 0 && (
|
||||
<div className="flex flex-row items-start flex-wrap mt-4">
|
||||
{ role.permissions.map(p => (
|
||||
OptionLabel(permissionsMap, p, onChangePermissions)
|
||||
)) }
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
@ -89,13 +169,50 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
{ 'Cancel' }
|
||||
</Button>
|
||||
</div>
|
||||
{ role.exists() && (
|
||||
<div>
|
||||
<Button
|
||||
data-hidden={ !role.exists() }
|
||||
onClick={ () => props.deleteHandler(role) }
|
||||
hover
|
||||
noPadding
|
||||
>
|
||||
<Icon name="trash" size="18"/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
role: state.getIn(['roles', 'instance']),
|
||||
permissions: state.getIn(['roles', 'permissions']),
|
||||
saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]),
|
||||
}), { edit, save })(RoleForm);
|
||||
export default connect(state => {
|
||||
const role = state.getIn(['roles', 'instance'])
|
||||
const projects = state.getIn([ 'site', 'list' ])
|
||||
return {
|
||||
role,
|
||||
projectOptions: projects.map(p => ({
|
||||
key: p.get('id'),
|
||||
value: p.get('id'),
|
||||
text: p.get('name'),
|
||||
disabled: role.projects.includes(p.get('id')),
|
||||
})).toJS(),
|
||||
permissions: state.getIn(['roles', 'permissions'])
|
||||
.map(({ text, value }) => ({ text, value, disabled: role.permissions.includes(value) })).toJS(),
|
||||
saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]),
|
||||
projectsMap: projects.reduce((acc, p) => {
|
||||
acc[ p.get('id') ] = p.get('name')
|
||||
return acc
|
||||
}
|
||||
, {}),
|
||||
}
|
||||
}, { edit, save })(RoleForm);
|
||||
|
||||
function OptionLabel(nameMap: any, p: any, onChangeOption: (e: any) => void) {
|
||||
return <div className="px-2 py-1 rounded bg-gray-lightest mr-2 mb-2 border flex items-center justify-between">
|
||||
<div>{nameMap[p]}</div>
|
||||
<div className="cursor-pointer ml-2" onClick={() => onChangeOption(p)}>
|
||||
<Icon name="close" size="12" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
.form {
|
||||
padding: 0 20px;
|
||||
|
||||
& .formGroup {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
& label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import React from 'react'
|
||||
import { Icon } from 'UI'
|
||||
import { Icon, Link } from 'UI'
|
||||
import stl from './roleItem.css'
|
||||
import cn from 'classnames'
|
||||
import { CLIENT_TABS, client as clientRoute } from 'App/routes';
|
||||
|
||||
function PermisionLabel({ permission }: any) {
|
||||
|
||||
function PermisionLabel({ label }: any) {
|
||||
return (
|
||||
<div className={cn(stl.label)}>{ permission }</div>
|
||||
<div className={cn(stl.label, 'mb-2')}>{ label }</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PermisionLabelLinked({ label, route }: any) {
|
||||
return (
|
||||
<Link to={route}><div className={cn(stl.label, 'mb-2 bg-active-blue color-teal')}>{ label }</div></Link>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -14,28 +22,33 @@ interface Props {
|
|||
deleteHandler?: (role: any) => void,
|
||||
editHandler?: (role: any) => void,
|
||||
permissions: any,
|
||||
isAdmin: boolean
|
||||
isAdmin: boolean,
|
||||
projects: any,
|
||||
}
|
||||
function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Props) {
|
||||
function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, projects }: Props) {
|
||||
return (
|
||||
<div className={cn(stl.wrapper)}>
|
||||
<Icon name="user-alt" size="16" marginRight="10" />
|
||||
<div className="flex items-center">
|
||||
<div className="mr-4">{ role.name }</div>
|
||||
<div className="grid grid-flow-col auto-cols-max">
|
||||
{role.permissions.map((permission: any) => (
|
||||
<PermisionLabel permission={permissions[permission]} key={permission.id} />
|
||||
// <span key={permission.id} className={cn(stl.permission)}>{ permissions[permission].name }</span>
|
||||
))}
|
||||
</div>
|
||||
<div className={cn(stl.wrapper, 'flex items-start relative py-4 hover border-b px-3 pr-20')}>
|
||||
<div className="flex" style={{ width: '20%'}}>
|
||||
<Icon name="user-alt" size="16" marginRight="10" />
|
||||
{ role.name }
|
||||
</div>
|
||||
<div className="flex items-start flex-wrap" style={{ width: '30%'}}>
|
||||
{role.allProjects ? (
|
||||
<PermisionLabelLinked label="All projects" route={clientRoute(CLIENT_TABS.SITES)}/>
|
||||
) : (
|
||||
role.projects.map(p => (
|
||||
<PermisionLabel label={projects[p]} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-start flex-wrap" style={{ width: '50%'}}>
|
||||
{role.permissions.map((permission: any) => (
|
||||
<PermisionLabel label={permissions[permission]} key={permission.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{ isAdmin && (
|
||||
<div className={ stl.actions }>
|
||||
{ !!deleteHandler &&
|
||||
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => deleteHandler(role) } id="trash">
|
||||
<Icon name="trash" size="16" color="teal"/>
|
||||
</div>
|
||||
}
|
||||
<div className={ cn(stl.actions, 'absolute right-0 top-0 bottom-0 mr-8') }>
|
||||
{ !!editHandler &&
|
||||
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => editHandler(role) }>
|
||||
<Icon name="edit" size="16" color="teal"/>
|
||||
|
|
@ -43,7 +56,6 @@ function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Pr
|
|||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-bottom: solid thin #e6e6e6;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-left: auto;
|
||||
/* margin-left: auto; */
|
||||
/* opacity: 0; */
|
||||
transition: all 0.4s;
|
||||
display: flex;
|
||||
|
|
@ -37,11 +29,11 @@
|
|||
}
|
||||
|
||||
.label {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
background-color: $gray-lightest;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
border: solid thin $gray-light;
|
||||
width: fit-content;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ const GDPR_FORM = 'GDPR_FORM';
|
|||
remove,
|
||||
fetchGDPR
|
||||
})
|
||||
@withPageTitle('Sites - OpenReplay Preferences')
|
||||
@withPageTitle('Projects - OpenReplay Preferences')
|
||||
class Sites extends React.PureComponent {
|
||||
state = {
|
||||
showTrackingCode: false,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
|
|||
import { errors as errorsRoute, isRoute } from "App/routes";
|
||||
import EventFilter from 'Components/BugFinder/EventFilter';
|
||||
import DateRange from 'Components/BugFinder/DateRange';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
import { SavedSearchList } from 'UI';
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ function getStatusLabel(status) {
|
|||
applyFilter,
|
||||
fetchSlackList,
|
||||
})
|
||||
@withPageTitle("Errors - OpenReplay")
|
||||
export default class Errors extends React.PureComponent {
|
||||
state = {
|
||||
status: UNRESOLVED,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { List, Map } from 'immutable';
|
|||
import Role from 'Types/role';
|
||||
import crudDuckGenerator from './tools/crudDuck';
|
||||
import { reduceDucks } from 'Duck/tools';
|
||||
import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
|
||||
const crudDuck = crudDuckGenerator('client/role', Role, { idKey: 'roleId' });
|
||||
export const { fetchList, init, edit, remove, } = crudDuck.actions;
|
||||
|
|
@ -11,19 +12,29 @@ const RESET_ERRORS = 'roles/RESET_ERRORS';
|
|||
const initialState = Map({
|
||||
list: List(),
|
||||
permissions: List([
|
||||
{ name: 'Session Replay', value: 'SESSION_REPLAY' },
|
||||
{ name: 'Developer Tools', value: 'DEV_TOOLS' },
|
||||
{ name: 'Errors', value: 'ERRORS' },
|
||||
{ name: 'Metrics', value: 'METRICS' },
|
||||
{ name: 'Assist (Live)', value: 'ASSIST_LIVE' },
|
||||
{ name: 'Assist (Call)', value: 'ASSIST_CALL' },
|
||||
{ text: 'Session Replay', value: 'SESSION_REPLAY' },
|
||||
{ text: 'Developer Tools', value: 'DEV_TOOLS' },
|
||||
{ text: 'Errors', value: 'ERRORS' },
|
||||
{ text: 'Metrics', value: 'METRICS' },
|
||||
{ text: 'Assist (Live)', value: 'ASSIST_LIVE' },
|
||||
{ text: 'Assist (Call)', value: 'ASSIST_CALL' },
|
||||
])
|
||||
});
|
||||
|
||||
const name = "role";
|
||||
const idKey = "roleId";
|
||||
|
||||
const updateItemInList = createListUpdater(idKey);
|
||||
const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ]
|
||||
? state.mergeIn([ "instance" ], instance)
|
||||
: state;
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case RESET_ERRORS:
|
||||
return state.setIn(['removeRequest', 'errors'], null);
|
||||
case crudDuck.actionTypes.SAVE.SUCCESS:
|
||||
return updateItemInList(updateInstance(state, action.data), action.data);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ export const forgotPassword = () => '/reset-password';
|
|||
export const CLIENT_TABS = {
|
||||
INTEGRATIONS: 'integrations',
|
||||
PROFILE: 'account',
|
||||
MANAGE_USERS: 'manage-users',
|
||||
MANAGE_ROLES: 'manage-roles',
|
||||
MANAGE_USERS: 'team',
|
||||
MANAGE_ROLES: 'roles',
|
||||
SITES: 'projects',
|
||||
CUSTOM_FIELDS: 'metadata',
|
||||
WEBHOOKS: 'webhooks',
|
||||
|
|
@ -73,7 +73,7 @@ export const client = (tab = routerClientTabString) => `/client/${ tab }`;
|
|||
export const OB_TABS = {
|
||||
INSTALLING: 'installing',
|
||||
IDENTIFY_USERS: 'identify-users',
|
||||
MANAGE_USERS: 'manage-users',
|
||||
MANAGE_USERS: 'team',
|
||||
INTEGRATIONS: 'integrations',
|
||||
};
|
||||
export const OB_DEFAULT_TAB = OB_TABS.INSTALLING;
|
||||
|
|
|
|||
|
|
@ -117,4 +117,10 @@
|
|||
.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hover {
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
}
|
||||
3
frontend/app/svg/icons/diagram-3.svg
Normal file
3
frontend/app/svg/icons/diagram-3.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-diagram-3" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5v-1zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
|
|
@ -1,18 +1,21 @@
|
|||
import Record from 'Types/Record';
|
||||
import { validateName } from 'App/validate';
|
||||
import { notEmptyString, validateName } from 'App/validate';
|
||||
import { List } from 'immutable';
|
||||
|
||||
export default Record({
|
||||
roleId: undefined,
|
||||
name: '',
|
||||
allProjects: true,
|
||||
permissions: List(),
|
||||
projects: List(),
|
||||
protected: false,
|
||||
description: ''
|
||||
description: '',
|
||||
permissionOptions: List(),
|
||||
}, {
|
||||
idKey: 'roleId',
|
||||
methods: {
|
||||
validate() {
|
||||
return validateName(this.name, { diacritics: true });
|
||||
return notEmptyString(this.name) && validateName(this.name, { diacritics: true }) && (this.allProjects || this.projects.size > 0);
|
||||
},
|
||||
toData() {
|
||||
const js = this.toJS();
|
||||
|
|
@ -21,10 +24,11 @@ export default Record({
|
|||
return js;
|
||||
},
|
||||
},
|
||||
fromJS({ permissions, ...rest }) {
|
||||
fromJS({ projects = [], permissions = [], ...rest }) {
|
||||
return {
|
||||
...rest,
|
||||
permissions: List(permissions)
|
||||
permissions: List(permissions),
|
||||
projects: List(projects),
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue