initial setup

This commit is contained in:
Vijaykant Yadav 2024-05-07 16:51:57 +05:30
parent 4fd5666d09
commit e5c89916c9
15 changed files with 2301 additions and 6 deletions

View file

@ -38,6 +38,7 @@ import { ManageGroupPermissions } from '@/ManageGroupPermissions';
import OrganizationLogin from '@/_components/OrganizationLogin/OrganizationLogin';
import { ManageOrgVars } from '@/ManageOrgVars';
import { useAppDataStore } from '@/_stores/appDataStore';
import { ManageGroupPermissionsV2 } from '@/ManageGroupPermissionsV2/ManageGroupPermissionsV2';
const AppWrapper = (props) => {
const { isAppDarkMode } = useAppDarkMode();
@ -275,6 +276,14 @@ class AppComponent extends React.Component {
</AdminRoute>
}
/>
<Route
path="groups-v2"
element={
<AdminRoute>
<ManageGroupPermissionsV2 switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
</AdminRoute>
}
/>
<Route
path="workspace-variables"
element={<ManageOrgVars switchDarkMode={this.switchDarkMode} darkMode={darkMode} />}

View file

@ -0,0 +1,193 @@
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import ModalBase from '@/_ui/Modal';
import { AppsSelect } from '@/_ui/Modal/AppsSelect';
import Multiselect from '@/_ui/Multiselect/Multiselect';
import React from 'react';
import { OverlayTrigger } from 'react-bootstrap';
import { withTranslation } from 'react-i18next';
class ManageGranularAccessComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isEmpty: true,
showAddPermissionModal: false,
errors: {},
values: {},
customSelected: true,
selectedApps: [],
};
}
openAddPermissionModal = () => this.setState({ showAddPermissionModal: true });
clsoseAddPermissionModal = () => this.setState({ showAddPermissionModal: false });
setSelectedApps = (values) => this.setState({ selectedApps: values });
render() {
const { isEmpty, showAddPermissionModal, errors, selectedApps } = this.state;
const apps = [
{ name: 'App 1', value: 'App1', label: 'app 1' },
{ name: 'App Long name 1', value: 'App2', label: 'app long name 1' },
{ name: 'App very long name', value: 'App3', label: 'app very long name' },
{ name: 'App 4', value: 'App4', label: 'app 4' },
{ name: 'App5veryverylongname', value: 'App5veryverylongname', label: 'App5veryverylongname' },
{ name: 'App 6', value: 'App6', label: '6' },
{ name: 'App 7', value: 'App 7', label: 'app 7' },
{ name: 'App 8', value: 'App 8', label: 'app 8' },
{ name: 'App 9', value: 'App 9', label: 'app 9' },
{ name: 'App 10', value: 'App 10', label: 'app 10' },
{ name: 'App 11', value: 'App 11', label: 'app 11' },
{ name: 'App 12', value: 'App 12', label: 'app 12' },
];
return (
<div className="row granular-access-container justify-content-center">
<ModalBase
size="md"
show={showAddPermissionModal}
handleClose={this.clsoseAddPermissionModal}
className="permission-manager-modal"
title={
<div className="my-3 permission-manager-title" data-cy="modal-title">
<span className="font-weight-500">
<SolidIcon name="apps" />
</span>
<div className="tj-text-md font-weight-500" data-cy="user-email">
Add app permissions
</div>
</div>
}
confirmBtnProps={{ title: 'Add', iconLeft: 'plus' }}
darkMode={this.props.darkMode}
>
<div className="form-group mb-3">
<label className="form-label bold-text">Permission name</label>
<div className="tj-app-input">
<input
type="text"
className={'form-control'}
placeholder={'Eg. Product analytics apps'}
name="permissionName"
value={''}
/>
<span className="text-danger">{errors['permissionName']}</span>
</div>
<div className="mt-1 tj-text-xxsm">
<div data-cy="workspace-login-help-text">Permission name must be unique and max 50 characters</div>
</div>
</div>
<div className="form-group mb-3">
<label className="form-label bold-text">Permission</label>
<div className="type-container">
<div className="left-container">
<label className="form-check form-check-inline">
<input className="form-check-input" type="radio" />
<div>
<span className="form-check-label text-muted">Edit</span>
<span className="text-muted tj-text-xsm">Access to app builder</span>
</div>
</label>
</div>
<div className="right-container">
<label className="form-check form-check-inline">
<input className="form-check-input" type="radio" />
<div>
<span className="form-check-label text-muted">View</span>
<span className="text-muted tj-text-xsm">Only view deployed version of app</span>
</div>
</label>
<label className="form-check form-check-inline">
<input className={`form-check-input`} type="checkbox" />
<div>
<span className={`form-check-label faded-text`}>Hide from dashboard</span>
<span className="text-muted tj-text-xsm">App will be accessible by URL only</span>
</div>
</label>
</div>
</div>
</div>
<div className="form-group mb-3">
<label className="form-label bold-text">Resources</label>
<div className="resources-container">
<label className="form-check form-check-inline">
<input className="form-check-input" type="radio" />
<div>
<span className="form-check-label text-muted">All apps</span>
<span className="text-muted tj-text-xsm">
This will select all apps in the workspace including any new apps created
</span>
</div>
</label>
<label className="form-check form-check-inline">
<input className="form-check-input" type="radio" />
<div>
<span className="form-check-label text-muted">Custom</span>
<span className="text-muted tj-text-xsm">
Select specific applications you want to add to the group
</span>
</div>
</label>
<AppsSelect allowSelectAll={true} value={selectedApps} onChange={this.setSelectedApps} options={apps} />
</div>
</div>
</ModalBase>
{isEmpty ? (
<div className="empty-container">
<div className="icon-container">
<SolidIcon name="granularaccess" />
</div>
<p className="my-2 tj-text-md font-weight-500">No permissions added yet</p>
<p className="tj-text-xsm mb-2">
Add assets to configure granular, asset-level permissions for this user group
</p>
<OverlayTrigger
// onToggle={handleOverlayToggle}
rootClose={true}
trigger="click"
placement={'bottom'}
overlay={
<div className={`settings-card tj-text card ${this.props.darkMode && 'dark-theme'}`}>
<ButtonSolid
variant="tertiary"
iconWidth="17"
fill="var(--slate9)"
className="apps-remove-btn permission-type remove-decoration tj-text-xsm font-weight-600"
leftIcon="dashboard"
onClick={() => {
this.openAddPermissionModal();
}}
>
Apps
</ButtonSolid>
</div>
}
>
<div className={'cursor-pointer'}>
<ButtonSolid
variant="tertiary"
iconWidth="17"
fill="var(--slate9)"
className="apps-remove-btn remove-decoration tj-text-xsm font-weight-600 add-permission-btn"
leftIcon="plus"
onClick={() => {
// this.openChangeRoleModal(user);
}}
>
Add permission
</ButtonSolid>
</div>
</OverlayTrigger>
</div>
) : (
<div></div>
)}
</div>
);
}
}
export const ManageGranularAccess = withTranslation()(ManageGranularAccessComponent);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,626 @@
import React from 'react';
import { groupPermissionService } from '@/_services';
import { Tooltip } from 'react-tooltip';
import { ConfirmDialog } from '@/_components';
import { toast } from 'react-hot-toast';
import { withTranslation } from 'react-i18next';
import ErrorBoundary from '@/Editor/ErrorBoundary';
import Modal from '../HomePage/Modal';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import FolderList from '@/_ui/FolderList/FolderList';
import { Loader } from '../ManageSSO/Loader';
import Popover from 'react-bootstrap/Popover';
import SolidIcon from '@/_ui/Icon/solidIcons/index';
import ModalBase from '@/_ui/Modal';
import OverflowTooltip from '@/_components/OverflowTooltip';
import { ManageGroupPermissionResourcesV2 } from '@/ManageGroupPermissionResourcesV2';
class ManageGroupPermissionsComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
groups: [],
creatingGroup: false,
showNewGroupForm: false,
newGroupName: null,
isDeletingGroup: false,
isUpdatingGroupName: false,
showGroupDeletionConfirmation: false,
showGroupNameUpdateForm: false,
groupToBeUpdated: null,
isSaveBtnDisabled: false,
selectedGroupPermissionId: null,
selectedGroup: 'All users',
isDuplicatingGroup: false,
groupDuplicateOption: { addPermission: true, addApps: true, addUsers: true },
showDuplicateGroupModal: false,
groupToDuplicate: '',
};
}
componentDidMount() {
this.fetchGroups();
}
findCurrentGroupDetails = (data) => {
let currentUpdatedGroup = data.group_permissions.find((item) => {
return item.group == this.state.newGroupName;
});
this.setState({ selectedGroup: currentUpdatedGroup.group });
return currentUpdatedGroup.id;
};
duplicateGroup = () => {
const { groupDuplicateOption, groupToDuplicate } = this.state;
this.setState({ isDuplicatingGroup: true, creatingGroup: true });
groupPermissionService
.duplicate(groupToDuplicate, groupDuplicateOption)
.then((data) => {
this.setState({
newGroupName: data?.group,
});
this.fetchGroups('current', () => {
this.setState({
newGroupName: '',
creatingGroup: false,
selectedGroupPermissionId: data?.id,
selectedGroup: data?.group,
isDuplicatingGroup: false,
showDuplicateGroupModal: false,
groupDuplicateOption: { addPermission: true, addApps: true, addUsers: true },
});
});
toast.success('Group duplicated successfully!');
})
.catch((err) => {
this.setState({
isDuplicatingGroup: false,
groupDuplicateOption: { addPermission: true, addApps: true, addUsers: true },
});
console.error('Error occured in duplicating: ', err);
toast.error('Could not duplicate group.\nPlease try again!');
});
};
toggleShowDuplicateModal = () => {
this.setState((prevState) => ({
showDuplicateGroupModal: !prevState.showDuplicateGroupModal,
groupToDuplicate: '',
groupDuplicateOption: { addPermission: true, addApps: true, addUsers: true },
}));
};
renderPopoverContent = (props, compoParam) => {
const { groupName, id } = compoParam;
const deleteGroup = () => {
this.deleteGroup(id);
};
const duplicateGroup = () => {
this.showDuplicateDiologBox(id);
};
const isDefaultGroup = groupName == 'all_users' || groupName == 'admin';
return (
<div
{...props}
style={{
position: 'absolute',
...props.style,
}}
>
<Popover
id="popover-group-menu"
className={this.props.darkMode ? 'popover-group-menu dark-theme' : 'popover-group-menu'}
placement="bottom"
>
<Popover.Body bsPrefix="popover-body">
<div>
<Field
customClass={this.props.darkMode ? 'dark-theme' : ''}
leftIcon="copy"
leftIconWidth="20"
leftViewBox="0 0 20 20"
text={'Duplicate group'}
onClick={duplicateGroup}
/>
<Field
customClass={this.props.darkMode ? 'dark-theme' : ''}
leftIcon="delete"
leftIconWidth="18"
leftIconHeight="18"
leftViewBox="0 0 20 20"
text={'Delete group'}
tooltipId="tooltip-for-delete"
tooltipContent="Cannot delete default group"
onClick={isDefaultGroup ? {} : deleteGroup}
buttonDisable={isDefaultGroup}
darkMode={this.props.darkMode}
/>
</div>
</Popover.Body>
</Popover>
{(groupName == 'all_users' || groupName == 'admin') && (
<Tooltip
id="tooltip-for-delete"
className="tooltip"
place="left"
style={{
zIndex: 99999,
}}
show={isDefaultGroup}
/>
)}
</div>
);
};
fetchGroups = (type = 'admin', callback = () => {}) => {
this.setState({
isLoading: true,
});
groupPermissionService
.getGroups()
.then((data) => {
this.setState(
{
groups: data.group_permissions,
isLoading: false,
selectedGroupPermissionId:
type == 'admin'
? data.group_permissions[0].id
: type == 'current'
? this.findCurrentGroupDetails(data)
: data.group_permissions.at(-1).id,
},
callback
);
})
.catch(({ error }) => {
toast.error(error);
this.setState({
isLoading: false,
});
});
};
changeNewGroupName = (value) => {
this.setState({
newGroupName: value,
isSaveBtnDisabled: false,
});
if ((this.state.groupToBeUpdated && this.state.groupToBeUpdated.group === value) || !value) {
this.setState({
isSaveBtnDisabled: true,
});
}
};
humanizeifDefaultGroupName = (groupName) => {
switch (groupName) {
case 'all_users':
return 'All users';
case 'admin':
return 'Admin';
default:
return groupName;
}
};
createGroup = () => {
this.setState({ creatingGroup: true });
groupPermissionService
.create(this.state.newGroupName)
.then(() => {
this.setState({
creatingGroup: false,
showNewGroupForm: false,
newGroupName: null,
selectedGroup: this.state.newGroupName,
});
toast.success('Group has been created');
this.fetchGroups('new');
})
.catch(({ error }) => {
toast.error(error);
this.setState({
creatingGroup: false,
showNewGroupForm: true,
});
});
};
deleteGroup = (groupPermissionId) => {
this.setState({
showGroupDeletionConfirmation: true,
groupToBeDeleted: groupPermissionId,
});
};
updateGroupName = (groupPermission) => {
this.setState({
showGroupNameUpdateForm: true,
groupToBeUpdated: groupPermission,
newGroupName: groupPermission.group,
isSaveBtnDisabled: true,
});
};
cancelDeleteGroupDialog = () => {
this.setState({
isDeletingGroup: false,
groupToBeDeleted: null,
showGroupDeletionConfirmation: false,
});
};
executeGroupDeletion = () => {
this.setState({ isDeletingGroup: true });
groupPermissionService
.del(this.state.groupToBeDeleted)
.then(() => {
toast.success('Group deleted successfully');
this.fetchGroups();
this.setState({ selectedGroup: 'All users', isDeletingGroup: false });
})
.catch(({ error }) => {
toast.error(error);
})
.finally(() => {
this.cancelDeleteGroupDialog();
});
};
showDuplicateDiologBox = (id) => {
this.setState({ groupToDuplicate: id, showDuplicateGroupModal: true, isDuplicatingGroup: false });
};
executeGroupUpdation = () => {
this.setState({ isUpdatingGroupName: true, selectedGroup: this.state.newGroupName });
groupPermissionService
.update(this.state.groupToBeUpdated?.id, { name: this.state.newGroupName })
.then(() => {
toast.success('Group name updated successfully');
this.fetchGroups('current');
this.setState({
isUpdatingGroupName: false,
groupToBeUpdated: null,
showGroupNameUpdateForm: false,
});
})
.catch(({ error }) => {
toast.error(error);
this.setState({
isUpdatingGroupName: false,
});
});
};
render() {
const {
isLoading,
showNewGroupForm,
showGroupNameUpdateForm,
creatingGroup,
isUpdatingGroupName,
groups,
isDeletingGroup,
showGroupDeletionConfirmation,
showDuplicateGroupModal,
isDuplicatingGroup,
groupDuplicateOption,
} = this.state;
const { addPermission, addApps, addUsers } = groupDuplicateOption;
const allFalse = [addPermission, addApps, addUsers].every((value) => !value);
return (
<ErrorBoundary showFallback={true}>
<div className="wrapper org-users-page animation-fade">
<div className="org-users-page-container">
<ConfirmDialog
show={showGroupDeletionConfirmation}
message={'This group will be permanently deleted. Do you want to continue?'}
confirmButtonLoading={isDeletingGroup}
onConfirm={() => this.executeGroupDeletion()}
onCancel={() => this.cancelDeleteGroupDialog()}
darkMode={this.props.darkMode}
/>
<ModalBase
show={showDuplicateGroupModal}
handleConfirm={this.duplicateGroup}
handleClose={this.toggleShowDuplicateModal}
title="Duplicate group"
confirmBtnProps={{ title: 'Duplicate', disabled: allFalse }}
isLoading={isDuplicatingGroup}
cancelDisabled={isDuplicatingGroup}
data-cy="modal-title"
darkMode={this.props.darkMode}
>
<div className="tj-text" data-cy="modal-message">
Duplicate the following parts of the group
</div>
<div className="group-duplcate-modal-body">
<div className="row check-row">
<div className="col-1 ">
<input
class="form-check-input"
checked={addUsers}
type="checkbox"
onChange={() => {
this.setState((prevState) => ({
groupDuplicateOption: {
...prevState.groupDuplicateOption,
addUsers: !prevState.groupDuplicateOption.addUsers,
},
}));
}}
data-cy="users-check-input"
/>
</div>
<div className="col-11">
<div className="tj-text " data-cy="users-label">
Users
</div>
</div>
</div>
<div className="row check-row">
<div className="col-1 ">
<input
class="form-check-input"
checked={addPermission}
type="checkbox"
onChange={() => {
this.setState((prevState) => ({
groupDuplicateOption: {
...prevState.groupDuplicateOption,
addPermission: !prevState.groupDuplicateOption.addPermission,
},
}));
}}
data-cy="permissions-check-input"
/>
</div>
<div className="col-11">
<div className="tj-text " data-cy="permissions-label">
Permissions
</div>
</div>
</div>
<div className="row check-row">
<div className="col-1 ">
<input
class="form-check-input"
checked={addApps}
type="checkbox"
onChange={() => {
this.setState((prevState) => ({
groupDuplicateOption: {
...prevState.groupDuplicateOption,
addApps: !prevState.groupDuplicateOption.addApps,
},
}));
}}
data-cy="apps-check-input"
/>
</div>
<div className="col-11">
<div className="tj-text " data-cy="apps-label">
Apps
</div>
</div>
</div>
</div>
</ModalBase>
<div className="d-flex groups-btn-container">
<p className="tj-text" data-cy="page-title">
{groups?.length} Groups
</p>
{!showNewGroupForm && !showGroupNameUpdateForm && (
<ButtonSolid
className="btn btn-primary create-new-group-button"
onClick={(e) => {
e.preventDefault();
this.setState({ newGroupName: null, showNewGroupForm: true, isSaveBtnDisabled: true });
}}
data-cy="create-new-group-button"
leftIcon="plus"
isLoading={isLoading}
iconWidth="16"
fill={'#FDFDFE'}
>
{this.props.t(
'header.organization.menus.manageGroups.permissions.createNewGroup',
'Create new group'
)}
</ButtonSolid>
)}
</div>
<Modal
show={showNewGroupForm || showGroupNameUpdateForm}
closeModal={() =>
this.setState({
showNewGroupForm: false,
showGroupNameUpdateForm: false,
newGroupName: null,
})
}
title={
showGroupNameUpdateForm
? this.props.t('header.organization.menus.manageGroups.permissions.updateGroup', 'Update group')
: this.props.t('header.organization.menus.manageGroups.permissions.addNewGroup', 'Add new group')
}
>
<form
id="my-form"
onSubmit={(e) => {
e.preventDefault();
if (showNewGroupForm) {
this.createGroup();
} else {
this.executeGroupUpdation();
}
}}
>
<div className="form-group mb-3 ">
<div className="row">
<div className="col tj-app-input">
<input
type="text"
required
className="form-control"
placeholder={this.props.t(
'header.organization.menus.manageGroups.permissions.enterName',
'Enter group name'
)}
onChange={(e) => {
this.changeNewGroupName(e.target.value);
}}
value={this.state.newGroupName}
data-cy="group-name-input"
autoFocus
/>
</div>
</div>
</div>
<div className="form-footer d-flex create-group-modal-footer">
<ButtonSolid
onClick={() =>
this.setState({
showNewGroupForm: false,
showGroupNameUpdateForm: false,
newGroupName: null,
})
}
disabled={creatingGroup}
data-cy="cancel-button"
variant="tertiary"
>
{this.props.t('globals.cancel', 'Cancel')}
</ButtonSolid>
<ButtonSolid
type="submit"
id="my-form"
disabled={creatingGroup || this.state.isSaveBtnDisabled}
data-cy="create-group-button"
isLoading={creatingGroup || isUpdatingGroupName}
leftIcon="plus"
fill={creatingGroup || this.state.isSaveBtnDisabled ? '#4C5155' : '#FDFDFE'}
>
{showGroupNameUpdateForm
? this.props.t('globals.save', 'Save')
: this.props.t('header.organization.menus.manageGroups.permissions.createGroup', 'Create Group')}
</ButtonSolid>
</div>
</form>
</Modal>
{!showNewGroupForm && !showGroupNameUpdateForm && (
<div className="org-users-page-card-wrap">
<div className="org-users-page-sidebar">
<div className="mb-2 d-flex align-items-center">
<SolidIcon name="usergear" />
<span className="ml-1">USER ROLE</span>
</div>
{groups.map((permissionGroup) => {
return (
<FolderList
key={permissionGroup.id}
listId={permissionGroup.id}
overlayFunctionParam={{
id: permissionGroup.id,
groupName: permissionGroup.group,
}}
selectedItem={
this.state.selectedGroup == this.humanizeifDefaultGroupName(permissionGroup.group)
}
onClick={() => {
this.setState({
selectedGroupPermissionId: permissionGroup.id,
selectedGroup: this.humanizeifDefaultGroupName(permissionGroup.group),
});
}}
toolTipText={this.humanizeifDefaultGroupName(permissionGroup.group)}
overLayComponent={this.renderPopoverContent}
className="groups-folder-list"
dataCy={this.humanizeifDefaultGroupName(permissionGroup.group)
.toLowerCase()
.replace(/\s+/g, '-')}
>
<span>
<OverflowTooltip>{this.humanizeifDefaultGroupName(permissionGroup.group)}</OverflowTooltip>
</span>
</FolderList>
);
})}
</div>
<div className="org-users-page-card-body">
{isLoading ? (
<Loader />
) : (
<ManageGroupPermissionResourcesV2
groupPermissionId={this.state.selectedGroupPermissionId}
darkMode={this.props.darkMode}
selectedGroup={this.state.selectedGroup}
updateGroupName={this.updateGroupName}
deleteGroup={this.deleteGroup}
/>
)}
</div>
</div>
)}
</div>
</div>
</ErrorBoundary>
);
}
}
export const ManageGroupPermissionsV2 = withTranslation()(ManageGroupPermissionsComponent);
const Field = ({
text,
onClick,
customClass,
leftIcon,
leftIconWidth,
leftIconHeight = '18',
leftIconClassName,
buttonDisable = false,
tooltipContent = '',
tooltipId = '',
darkMode = false,
}) => {
return (
<div className={`field ${customClass ? ` ${customClass}` : ''}`}>
<span
className="row option-row"
role="button"
onClick={!buttonDisable && onClick}
data-cy={`${text.toLowerCase().replace(/\s+/g, '-')}-card-option`}
data-tooltip-content={tooltipContent}
data-tooltip-id={tooltipId}
>
<div className={`col-2 ${leftIconClassName}`}>
{leftIcon && (
<SolidIcon
name={leftIcon}
width={leftIconWidth}
height={leftIconHeight}
{...(buttonDisable ? { fill: '#D7DBDF' } : {})}
></SolidIcon>
)}
</div>
<div className={`col ${buttonDisable ? 'disable' : ''} ${darkMode ? 'dark-theme' : ''}`}>{text}</div>
</span>
</div>
);
};

