mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 14:27:30 +00:00
feat(rbac): added frontend permission validation for missing pages and merged new features
This commit is contained in:
@@ -138,6 +138,17 @@ export const getRoles = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
|
const customRoles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
|
||||||
const roles = [
|
const roles = [
|
||||||
|
...(isOrgRole
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
_id: "owner",
|
||||||
|
name: "Owner",
|
||||||
|
slug: "owner",
|
||||||
|
description: "Complete administration access over the organization.",
|
||||||
|
permissions: adminPermissions.rules
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
_id: "admin",
|
_id: "admin",
|
||||||
name: "Admin",
|
name: "Admin",
|
||||||
@@ -152,24 +163,19 @@ export const getRoles = async (req: Request, res: Response) => {
|
|||||||
description: "Non-administrative role in an organization",
|
description: "Non-administrative role in an organization",
|
||||||
permissions: isOrgRole ? memberPermissions.rules : adminProjectPermissions.rules
|
permissions: isOrgRole ? memberPermissions.rules : adminProjectPermissions.rules
|
||||||
},
|
},
|
||||||
{
|
...(isOrgRole
|
||||||
_id: "viewer",
|
? []
|
||||||
name: "Viewer",
|
: [
|
||||||
slug: "viewer",
|
{
|
||||||
description: "Non-administrative role in an organization",
|
_id: "viewer",
|
||||||
permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules
|
name: "Viewer",
|
||||||
},
|
slug: "viewer",
|
||||||
|
description: "Non-administrative role in an organization",
|
||||||
|
permissions: isOrgRole ? viewerProjectPermission.rules : viewerProjectPermission.rules
|
||||||
|
}
|
||||||
|
]),
|
||||||
...customRoles
|
...customRoles
|
||||||
];
|
];
|
||||||
if (isOrgRole) {
|
|
||||||
roles.unshift({
|
|
||||||
_id: "owner",
|
|
||||||
name: "Owner",
|
|
||||||
slug: "owner",
|
|
||||||
description: "Complete administration access over the organization.",
|
|
||||||
permissions: adminPermissions.rules
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: "Successfully fetched role list",
|
message: "Successfully fetched role list",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type Props = {
|
|||||||
} & BoundCanProps<TProjectPermission>;
|
} & BoundCanProps<TProjectPermission>;
|
||||||
|
|
||||||
export const ProjectPermissionCan: FunctionComponent<Props> = ({
|
export const ProjectPermissionCan: FunctionComponent<Props> = ({
|
||||||
label = "Permission Denied. Kindly contact your org admin",
|
label = "Permission Denied. Kindly contact your project admin",
|
||||||
children,
|
children,
|
||||||
passThrough = true,
|
passThrough = true,
|
||||||
...props
|
...props
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Button, Modal, ModalContent } from "@app/components/v2";
|
import { Button, Modal, ModalContent } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
|
import { withProjectPermission } from "@app/hoc";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useDeleteIntegration,
|
useDeleteIntegration,
|
||||||
@@ -31,191 +32,194 @@ type Props = {
|
|||||||
frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>;
|
frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IntegrationsPage = ({ frameworkIntegrations }: Props) => {
|
export const IntegrationsPage = withProjectPermission(
|
||||||
const { t } = useTranslation();
|
({ frameworkIntegrations }: Props) => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const { createNotification } = useNotificationContext();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const workspaceId = currentWorkspace?._id || "";
|
const workspaceId = currentWorkspace?._id || "";
|
||||||
const environments = currentWorkspace?.environments || [];
|
const environments = currentWorkspace?.environments || [];
|
||||||
|
|
||||||
const { data: latestWsKey } = useGetUserWsKey(workspaceId);
|
const { data: latestWsKey } = useGetUserWsKey(workspaceId);
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
"activeBot"
|
"activeBot"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } =
|
const { data: cloudIntegrations, isLoading: isCloudIntegrationsLoading } =
|
||||||
useGetCloudIntegrations();
|
useGetCloudIntegrations();
|
||||||
|
|
||||||
const { data: integrationAuths, isLoading: isIntegrationAuthLoading } =
|
const { data: integrationAuths, isLoading: isIntegrationAuthLoading } =
|
||||||
useGetWorkspaceAuthorizations(
|
useGetWorkspaceAuthorizations(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
useCallback((data: IntegrationAuth[]) => {
|
useCallback((data: IntegrationAuth[]) => {
|
||||||
const groupBy: Record<string, IntegrationAuth> = {};
|
const groupBy: Record<string, IntegrationAuth> = {};
|
||||||
data.forEach((el) => {
|
data.forEach((el) => {
|
||||||
groupBy[el.integration] = el;
|
groupBy[el.integration] = el;
|
||||||
});
|
});
|
||||||
return groupBy;
|
return groupBy;
|
||||||
}, [])
|
}, [])
|
||||||
);
|
);
|
||||||
// mutation
|
// mutation
|
||||||
const {
|
const {
|
||||||
data: integrations,
|
data: integrations,
|
||||||
isLoading: isIntegrationLoading,
|
isLoading: isIntegrationLoading,
|
||||||
isFetching: isIntegrationFetching
|
isFetching: isIntegrationFetching
|
||||||
} = useGetWorkspaceIntegrations(workspaceId);
|
} = useGetWorkspaceIntegrations(workspaceId);
|
||||||
|
|
||||||
const { data: bot } = useGetWorkspaceBot(workspaceId);
|
const { data: bot } = useGetWorkspaceBot(workspaceId);
|
||||||
|
|
||||||
// mutation
|
// mutation
|
||||||
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
|
const { mutateAsync: updateBotActiveStatus, mutate: updateBotActiveStatusSync } =
|
||||||
useUpdateBotActiveStatus();
|
useUpdateBotActiveStatus();
|
||||||
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
const { mutateAsync: deleteIntegration } = useDeleteIntegration();
|
||||||
const {
|
const {
|
||||||
mutateAsync: deleteIntegrationAuth,
|
mutateAsync: deleteIntegrationAuth,
|
||||||
isLoading: isDeleteIntegrationAuthSuccess,
|
isLoading: isDeleteIntegrationAuthSuccess,
|
||||||
reset: resetDeleteIntegrationAuth
|
reset: resetDeleteIntegrationAuth
|
||||||
} = useDeleteIntegrationAuth();
|
} = useDeleteIntegrationAuth();
|
||||||
|
|
||||||
// summary: this use effect is trigger when all integration auths are removed thus deactivate bot
|
// summary: this use effect is trigger when all integration auths are removed thus deactivate bot
|
||||||
// details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched
|
// details: so onsuccessfully deleting an integration auth, immediately integration list is refeteched
|
||||||
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook
|
// After the refetch is completed check if its empty. Then set bot active and reset the submit hook
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) {
|
if (isDeleteIntegrationAuthSuccess && !isIntegrationFetching && !integrations?.length) {
|
||||||
if (bot?._id)
|
if (bot?._id)
|
||||||
updateBotActiveStatusSync({
|
updateBotActiveStatusSync({
|
||||||
isActive: false,
|
isActive: false,
|
||||||
botId: bot._id,
|
botId: bot._id,
|
||||||
workspaceId
|
workspaceId
|
||||||
});
|
});
|
||||||
resetDeleteIntegrationAuth();
|
resetDeleteIntegrationAuth();
|
||||||
}
|
|
||||||
}, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]);
|
|
||||||
|
|
||||||
const handleProviderIntegration = async (provider: string) => {
|
|
||||||
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
|
||||||
if (!selectedCloudIntegration) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (bot && !bot.isActive) {
|
|
||||||
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
|
|
||||||
await updateBotActiveStatus({
|
|
||||||
workspaceId,
|
|
||||||
botKey,
|
|
||||||
isActive: true,
|
|
||||||
botId: bot._id
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
}, [isIntegrationFetching, isDeleteIntegrationAuthSuccess, integrations?.length]);
|
||||||
if (!integrationAuthForProvider) {
|
|
||||||
redirectForProviderAuth(selectedCloudIntegration);
|
const handleProviderIntegration = async (provider: string) => {
|
||||||
|
const selectedCloudIntegration = cloudIntegrations?.find(({ slug }) => provider === slug);
|
||||||
|
if (!selectedCloudIntegration) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (bot && !bot.isActive) {
|
||||||
|
const botKey = generateBotKey(bot.publicKey, latestWsKey!);
|
||||||
|
await updateBotActiveStatus({
|
||||||
|
workspaceId,
|
||||||
|
botKey,
|
||||||
|
isActive: true,
|
||||||
|
botId: bot._id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const integrationAuthForProvider = integrationAuths?.[provider];
|
||||||
|
if (!integrationAuthForProvider) {
|
||||||
|
redirectForProviderAuth(selectedCloudIntegration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id);
|
||||||
|
router.push(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// function to strat integration for a provider
|
||||||
|
// confirmation to user passing the bot key for provider to get secret access
|
||||||
|
const handleProviderIntegrationStart = (provider: string) => {
|
||||||
|
if (!bot?.isActive) {
|
||||||
|
handlePopUpOpen("activeBot", { provider });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
handleProviderIntegration(provider);
|
||||||
|
};
|
||||||
|
|
||||||
const url = redirectToIntegrationAppConfigScreen(provider, integrationAuthForProvider._id);
|
const handleUserAcceptBotCondition = () => {
|
||||||
router.push(url);
|
const { provider } = popUp.activeBot?.data as { provider: string };
|
||||||
} catch (error) {
|
handleProviderIntegration(provider);
|
||||||
console.error(error);
|
handlePopUpClose("activeBot");
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// function to strat integration for a provider
|
const handleIntegrationDelete = async (integrationId: string, cb: () => void) => {
|
||||||
// confirmation to user passing the bot key for provider to get secret access
|
try {
|
||||||
const handleProviderIntegrationStart = (provider: string) => {
|
await deleteIntegration({ id: integrationId, workspaceId });
|
||||||
if (!bot?.isActive) {
|
if (cb) cb();
|
||||||
handlePopUpOpen("activeBot", { provider });
|
createNotification({
|
||||||
return;
|
type: "success",
|
||||||
}
|
text: "Deleted integration"
|
||||||
handleProviderIntegration(provider);
|
});
|
||||||
};
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to delete integration"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserAcceptBotCondition = () => {
|
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
||||||
const { provider } = popUp.activeBot?.data as { provider: string };
|
const integrationAuthForProvider = integrationAuths?.[provider];
|
||||||
handleProviderIntegration(provider);
|
if (!integrationAuthForProvider) return;
|
||||||
handlePopUpClose("activeBot");
|
try {
|
||||||
};
|
await deleteIntegrationAuth({
|
||||||
|
id: integrationAuthForProvider._id,
|
||||||
|
workspaceId
|
||||||
|
});
|
||||||
|
if (cb) cb();
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Revoked provider authentication"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to revoke provider authentication"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleIntegrationDelete = async (integrationId: string, cb: () => void) => {
|
return (
|
||||||
try {
|
<div className="container mx-auto max-w-7xl pb-12 text-white">
|
||||||
await deleteIntegration({ id: integrationId, workspaceId });
|
<IntegrationsSection
|
||||||
if (cb) cb();
|
isLoading={isIntegrationLoading}
|
||||||
createNotification({
|
integrations={integrations}
|
||||||
type: "success",
|
environments={environments}
|
||||||
text: "Deleted integration"
|
onIntegrationDelete={({ _id: id }, cb) => handleIntegrationDelete(id, cb)}
|
||||||
});
|
/>
|
||||||
} catch (err) {
|
<CloudIntegrationSection
|
||||||
console.log(err);
|
isLoading={isCloudIntegrationsLoading || isIntegrationAuthLoading}
|
||||||
createNotification({
|
cloudIntegrations={cloudIntegrations}
|
||||||
type: "error",
|
integrationAuths={integrationAuths}
|
||||||
text: "Failed to delete integration"
|
onIntegrationStart={handleProviderIntegrationStart}
|
||||||
});
|
onIntegrationRevoke={handleIntegrationAuthRevoke}
|
||||||
}
|
/>
|
||||||
};
|
<Modal
|
||||||
|
isOpen={popUp.activeBot?.isOpen}
|
||||||
const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => {
|
onOpenChange={(isOpen) => handlePopUpToggle("activeBot", isOpen)}
|
||||||
const integrationAuthForProvider = integrationAuths?.[provider];
|
|
||||||
if (!integrationAuthForProvider) return;
|
|
||||||
try {
|
|
||||||
await deleteIntegrationAuth({
|
|
||||||
id: integrationAuthForProvider._id,
|
|
||||||
workspaceId
|
|
||||||
});
|
|
||||||
if (cb) cb();
|
|
||||||
createNotification({
|
|
||||||
type: "success",
|
|
||||||
text: "Revoked provider authentication"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
createNotification({
|
|
||||||
type: "error",
|
|
||||||
text: "Failed to revoke provider authentication"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto max-w-7xl pb-12 text-white">
|
|
||||||
<IntegrationsSection
|
|
||||||
isLoading={isIntegrationLoading}
|
|
||||||
integrations={integrations}
|
|
||||||
environments={environments}
|
|
||||||
onIntegrationDelete={({ _id: id }, cb) => handleIntegrationDelete(id, cb)}
|
|
||||||
/>
|
|
||||||
<CloudIntegrationSection
|
|
||||||
isLoading={isCloudIntegrationsLoading || isIntegrationAuthLoading}
|
|
||||||
cloudIntegrations={cloudIntegrations}
|
|
||||||
integrationAuths={integrationAuths}
|
|
||||||
onIntegrationStart={handleProviderIntegrationStart}
|
|
||||||
onIntegrationRevoke={handleIntegrationAuthRevoke}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
isOpen={popUp.activeBot?.isOpen}
|
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("activeBot", isOpen)}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
title={t("integrations.grant-access-to-secrets") as string}
|
|
||||||
footerContent={
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Button onClick={() => handleUserAcceptBotCondition()}>
|
|
||||||
{t("integrations.grant-access-button") as string}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => handlePopUpClose("activeBot")}
|
|
||||||
variant="outline_bg"
|
|
||||||
colorSchema="secondary"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{t("integrations.why-infisical-needs-access")}
|
<ModalContent
|
||||||
</ModalContent>
|
title={t("integrations.grant-access-to-secrets") as string}
|
||||||
</Modal>
|
footerContent={
|
||||||
<FrameworkIntegrationSection frameworks={frameworkIntegrations} />
|
<div className="flex items-center space-x-2">
|
||||||
</div>
|
<Button onClick={() => handleUserAcceptBotCondition()}>
|
||||||
);
|
{t("integrations.grant-access-button") as string}
|
||||||
};
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePopUpClose("activeBot")}
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("integrations.why-infisical-needs-access")}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<FrameworkIntegrationSection frameworks={frameworkIntegrations} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Integrations }
|
||||||
|
);
|
||||||
|
|||||||
+17
-1
@@ -2,7 +2,9 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { DeleteActionModal,Skeleton, Tooltip } from "@app/components/v2";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { DeleteActionModal, Skeleton, Tooltip } from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
|
import { IntegrationAuth, TCloudIntegration } from "@app/hooks/api/types";
|
||||||
|
|
||||||
@@ -28,6 +30,8 @@ export const CloudIntegrationSection = ({
|
|||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"deleteConfirmation"
|
"deleteConfirmation"
|
||||||
] as const);
|
] as const);
|
||||||
|
const permission = useProjectPermission();
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
const isEmpty = !isLoading && !cloudIntegrations?.length;
|
const isEmpty = !isLoading && !cloudIntegrations?.length;
|
||||||
|
|
||||||
@@ -57,6 +61,18 @@ export const CloudIntegrationSection = ({
|
|||||||
} flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`}
|
} flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!cloudIntegration.isAvailable) return;
|
if (!cloudIntegration.isAvailable) return;
|
||||||
|
if (
|
||||||
|
permission.cannot(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Permission Denied. Kindly contact your project admin"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
onIntegrationStart(cloudIntegration.slug);
|
onIntegrationStart(cloudIntegration.slug);
|
||||||
}}
|
}}
|
||||||
key={cloudIntegration.slug}
|
key={cloudIntegration.slug}
|
||||||
|
|||||||
+22
-12
@@ -2,6 +2,7 @@ import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
|
||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { TIntegration } from "@app/hooks/api/types";
|
import { TIntegration } from "@app/hooks/api/types";
|
||||||
|
|
||||||
@@ -125,18 +127,26 @@ export const IntegrationsSection = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex cursor-default items-center">
|
<div className="flex cursor-default items-center">
|
||||||
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
<ProjectPermissionCan
|
||||||
<Tooltip content="Remove Integration">
|
I={ProjectPermissionActions.Delete}
|
||||||
<IconButton
|
a={ProjectPermissionSub.Integrations}
|
||||||
onClick={() => handlePopUpOpen("deleteConfirmation", integration)}
|
>
|
||||||
ariaLabel="delete"
|
{(isAllowed) => (
|
||||||
colorSchema="danger"
|
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
||||||
variant="star"
|
<Tooltip content="Remove Integration">
|
||||||
>
|
<IconButton
|
||||||
<FontAwesomeIcon icon={faXmark} className="px-0.5" />
|
onClick={() => handlePopUpOpen("deleteConfirmation", integration)}
|
||||||
</IconButton>
|
ariaLabel="delete"
|
||||||
</Tooltip>
|
isDisabled={!isAllowed}
|
||||||
</div>
|
colorSchema="danger"
|
||||||
|
variant="star"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} className="px-0.5" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+13
-11
@@ -42,17 +42,19 @@ export const AutoCapitalizationSection = withProjectPermission(
|
|||||||
<p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p>
|
<p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p>
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Checkbox
|
<div className="w-max">
|
||||||
className="data-[state=checked]:bg-primary"
|
<Checkbox
|
||||||
id="autoCapitalization"
|
className="data-[state=checked]:bg-primary"
|
||||||
isDisabled={!isAllowed}
|
id="autoCapitalization"
|
||||||
isChecked={currentWorkspace?.autoCapitalization ?? false}
|
isDisabled={!isAllowed}
|
||||||
onCheckedChange={(state) => {
|
isChecked={currentWorkspace?.autoCapitalization ?? false}
|
||||||
handleToggleCapitalizationToggle(state as boolean);
|
onCheckedChange={(state) => {
|
||||||
}}
|
handleToggleCapitalizationToggle(state as boolean);
|
||||||
>
|
}}
|
||||||
{t("settings.project.auto-capitalization-description")}
|
>
|
||||||
</Checkbox>
|
{t("settings.project.auto-capitalization-description")}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+13
-11
@@ -90,17 +90,19 @@ export const E2EESection = withProjectPermission(
|
|||||||
</p>
|
</p>
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Checkbox
|
<div className="w-max">
|
||||||
className="data-[state=checked]:bg-primary"
|
<Checkbox
|
||||||
id="autoCapitalization"
|
className="data-[state=checked]:bg-primary"
|
||||||
isChecked={!bot.isActive}
|
id="autoCapitalization"
|
||||||
isDisabled={!isAllowed}
|
isChecked={!bot.isActive}
|
||||||
onCheckedChange={async () => {
|
isDisabled={!isAllowed}
|
||||||
await toggleBotActivate();
|
onCheckedChange={async () => {
|
||||||
}}
|
await toggleBotActivate();
|
||||||
>
|
}}
|
||||||
End-to-end encryption enabled
|
>
|
||||||
</Checkbox>
|
End-to-end encryption enabled
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+52
-32
@@ -1,4 +1,4 @@
|
|||||||
import { faArrowDown,faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowDown, faArrowUp, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
@@ -16,9 +16,7 @@ import {
|
|||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import {
|
import { useReorderWsEnvironment } from "@app/hooks/api";
|
||||||
useReorderWsEnvironment
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -39,18 +37,23 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const reorderWsEnvironment = useReorderWsEnvironment();
|
const reorderWsEnvironment = useReorderWsEnvironment();
|
||||||
|
|
||||||
const handleReorderEnv= async (shouldMoveUp: boolean, name: string, slug: string) => {
|
const handleReorderEnv = async (shouldMoveUp: boolean, name: string, slug: string) => {
|
||||||
try {
|
try {
|
||||||
if (!currentWorkspace?._id) return;
|
if (!currentWorkspace?._id) return;
|
||||||
|
|
||||||
const indexOfEnv = currentWorkspace.environments.findIndex((env) => env.name === name && env.slug === slug);
|
const indexOfEnv = currentWorkspace.environments.findIndex(
|
||||||
|
(env) => env.name === name && env.slug === slug
|
||||||
|
);
|
||||||
|
|
||||||
// check that this reordering is possible
|
// check that this reordering is possible
|
||||||
if (indexOfEnv === 0 && shouldMoveUp || indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp) {
|
if (
|
||||||
return
|
(indexOfEnv === 0 && shouldMoveUp) ||
|
||||||
|
(indexOfEnv === currentWorkspace.environments.length - 1 && !shouldMoveUp)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1
|
const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1;
|
||||||
|
|
||||||
await reorderWsEnvironment.mutateAsync({
|
await reorderWsEnvironment.mutateAsync({
|
||||||
workspaceID: currentWorkspace._id,
|
workspaceID: currentWorkspace._id,
|
||||||
@@ -92,31 +95,48 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>{slug}</Td>
|
<Td>{slug}</Td>
|
||||||
<Td className="flex items-center justify-end">
|
<Td className="flex items-center justify-end">
|
||||||
<IconButton
|
<ProjectPermissionCan
|
||||||
className="mr-3 py-2"
|
I={ProjectPermissionActions.Edit}
|
||||||
onClick={() => {
|
a={ProjectPermissionSub.Environments}
|
||||||
handleReorderEnv(false, name, slug)
|
|
||||||
}}
|
|
||||||
colorSchema="primary"
|
|
||||||
variant="plain"
|
|
||||||
ariaLabel="update"
|
|
||||||
isDisabled={pos === currentWorkspace.environments.length - 1}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faArrowDown} />
|
{(isAllowed) => (
|
||||||
</IconButton>
|
<IconButton
|
||||||
<IconButton
|
className="mr-3 py-2"
|
||||||
className="mr-3 py-2"
|
onClick={() => {
|
||||||
onClick={() => {
|
handleReorderEnv(false, name, slug);
|
||||||
handleReorderEnv(true, name, slug)
|
}}
|
||||||
}}
|
colorSchema="primary"
|
||||||
colorSchema="primary"
|
variant="plain"
|
||||||
variant="plain"
|
ariaLabel="update"
|
||||||
ariaLabel="update"
|
isDisabled={pos === currentWorkspace.environments.length - 1 || !isAllowed}
|
||||||
isDisabled={pos === 0}
|
>
|
||||||
|
<FontAwesomeIcon icon={faArrowDown} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.Environments}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faArrowUp} />
|
{(isAllowed) => (
|
||||||
</IconButton>
|
<IconButton
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Environments}
|
className="mr-3 py-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleReorderEnv(true, name, slug);
|
||||||
|
}}
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
isDisabled={pos === 0 || !isAllowed}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faArrowUp} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Edit}
|
||||||
|
a={ProjectPermissionSub.Environments}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
+14
-3
@@ -1,3 +1,4 @@
|
|||||||
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
decryptSymmetric
|
decryptSymmetric
|
||||||
@@ -65,9 +66,19 @@ export const ProjectIndexSecretsSection = withProjectPermission(
|
|||||||
secrets. To access individual secrets by name through the SDK and public API, please
|
secrets. To access individual secrets by name through the SDK and public API, please
|
||||||
enable blind indexing.
|
enable blind indexing.
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={onEnableBlindIndices} color="mineshaft" size="sm" type="submit">
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
||||||
Enable Blind Indexing
|
{(isAllowed) => (
|
||||||
</Button>
|
<Button
|
||||||
|
onClick={onEnableBlindIndices}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
color="mineshaft"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Enable Blind Indexing
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
|
|||||||
Reference in New Issue
Block a user