Wired access controls for environemnts to frontend

This commit is contained in:
Vladyslav Matsiiako
2023-01-30 20:36:04 -08:00
parent 61c17ccc5e
commit 4ef8c273f7
10 changed files with 422 additions and 47 deletions

View File

@@ -1,4 +1,4 @@
{
"title": "Project Members",
"description": "This page shows the members of the selected project."
"description": "This page shows the members of the selected project, and allows you to modify their permissions."
}

View File

@@ -42,15 +42,15 @@ const UpgradePlanModal = ({
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-bunker-400 p-6 pt-5 text-left align-middle shadow-xl transition-all'>
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-500 p-6 pt-5 text-left align-middle shadow-xl transition-all'>
<Dialog.Title
as='h3'
className='text-lg font-medium leading-6 text-bunker-200'
className='text-xl font-medium leading-6 text-primary'
>
Unleash Infisical&apos;s Full Power
</Dialog.Title>
<div className='mt-2'>
<p className='text-sm text-bunker-300 mb-0.5'>
<p className='text-sm text-bunker-300 mb-1'>
{text}
</p>
<p className='text-sm text-bunker-300'>
@@ -60,7 +60,7 @@ const UpgradePlanModal = ({
<div className='mt-4'>
<button
type='button'
className='inline-flex justify-center rounded-md border border-transparent bg-primary opacity-90 hover:opacity-100 px-4 py-2 text-sm font-medium text-black hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
className='inline-flex justify-center rounded-md border border-transparent bg-primary opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-black hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
onClick={() => router.push(`/settings/billing/${localStorage.getItem("projectData.id")}`)}
>
Upgrade Now

View File

@@ -0,0 +1,299 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons';
import { plans } from 'public/data/frequentConstants';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
import { Select, SelectItem } from '@app/components/v2';
import updateUserProjectPermission from '@app/ee/api/memberships/UpdateUserProjectPermission';
import getOrganizationSubscriptions from '@app/pages/api/organization/GetOrgSubscription';
import changeUserRoleInWorkspace from '@app/pages/api/workspace/changeUserRoleInWorkspace';
import deleteUserFromWorkspace from '@app/pages/api/workspace/deleteUserFromWorkspace';
import getLatestFileKey from '@app/pages/api/workspace/getLatestFileKey';
import getProjectInfo from '@app/pages/api/workspace/getProjectInfo';
import uploadKeys from '@app/pages/api/workspace/uploadKeys';
import { decryptAssymmetric, encryptAssymmetric } from '../../utilities/cryptography/crypto';
import guidGenerator from '../../utilities/randomId';
import Button from '../buttons/Button';
import UpgradePlanModal from '../dialog/UpgradePlan';
// const roles = ['admin', 'user'];
// TODO: Set type for this
type Props = {
userData: any[];
changeData: (users: any[]) => void;
myUser: string;
filter: string;
};
type EnvironmentProps = {
name: string;
slug: string;
}
/**
* This is the component that shows the users of a certin project
* #TODO: add the possibility of choosing and doing operations on multiple users.
* @param {*} props
* @returns
*/
const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const host = window.location.origin;
const router = useRouter();
const [myRole, setMyRole] = useState('member');
const [currentPlan, setCurrentPlan] = useState('');
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
const { createNotification } = useNotificationContext();
const workspaceId = router.query.id as string;
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId: string, index: number) => {
// setUserIdToBeDeleted(userId);
// onClick();
deleteUserFromWorkspace(membershipId);
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length)
]);
};
// Update the rold of a certain user
const handleRoleUpdate = (index: number, e: string) => {
changeUserRoleInWorkspace(userData[index].membershipId, e.toLowerCase());
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e.toLocaleLowerCase(),
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
deniedPermissions: userData[index].deniedPermissions
}
],
...userData.slice(index + 1, userData?.length)
]);
createNotification({
text: `Successfully changed user role.`,
type: 'success'
});
};
const handlePermissionUpdate = (index: number, val: string, membershipId: string, slug: string ) => {
let denials: { ability: string; environmentSlug: string; }[];
if (val === "Read Only") {
denials = [{
ability: "write",
environmentSlug: slug
}];
} else if (val === "No Access") {
denials = [{
ability: "write",
environmentSlug: slug
}, {
ability: "read",
environmentSlug: slug
}];
} else {
denials = [];
}
if (currentPlan !== plans.professional && host !== 'https://app.infisical.com') {
setIsUpgradeModalOpen(true);
} else {
const allDenials = userData[index].deniedPermissions.filter((perm: { ability: string; environmentSlug: string; }) => perm.environmentSlug !== slug).concat(denials);
updateUserProjectPermission({ membershipId, denials: allDenials});
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: userData[index].role,
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
deniedPermissions: allDenials
}
],
...userData.slice(index + 1, userData?.length)
]);
createNotification({
text: `Successfully changed user permissions.`,
type: 'success'
});
}
};
useEffect(() => {
setMyRole(userData.filter((user) => user.email === myUser)[0]?.role);
(async () => {
const result = await getProjectInfo({ projectId: workspaceId });
setWorkspaceEnvs(result.environments);
const orgId = localStorage.getItem('orgData.id') as string;
const subscriptions = await getOrganizationSubscriptions({
orgId
});
if (subscriptions) {
setCurrentPlan(subscriptions.data[0].plan.product)
}
})();
}, [userData, myUser]);
const grantAccess = async (id: string, publicKey: string) => {
const result = await getLatestFileKey({ workspaceId });
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string;
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey: PRIVATE_KEY
});
uploadKeys(workspaceId, id, ciphertext, nonce);
router.reload();
};
const closeUpgradeModal = () => {
setIsUpgradeModalOpen(false);
}
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max">
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
<UpgradePlanModal
isOpen={isUpgradeModalOpen}
onClose={closeUpgradeModal}
text="You can change user permissions if you switch to Infisical's Professional plan."
/>
<table className="w-full my-0.5">
<thead className="text-gray-400 text-sm font-light">
<tr>
<th className="text-left pl-4 py-3.5">NAME</th>
<th className="text-left pl-4 py-3.5">EMAIL</th>
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
{workspaceEnvs.map(env => (
<th key={guidGenerator()} className="text-left pl-8 py-1 max-w-min break-normal">
<span>{env.name.toUpperCase()}<br/></span>
{/* <span>PERMISSION</span> */}
</th>
))}
<th aria-label="buttons" />
</tr>
</thead>
<tbody>
{userData?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
).length > 0 &&
userData
?.filter(
(user) =>
user.firstName?.toLowerCase().includes(filter) ||
user.lastName?.toLowerCase().includes(filter) ||
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName} {row.lastName}
</td>
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="justify-start h-full flex flex-row items-center">
<Select
className="w-36"
// open={isOpen}
onValueChange={(e) => handleRoleUpdate(index, e)}
value={row.role}
disabled={myRole !== 'admin' || myUser === row.email}
// onOpenChange={(open) => setIsOpen(open)}
>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</Select>
{row.status === 'completed' && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<Button
onButtonPressed={() => grantAccess(row.userId, row.publicKey)}
color="mineshaft"
text="Grant Access"
size="md"
/>
</div>
)}
</div>
</td>
{workspaceEnvs.map((env) => <td key={guidGenerator()} className="pl-8 py-2 border-mineshaft-700 border-t text-gray-300">
<Select
className="w-36"
// open={isOpen}
onValueChange={(val) => handlePermissionUpdate(index, val, row.membershipId, env.slug)}
value={
// eslint-disable-next-line no-nested-ternary
(row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read"))
? "No Access"
: (row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") ? "Read Only" : "Read & Write")
}
disabled={myUser === row.email || myRole !== 'admin'}
// onOpenChange={(open) => setIsOpen(open)}
>
<SelectItem value="No Access">No Access</SelectItem>
<SelectItem value="Read Only">Read Only</SelectItem>
<SelectItem value="Read & Write">Read & Write</SelectItem>
</Select>
</td>)}
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
color="red"
size="icon-sm"
icon={faX}
/>
</div>
) : (
<div className="w-9 h-9" />
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default ProjectUsersTable;

View File

@@ -110,14 +110,14 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
};
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1">
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max">
<div className="absolute rounded-t-md w-full h-[3.25rem] bg-white/5" />
<table className="w-full my-0.5">
<thead className="text-gray-400 text-sm font-light">
<tr>
<th className="text-left pl-6 py-3.5">FIRST NAME</th>
<th className="text-left pl-6 py-3.5">LAST NAME</th>
<th className="text-left pl-6 py-3.5">EMAIL</th>
<th className="text-left pl-4 py-3.5">NAME</th>
<th className="text-left pl-4 py-3.5">EMAIL</th>
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
<th aria-label="buttons" />
</tr>
</thead>
@@ -136,18 +136,15 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
user.email?.toLowerCase().includes(filter)
)
.map((row, index) => (
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-800/5">
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName}
<tr key={guidGenerator()} className="bg-bunker-800 hover:bg-bunker-700">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.firstName} {row.lastName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
{row.lastName}
</td>
<td className="pl-6 py-2 border-mineshaft-700 border-t text-gray-300">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
{row.email}
</td>
<td className="flex flex-row justify-end pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<div className="justify-end mr-6 mx-2 w-full h-full flex flex-row items-center">
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="justify-start h-full flex flex-row items-center">
{row.status === 'granted' &&
((myRole === 'admin' && row.role !== 'owner') || myRole === 'owner') &&
myUser !== row.email ? (
@@ -157,14 +154,12 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
data={
myRole === 'owner' ? ['owner', 'admin', 'member'] : ['admin', 'member']
}
text="Role: "
/>
) : (
row.status !== 'invited' &&
row.status !== 'verified' && (
<Listbox
isSelected={row.role}
text="Role: "
onChange={() => {
throw new Error('Function not implemented.');
}}
@@ -173,7 +168,7 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
)
)}
{(row.status === 'invited' || row.status === 'verified') && (
<div className="w-full pl-9">
<div className="w-full pr-20">
<Button
onButtonPressed={() => deleteMembershipAndResendInvite(row.email)}
color="mineshaft"
@@ -193,10 +188,12 @@ const UserTable = ({ userData, changeData, myUser, filter, resendInvite, isOrg }
</div>
)}
</div>
</td>
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center">
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
color="red"

View File

@@ -23,26 +23,26 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
ref={ref}
className={twMerge(
`inline-flex items-center justify-between data-[placeholder]:text-gray-500
px-4 py-2.5 font-inter text-sm text-white rounded-md bg-mineshaft-800`,
px-3 py-2 font-inter text-sm text-bunker-200 font-normal rounded-md bg-mineshaft-800 outline-none`,
className
)}
>
<SelectPrimitive.Value placeholder={placeholder} />
<SelectPrimitive.Icon className="ml-3">
{!props.disabled && <SelectPrimitive.Icon className="ml-3">
<FontAwesomeIcon icon={faChevronDown} size="sm" />
</SelectPrimitive.Icon>
</SelectPrimitive.Icon>}
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content
position="popper"
sideOffset={5}
className="overflow-hidden text-white rounded-md shadow-md font-inter bg-mineshaft-800"
style={{ width: 'var(--radix-select-trigger-width)' }}
// position="popper"
sideOffset={4}
className="overflow-hidden text-bunker-100 rounded-md shadow-md font-inter bg-mineshaft-800"
style={{ width: 'var(--radix-select-trigger-width) + 6' }}
>
<SelectPrimitive.ScrollUpButton>
<FontAwesomeIcon icon={faChevronUp} size="sm" />
</SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport className="p-2">
<SelectPrimitive.Viewport className="p-1.5">
{isLoading ? (
<div className="flex items-center justify-center">
<Spinner size="xs" />
@@ -75,16 +75,16 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
<SelectPrimitive.Item
{...props}
className={twMerge(
`text-sm rounded-sm transition-all hover:text-primary
hover:bg-mineshaft-700 flex items-center pl-10 pr-4 py-2 cursor-pointer
`text-sm rounded-sm transition-all hover:bg-mineshaft-500
flex items-center pl-10 pr-4 py-2 cursor-pointer rounded-md
select-none outline-none relative`,
isSelected && 'text-primary',
isSelected && 'bg-primary',
isDisabled && 'text-gray-600 hover:bg-transparent cursor-not-allowed hover:text-gray-600',
className
)}
ref={forwardedRef}
>
<SelectPrimitive.ItemIndicator className="absolute left-2">
<SelectPrimitive.ItemIndicator className="absolute left-3.5">
<FontAwesomeIcon icon={faCheck} size="sm" />
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemText className="">{children}</SelectPrimitive.ItemText>

View File

@@ -0,0 +1,39 @@
import SecurityClient from '@app/components/utilities/SecurityClient';
/**
* This function updates user permissions for a certain environment in a project
* @param {object} obj
* @param {string} obj.membershipId - membershipId of a certain user in a project
* @param {*[]} obj.denials - permissions that we are prohibitting users to do
* @returns
*/
const updateUserProjectPermission = async ({
membershipId,
denials
}: {
membershipId: string;
denials: {
ability: string;
environmentSlug: string;
}[]
}) =>
SecurityClient.fetchCall(`/api/v1/membership/${membershipId}/deny-permissions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
permissions: denials
})
}).then(async (res) => {
console.log({
permissions: denials
}, res)
if (res && res.status === 200) {
return res.json();
}
console.log('Failed to update user permissions for a certain environment in a project');
return undefined;
});
export default updateUserProjectPermission;

View File

@@ -13,6 +13,7 @@ export interface IMembershipOrg {
organization: string;
role: 'owner' | 'admin' | 'member';
status: 'invited' | 'accepted';
deniedPermissions: any[];
}
/**
* This route lets us get all the users in an org.

View File

@@ -0,0 +1,22 @@
import SecurityClient from '@app/components/utilities/SecurityClient';
/**
* This route lets us get the environments that a certain user has acess to in a certain project
* @param {string} workspaceId
* @returns
*/
const getWorkspaceEnvironments = ({ workspaceId }: { workspaceId: string }) =>
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/environments`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}).then(async (res) => {
if (res?.status === 200) {
return (await res.json()).accessibleEnvironments;
}
console.log('Failed to get accessible environments');
return undefined;
});
export default getWorkspaceEnvironments;

View File

@@ -41,11 +41,13 @@ import updateSecrets from '../api/files/UpdateSecrets';
import getUser from '../api/user/getUser';
import checkUserAction from '../api/userActions/checkUserAction';
import registerUserAction from '../api/userActions/registerUserAction';
import getWorkspaceEnvironments from '../api/workspace/getWorkspaceEnvironments';
import getWorkspaces from '../api/workspace/getWorkspaces';
type WorkspaceEnv = {
name: string;
slug: string;
isWriteDenied: boolean;
};
interface SecretDataProps {
@@ -133,7 +135,8 @@ export default function Dashboard() {
const [selectedSnapshotEnv, setSelectedSnapshotEnv] = useState<WorkspaceEnv>();
const [selectedEnv, setSelectedEnv] = useState<WorkspaceEnv>({
name: '',
slug: ''
slug: '',
isWriteDenied: false
});
const [atSecretAreaTop, setAtSecretsAreaTop] = useState(true);
const secretsTop = useRef<HTMLDivElement>(null);
@@ -214,9 +217,10 @@ export default function Dashboard() {
router.push(`/dashboard/${userWorkspaces?.[0]?._id}`);
}
setWorkspaceEnvs(workspace?.environments || []);
const accessibleEnvironments = await getWorkspaceEnvironments({ workspaceId });
setWorkspaceEnvs(accessibleEnvironments || []);
// set env
const env = workspace?.environments?.[0] || {
const env = accessibleEnvironments?.[0] || {
name: 'unknown',
slug: 'unkown'
};
@@ -351,6 +355,7 @@ export default function Dashboard() {
findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
if (nameErrors) {
setSaveLoading(false);
return createNotification({
text: 'Solve all name errors before saving secrets.',
type: 'error'
@@ -358,12 +363,21 @@ export default function Dashboard() {
}
if (duplicatesExist) {
setSaveLoading(false);
return createNotification({
text: 'Remove duplicated secret names before saving.',
type: 'error'
});
}
if (selectedEnv.isWriteDenied) {
setSaveLoading(false);
return createNotification({
text: 'You are not allowed to edit this environment',
type: 'error'
});
}
// Once "Save changes" is clicked, disable that button
setButtonReady(false);
@@ -580,7 +594,8 @@ export default function Dashboard() {
setSelectedEnv(
workspaceEnvs.find(({ name }) => envName === name) || {
name: 'unknown',
slug: 'unknown'
slug: 'unknown',
isWriteDenied: false
}
)
}
@@ -660,7 +675,8 @@ export default function Dashboard() {
setSelectedEnv(
workspaceEnvs.find(({ name }) => envName === name) || {
name: 'unknown',
slug: 'unknown'
slug: 'unknown',
isWriteDenied: false
}
)
}
@@ -673,7 +689,8 @@ export default function Dashboard() {
setSelectedSnapshotEnv(
workspaceEnvs.find(({ name }) => envName === name) || {
name: 'unknown',
slug: 'unknown'
slug: 'unknown',
isWriteDenied: false
}
)
}

View File

@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from '@app/components/basic/buttons/Button';
import AddProjectMemberDialog from '@app/components/basic/dialog/AddProjectMemberDialog';
import UserTable from '@app/components/basic/table/UserTable';
import ProjectUsersTable from '@app/components/basic/table/ProjectUsersTable';
import NavHeader from '@app/components/navigation/NavHeader';
import guidGenerator from '@app/components/utilities/randomId';
import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps';
@@ -33,6 +33,7 @@ interface UserProps {
}
interface MembershipProps {
deniedPermissions: any[];
user: UserProps;
inviteEmail: string;
role: string;
@@ -76,6 +77,7 @@ export default function Users() {
status: membership?.status,
userId: membership.user?._id,
membershipId: membership._id,
deniedPermissions: membership.deniedPermissions,
publicKey: membership.user?.publicKey
}));
setUserList(tempUserList);
@@ -144,7 +146,7 @@ export default function Users() {
};
return userList ? (
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start max-w-[calc(100vw-240px)]">
<Head>
<title>{t('common:head-title', { title: t('settings-members:title') })}</title>
<link rel="icon" href="/infisical.ico" />
@@ -168,7 +170,7 @@ export default function Users() {
setEmail={setEmail}
/>
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
<div className="px-6 pb-1 w-full flex flex-row items-start min-w-6xl max-w-6xl">
<div className="px-6 pb-1 w-full flex flex-row items-start">
<div className="h-10 w-full bg-white/5 mt-2 rounded-md flex flex-row items-center">
<FontAwesomeIcon
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
@@ -191,14 +193,12 @@ export default function Users() {
/>
</div>
</div>
<div className="block overflow-y-auto min-w-6xl max-w-6xl px-6">
<UserTable
<div className="block overflow-x-scroll px-6 pb-6 no-scrollbar no-scrollbar::-webkit-scrollbar">
<ProjectUsersTable
userData={userList}
changeData={setUserList}
myUser={personalEmail}
filter={searchUsers}
resendInvite={submitAddModal}
isOrg={false}
// onClick={openDeleteModal}
// deleteUser={deleteMembership}
// setUserIdToBeDeleted={setUserIdToBeDeleted}