View file

@ -17,13 +17,15 @@ export function OrganizationSettings(props) {
const { updateSidebarNAV } = useContext(BreadCrumbContext);
const { workspaceId } = useParams();
const sideBarNavs = ['Users', 'Groups', 'Workspace login', 'Workspace variables'];
const sideBarNavs = ['Users', 'Groups', 'Groups V2', 'Workspace login', 'Workspace variables'];
const defaultOrgName = (groupName) => {
switch (groupName) {
case 'users':
return 'Users';
case 'groups':
return 'Groups';
case 'groups2':
return 'Groups V2';
case 'workspace-login':
return 'Workspace login';
case 'workspace-variables':

View file

@ -19,7 +19,7 @@ function MultiSelectUser({
const listOfOptions = useRef([]);
useEffect(() => {
setOptions(filterOptions(listOfOptions.current));
setOptions(listOfOptions.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedValues, listOfOptions.current]);
@ -68,7 +68,7 @@ function MultiSelectUser({
);
return (
<div className="tj-ms tj-ms-count">
<FilterPreview text={`${selectedValues.length} selected`} onClose={selectedValues.length ? onReset : undefined} />
{/* <FilterPreview text={`${selectedValues.length} selected`} onClose={selectedValues.length ? onReset : undefined} /> */}
<Select
className={className}
getOptions={onSearch ? searchFunction : undefined}

View file

@ -0,0 +1,137 @@
import config from 'config';
import { authHeader, handleResponse } from '@/_helpers';
export const groupPermissionV2Service = {
// create,
// update,
// del,
// getGroup,
getGroups,
// getAppsInGroup,
// getAppsNotInGroup,
// getUsersInGroup,
// getUsersNotInGroup,
// updateAppGroupPermission,
// duplicate,
};
function create(group) {
const body = {
group,
};
const requestOptions = {
method: 'POST',
headers: authHeader(),
credentials: 'include',
body: JSON.stringify(body),
};
return fetch(`${config.apiUrl}/group_permissions`, requestOptions).then(handleResponse);
}
function update(groupPermissionId, body) {
const requestOptions = {
method: 'PUT',
headers: authHeader(),
credentials: 'include',
body: JSON.stringify(body),
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}`, requestOptions).then(handleResponse);
}
function del(groupPermissionId) {
const requestOptions = {
method: 'DELETE',
headers: authHeader(),
credentials: 'include',
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}`, requestOptions).then(handleResponse);
}
function getGroup(groupPermissionId) {
const requestOptions = {
method: 'GET',
headers: authHeader(),
credentials: 'include',
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}`, requestOptions).then(handleResponse);
}
function getGroups() {
const requestOptions = {
method: 'GET',
headers: authHeader(),
credentials: 'include',
};
return fetch(`${config.apiUrl}/v2/group_permissions`, requestOptions).then(handleResponse);
}
function getAppsInGroup(groupPermissionId) {
const requestOptions = {
method: 'GET',
headers: authHeader(),
credentials: 'include',
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}/apps`, requestOptions).then(handleResponse);
}
function getAppsNotInGroup(groupPermissionId) {
const requestOptions = {
method: 'GET',
headers: authHeader(),
credentials: 'include',
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}/addable_apps`, requestOptions).then(
handleResponse
);
}
function getUsersInGroup(groupPermissionId) {
const requestOptions = {
method: 'GET',
headers: authHeader(),
credentials: 'include',
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}/users`, requestOptions).then(handleResponse);
}
function getUsersNotInGroup(searchInput, groupPermissionId) {
const requestOptions = {
method: 'GET',
headers: authHeader(),
credentials: 'include',
};
return fetch(
`${config.apiUrl}/group_permissions/${groupPermissionId}/addable_users?input=${searchInput.trim()}`,
requestOptions
).then(handleResponse);
}
function updateAppGroupPermission(groupPermissionId, appGroupPermissionId, actions) {
const body = {
actions,
};
const requestOptions = {
method: 'PUT',
headers: authHeader(),
credentials: 'include',
body: JSON.stringify(body),
};
return fetch(
`${config.apiUrl}/group_permissions/${groupPermissionId}/app_group_permissions/${appGroupPermissionId}`,
requestOptions
).then(handleResponse);
}
function duplicate(groupPermissionId, body) {
const requestOptions = {
method: 'POST',
headers: authHeader(),
credentials: 'include',
body: JSON.stringify(body),
};
return fetch(`${config.apiUrl}/group_permissions/${groupPermissionId}/duplicate`, requestOptions).then(
handleResponse
);
}

View file

@ -22,3 +22,4 @@ export * from './globalDatasource.service';
export * from './app_environment.service';
export * from './copilot.service';
export * from './organization_constants.service';
export * from './groupPermission.v2.service';

View file

@ -0,0 +1,57 @@
@import "./typography.scss";
@import "./designtheme.scss";
.granular-access-container {
.empty-container {
height: fit-content;
display: flex;
align-items: center;
text-align: center;
flex-direction: column;
margin-top: 10rem;
width: 330px;
.icon-container {
width: 50px;
height: 50px;
background: var(--indigo4);
svg {
width: 45px;
height: 45px;
path {
fill: var(--indigo9);
}
}
}
.add-permission-btn {
width: 135px;
}
}
}
.permission-type {
border: 0px !important;
width: 100% !important;
justify-content: flex-start;
padding-left: 1rem;
}
.permission-manager-modal {
.permission-manager-title {
display: flex;
align-items: center;
gap: 5px;
}
.type-container {
display: flex;
justify-content: space-between;
.right-container {
display: flex;
flex-direction: column;
}
}
}

View file

@ -12,6 +12,7 @@
@import "./ui-operations.scss";
@import 'react-loading-skeleton/dist/skeleton.css';
@import './table-component.scss';
@import './groups-permissions.scss';
/* ibm-plex-sans-100 - latin */
@font-face {
@ -9571,8 +9572,14 @@ tbody {
display: flex;
align-items: center;
justify-content: center;
background: var(--grass3);
background: var(--indigo3);
border-radius: 100px;
border: 2px solid var(--indigo9);
color: var(--indigo9);
path {
fill: var(--indigo9);
}
}
.sso-icon-wrapper {
@ -12860,4 +12867,27 @@ tbody {
&:active {
background-color: var(--interactive-overlays-fill-pressed) !important;
}
}
.modal-base {
.modal-footer {
border-top: 1px solid var(--slate5);
padding: 1rem;
.tj-btn-left-icon {
svg {
width: 20px;
height: 20px;
path {
fill: var(--indigo1);
}
}
}
.tj-large-btn {
font-weight: 500;
font-size: 14px;
}
}
}

View file

@ -0,0 +1,19 @@
import React from 'react';
const GranularAccess = ({ fill = '#C1C8CD', width = '17', className = '', viewBox = '0 0 17 17' }) => (
<svg
width={width}
height={width}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M9.95117 13.7257V13.3591C9.95117 12.8591 10.1956 12.4619 10.6845 12.1674C11.1734 11.873 11.8178 11.7257 12.6178 11.7257C13.4178 11.7257 14.0623 11.873 14.5512 12.1674C15.0401 12.4619 15.2845 12.8591 15.2845 13.3591V13.7257H9.95117ZM12.6178 11.0591C12.2512 11.0591 11.9373 10.9285 11.6762 10.6674C11.4151 10.4063 11.2845 10.0924 11.2845 9.72575C11.2845 9.35908 11.4151 9.04519 11.6762 8.78408C11.9373 8.52297 12.2512 8.39242 12.6178 8.39242C12.9845 8.39242 13.2984 8.52297 13.5595 8.78408C13.8206 9.04519 13.9512 9.35908 13.9512 9.72575C13.9512 10.0924 13.8206 10.4063 13.5595 10.6674C13.2984 10.9285 12.9845 11.0591 12.6178 11.0591ZM3.28451 13.7257C2.91784 13.7257 2.60395 13.5952 2.34284 13.3341C2.08173 13.073 1.95117 12.7591 1.95117 12.3924V4.39242C1.95117 4.02575 2.08173 3.71186 2.34284 3.45075C2.60395 3.18964 2.91784 3.05908 3.28451 3.05908H6.73451C6.91228 3.05908 7.08173 3.09242 7.24284 3.15908C7.40395 3.22575 7.54562 3.32019 7.66784 3.44242L8.61784 4.39242H13.9512C14.3178 4.39242 14.6317 4.52297 14.8928 4.78408C15.154 5.04519 15.2845 5.35908 15.2845 5.72575V7.90908C14.8956 7.6313 14.4734 7.42019 14.0178 7.27575C13.5623 7.1313 13.0901 7.05908 12.6012 7.05908C11.2901 7.05908 10.1873 7.51742 9.29284 8.43408C8.39839 9.35075 7.95117 10.4424 7.95117 11.7091C7.95117 12.0646 7.99006 12.4091 8.06784 12.7424C8.14562 13.0757 8.26228 13.4035 8.41784 13.7257H3.28451Z"
fill={fill}
/>
</svg>
);
export default GranularAccess;

View file

@ -0,0 +1,19 @@
import React from 'react';
const UserGear = ({ fill = '#889096', width = '17', className = '', viewBox = '0 0 17 17' }) => (
<svg
width={width}
height={width}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M1.95117 12.3926V11.8593C1.95117 11.4926 2.04562 11.1482 2.23451 10.8259C2.42339 10.5037 2.68451 10.2593 3.01784 10.0926C3.58451 9.80371 4.22339 9.55926 4.9345 9.35926C5.64562 9.15926 6.42895 9.05926 7.2845 9.05926H7.51784C7.58451 9.05926 7.65117 9.07038 7.71784 9.0926C7.39562 9.8926 7.26228 10.687 7.31784 11.4759C7.37339 12.2648 7.64006 13.0148 8.11784 13.7259H3.28451C2.91784 13.7259 2.60395 13.5954 2.34284 13.3343C2.08173 13.0732 1.95117 12.7593 1.95117 12.3926ZM11.9512 12.3926C12.3178 12.3926 12.6317 12.262 12.8928 12.0009C13.1539 11.7398 13.2845 11.4259 13.2845 11.0593C13.2845 10.6926 13.1539 10.3787 12.8928 10.1176C12.6317 9.85649 12.3178 9.72593 11.9512 9.72593C11.5845 9.72593 11.2706 9.85649 11.0095 10.1176C10.7484 10.3787 10.6178 10.6926 10.6178 11.0593C10.6178 11.4259 10.7484 11.7398 11.0095 12.0009C11.2706 12.262 11.5845 12.3926 11.9512 12.3926ZM7.2845 8.3926C6.55117 8.3926 5.92339 8.13149 5.40117 7.60926C4.87895 7.08704 4.61784 6.45926 4.61784 5.72593C4.61784 4.9926 4.87895 4.36482 5.40117 3.8426C5.92339 3.32038 6.55117 3.05927 7.2845 3.05927C8.01784 3.05927 8.64562 3.32038 9.16784 3.8426C9.69006 4.36482 9.95117 4.9926 9.95117 5.72593C9.95117 6.45926 9.69006 7.08704 9.16784 7.60926C8.64562 8.13149 8.01784 8.3926 7.2845 8.3926ZM11.1845 13.8593L11.0845 13.3926C10.9512 13.337 10.8262 13.2787 10.7095 13.2176C10.5928 13.1565 10.4734 13.0815 10.3512 12.9926L9.86784 13.1426C9.72339 13.187 9.58173 13.1815 9.44284 13.1259C9.30395 13.0704 9.19562 12.9815 9.11784 12.8593L8.9845 12.6259C8.90673 12.4926 8.87895 12.3482 8.90117 12.1926C8.92339 12.037 8.99562 11.9093 9.11784 11.8093L9.4845 11.4926C9.46228 11.337 9.45117 11.1926 9.45117 11.0593C9.45117 10.9259 9.46228 10.7815 9.4845 10.6259L9.11784 10.3093C8.99562 10.2093 8.92339 10.0843 8.90117 9.93426C8.87895 9.78426 8.90673 9.6426 8.9845 9.50926L9.1345 9.25926C9.21228 9.13704 9.31784 9.04815 9.45117 8.9926C9.5845 8.93704 9.72339 8.93149 9.86784 8.97593L10.3512 9.12593C10.4734 9.03704 10.5928 8.96204 10.7095 8.90093C10.8262 8.83982 10.9512 8.78149 11.0845 8.72593L11.1845 8.2426C11.2178 8.08704 11.2928 7.96204 11.4095 7.8676C11.5262 7.77315 11.6623 7.72593 11.8178 7.72593H12.0845C12.2401 7.72593 12.3762 7.77593 12.4928 7.87593C12.6095 7.97593 12.6845 8.10371 12.7178 8.25926L12.8178 8.72593C12.9512 8.78149 13.0762 8.8426 13.1928 8.90926C13.3095 8.97593 13.4289 9.05926 13.5512 9.15926L14.0012 9.00926C14.1567 8.95371 14.3067 8.95371 14.4512 9.00926C14.5956 9.06482 14.7067 9.15926 14.7845 9.2926L14.9178 9.52593C14.9956 9.65926 15.0234 9.80371 15.0012 9.95926C14.9789 10.1148 14.9067 10.2426 14.7845 10.3426L14.4178 10.6593C14.4401 10.7926 14.4512 10.9315 14.4512 11.0759C14.4512 11.2204 14.4401 11.3593 14.4178 11.4926L14.7845 11.8093C14.9067 11.9093 14.9789 12.0343 15.0012 12.1843C15.0234 12.3343 14.9956 12.4759 14.9178 12.6093L14.7678 12.8593C14.6901 12.9815 14.5845 13.0704 14.4512 13.1259C14.3178 13.1815 14.1789 13.187 14.0345 13.1426L13.5512 12.9926C13.4289 13.0815 13.3095 13.1565 13.1928 13.2176C13.0762 13.2787 12.9512 13.337 12.8178 13.3926L12.7178 13.8759C12.6845 14.0315 12.6095 14.1565 12.4928 14.2509C12.3762 14.3454 12.2401 14.3926 12.0845 14.3926H11.8178C11.6623 14.3926 11.5262 14.3426 11.4095 14.2426C11.2928 14.1426 11.2178 14.0148 11.1845 13.8593Z"
fill={fill}
/>
</svg>
);
export default UserGear;

View file

@ -162,6 +162,8 @@ import Capitalize from './Capitalize.jsx';
import Oblique from './Oblique.jsx';
import TriangleUpCenter from './TriangleUpCenter.jsx';
import TriangleDownCenter from './TriangleDownCenter.jsx';
import UserGear from './UserGear.jsx';
import GranularAccess from './GranularAccess.jsx';
const Icon = (props) => {
switch (props.name) {
@ -283,6 +285,8 @@ const Icon = (props) => {
return <Globe {...props} />;
case 'grid':
return <Grid {...props} />;
case 'granularaccess':
return <GranularAccess {...props} />;
case 'helppolygon':
return <HelpPolygon {...props} />;
case 'home':
@ -421,6 +425,8 @@ const Icon = (props) => {
return <UserAdd {...props} />;
case 'usergroup':
return <UserGroup {...props} />;
case 'usergear':
return <UserGear {...props} />;
case 'userremove':
return <UserRemove {...props} />;
case 'uturn':

View file

@ -0,0 +1,189 @@
import { getWorkspaceId } from '@/_helpers/utils';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Select, { components } from 'react-select';
export function AppsSelect(props) {
const navigate = useNavigate();
const workspaceId = getWorkspaceId();
const darkMode = localStorage.getItem('darkMode') === 'true';
//Will be used when workspace routing settings have been merged
const Menu = (props) => {
return (
<components.Menu {...props}>
{props.children}
<div className="add-group-btn">
<ButtonSolid
onClick={() => navigate(`/${workspaceId}/workspace-settings`)}
iconCustomClass="rectangle-add-icon"
className="create-group"
fill="var(--indigo9)"
variant="secondary"
leftIcon="addrectangle"
>
Create new group
</ButtonSolid>
</div>
</components.Menu>
);
};
const InputOption = ({ getStyles, Icon, isDisabled, isFocused, isSelected, children, innerProps, ...rest }) => {
const [isActive, setIsActive] = useState(false);
const onMouseDown = () => setIsActive(true);
const onMouseUp = () => setIsActive(false);
const onMouseLeave = () => setIsActive(false);
const style = {
alignItems: 'center',
backgroundColor: 'transparent',
color: 'inherit',
display: 'flex ',
};
const props = {
...innerProps,
onMouseDown,
onMouseUp,
onMouseLeave,
style,
};
return (
<components.Option
{...rest}
isDisabled={isDisabled}
isFocused={isFocused}
isSelected={isSelected}
getStyles={getStyles}
innerProps={props}
className={isDisabled && 'disabled'}
>
<input
style={{ width: '1.2rem', height: '1.2rem', borderRadius: '6px !important' }}
type="checkbox"
className="form-check-input"
checked={isSelected}
data-cy="group-check-input"
/>
<div className="select-option">{children}</div>
</components.Option>
);
};
const MultiValue = (props) => (
<components.MultiValue {...props}>
<div className="selected-value">{props.data.name}</div>
</components.MultiValue>
);
const selectStyles = {
indicatorSeparator: (base) => ({
...base,
display: 'none',
}),
option: (base) => ({
...base,
'.select-option': {
margin: '0px 10px',
},
}),
multiValue: (base) => ({
...base,
borderRadius: '6px',
backgroundColor: 'var(--slate3)',
color: 'var(--slate11)',
'.selected-value': {
padding: '0px 6px 1px 3px',
color: 'var(--slate11)',
},
}),
multiValueRemove: (base, state) => ({
...base,
'&:hover': {
backgroundColor: 'var(--tomato3)',
color: 'var(--tomato9)',
},
paddingLeft: '0px',
...(state.data.isFixed && { display: 'none' }),
}),
input: (base) => ({
...base,
input: {
height: '25px !important',
color: 'var(--slate11) !important',
},
}),
control: (base) => ({
...base,
outline: 'none',
border: '1px solid var(--slate7)',
boxShadow: 'none',
borderRadius: '6px',
background: 'unset',
'&:hover': {
border: '1px solid var(--slate8)',
},
}),
menuList: (base) => ({
...base,
maxHeight: '200px',
}),
menu: (base) => ({
...base,
background: 'var(--slate1)',
'.add-group-btn': {
display: 'flex',
justifyContent: 'flex-end',
padding: '8px',
borderTop: '1px solid var(--slate5)',
'.create-group': {
background: 'none !important',
'.rectangle-add-icon': {
width: '20px',
height: '20px',
},
},
},
}),
};
return (
<Select
isMulti
menuIsOpen={true}
width={'100%'}
isClearable={false}
hasSearch={true}
closeMenuOnSelect={false}
hideSelectedOptions={false}
className={darkMode && 'theme-dark dark-theme'}
components={{ Option: InputOption, MultiValue, IndicatorSeparator: null }}
{...props}
onChange={(selected) => {
if (
props.allowSelectAll &&
selected !== null &&
selected.length > 0 &&
selected[selected.length - 1].value === props.allOption.value
) {
return props.onChange(props.options);
}
return props.onChange(selected);
}}
options={[props.allowSelectAll ? props.allOption : null, ...props.options]}
styles={selectStyles}
placeholder="Select apps.."
noOptionsMessage={() => 'No apps found'}
/>
);
}
AppsSelect.defaultProps = {
allOption: {
label: 'Select all',
value: '*',
},
};

View file

@ -14,14 +14,16 @@ export default function ModalBase({
isLoading,
children,
cancelDisabled,
className = '',
size = 'sm',
}) {
return (
<Modal
show={show}
onHide={handleClose}
size="sm"
size={size}
centered={true}
contentClassName={`${darkMode ? 'theme-dark dark-theme modal-base' : 'modal-base'}`}
contentClassName={`${className} ${darkMode ? 'theme-dark dark-theme modal-base' : 'modal-base'}`}
>
<Modal.Header>
<Modal.Title className="font-weight-500" data-cy="modal-title">