mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 09:28:28 +00:00
feat(rbac): implemented granular blocking of actions based on permissions on org level ui
This commit is contained in:
@@ -292,7 +292,7 @@ export const createOrganizationPortalSession = async (req: Request, res: Respons
|
|||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
GeneralPermissionActions.Create,
|
GeneralPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { FunctionComponent, ReactNode } from "react";
|
||||||
|
import { BoundCanProps, Can } from "@casl/react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
OrgWorkspacePermissionActions,
|
||||||
|
TOrgPermission,
|
||||||
|
useOrgPermission
|
||||||
|
} from "@app/context/OrgPermissionContext";
|
||||||
|
|
||||||
|
import { Tooltip } from "../v2";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label?: ReactNode;
|
||||||
|
} & BoundCanProps<TOrgPermission>;
|
||||||
|
|
||||||
|
export const OrgPermissionCan: FunctionComponent<Props> = ({
|
||||||
|
label = "Permission Denied. Kindly contact your org admin",
|
||||||
|
children,
|
||||||
|
passThrough = true,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const permission = useOrgPermission();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Can
|
||||||
|
{...props}
|
||||||
|
passThrough={passThrough}
|
||||||
|
ability={props?.ability || permission}
|
||||||
|
I={OrgWorkspacePermissionActions.Read}
|
||||||
|
a={OrgPermissionSubjects.Sso}
|
||||||
|
>
|
||||||
|
{(isAllowed, ability) => {
|
||||||
|
// akhilmhdh: This is set as type due to error in casl react type.
|
||||||
|
const finalChild =
|
||||||
|
typeof children === "function"
|
||||||
|
? children(isAllowed, ability as TOrgPermission)
|
||||||
|
: children;
|
||||||
|
|
||||||
|
if (!isAllowed && passThrough) {
|
||||||
|
return <Tooltip content={label}>{finalChild}</Tooltip>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAllowed) return null;
|
||||||
|
|
||||||
|
return finalChild;
|
||||||
|
}}
|
||||||
|
</Can>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { OrgPermissionCan } from "./OrgPermissionCan";
|
||||||
@@ -3,13 +3,13 @@ import { createContext, ReactNode, useContext } from "react";
|
|||||||
import { useGetUserOrgPermissions } from "@app/hooks/api";
|
import { useGetUserOrgPermissions } from "@app/hooks/api";
|
||||||
|
|
||||||
import { useOrganization } from "../OrganizationContext";
|
import { useOrganization } from "../OrganizationContext";
|
||||||
import { TPermission } from "./types";
|
import { TOrgPermission } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PermissionContext = createContext<null | TPermission>(null);
|
const OrgPermissionContext = createContext<null | TOrgPermission>(null);
|
||||||
|
|
||||||
export const OrgPermissionProvider = ({ children }: Props): JSX.Element => {
|
export const OrgPermissionProvider = ({ children }: Props): JSX.Element => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
@@ -37,11 +37,13 @@ export const OrgPermissionProvider = ({ children }: Props): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PermissionContext.Provider value={permission}>{children}</PermissionContext.Provider>;
|
return (
|
||||||
|
<OrgPermissionContext.Provider value={permission}>{children}</OrgPermissionContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useOrgPermission = () => {
|
export const useOrgPermission = () => {
|
||||||
const ctx = useContext(PermissionContext);
|
const ctx = useContext(OrgPermissionContext);
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error("useOrgPermission to be used within <OrgPermissionProvider>");
|
throw new Error("useOrgPermission to be used within <OrgPermissionProvider>");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext";
|
export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext";
|
||||||
|
export type { TOrgPermission } from "./types";
|
||||||
|
export {
|
||||||
|
OrgGeneralPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
OrgWorkspacePermissionActions
|
||||||
|
} from "./types";
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { MongoAbility } from "@casl/ability";
|
import { MongoAbility } from "@casl/ability";
|
||||||
|
|
||||||
export enum GeneralPermissionActions {
|
export enum OrgGeneralPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum WorkspacePermissionActions {
|
export enum OrgWorkspacePermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create"
|
Create = "create"
|
||||||
}
|
}
|
||||||
@@ -24,13 +24,13 @@ export enum OrgPermissionSubjects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
| [WorkspacePermissionActions, OrgPermissionSubjects.Workspace]
|
| [OrgWorkspacePermissionActions, OrgPermissionSubjects.Workspace]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Role]
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.Role]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Member]
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.Member]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Settings]
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.Settings]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Billing];
|
| [OrgGeneralPermissionActions, OrgPermissionSubjects.Billing];
|
||||||
|
|
||||||
export type TPermission = MongoAbility<OrgPermissionSet>;
|
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
export { AuthProvider } from "./AuthContext";
|
export { AuthProvider } from "./AuthContext";
|
||||||
export { OrgProvider, useOrganization } from "./OrganizationContext";
|
export { OrgProvider, useOrganization } from "./OrganizationContext";
|
||||||
export { OrgPermissionProvider,useOrgPermission } from "./OrgPermissionContext";
|
export type { TOrgPermission } from "./OrgPermissionContext";
|
||||||
|
export {
|
||||||
|
OrgGeneralPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
OrgWorkspacePermissionActions
|
||||||
|
} from "./OrgPermissionContext";
|
||||||
|
export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext";
|
||||||
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
|
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
|
||||||
export { UserProvider, useUser } from "./UserContext";
|
export { UserProvider, useUser } from "./UserContext";
|
||||||
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
|
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { withPermission } from "./withPermission";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { withPermission } from "./withPermission";
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { ComponentType } from "react";
|
||||||
|
import { Abilities, AbilityTuple, Generics, SubjectType } from "@casl/ability";
|
||||||
|
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { TOrgPermission, useOrgPermission } from "@app/context";
|
||||||
|
|
||||||
|
type Props<T extends Abilities> = (T extends AbilityTuple
|
||||||
|
? {
|
||||||
|
action: T[0];
|
||||||
|
subject: Extract<T[1], SubjectType>;
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
action: string;
|
||||||
|
subject: string;
|
||||||
|
}) & { className?: string; containerClassName?: string };
|
||||||
|
|
||||||
|
export const withPermission = <T extends {}, J extends TOrgPermission>(
|
||||||
|
Component: ComponentType<T>,
|
||||||
|
{ action, subject, className, containerClassName }: Props<Generics<J>["abilities"]>
|
||||||
|
) => {
|
||||||
|
const HOC = (hocProps: T) => {
|
||||||
|
const permission = useOrgPermission();
|
||||||
|
|
||||||
|
// akhilmhdh: Set as any due to casl/react ts type bug
|
||||||
|
// REASON: casl due to its type checking can't seem to union even if union intersection is applied
|
||||||
|
if (permission.cannot(action as any, subject)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"container h-full mx-auto flex justify-center items-center",
|
||||||
|
containerClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"rounded-md bg-mineshaft-800 text-bunker-300 p-16 flex space-x-12 items-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-medium mb-2">Permission Denied</div>
|
||||||
|
<div className="text-sm">
|
||||||
|
You do not have permission to this page. <br /> Kindly contact your organization
|
||||||
|
administrator
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Component {...hocProps} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
HOC.displayName = "WithPermission";
|
||||||
|
return HOC;
|
||||||
|
};
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
|
||||||
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, TOrgPermission } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
import { BillingSettingsPage } from "@app/views/Settings/BillingSettingsPage";
|
import { BillingSettingsPage } from "@app/views/Settings/BillingSettingsPage";
|
||||||
|
|
||||||
export default function SettingsBilling() {
|
const SettingsBilling = withPermission<{}, TOrgPermission>(
|
||||||
|
() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -16,6 +19,10 @@ export default function SettingsBilling() {
|
|||||||
<BillingSettingsPage />
|
<BillingSettingsPage />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
{ action: OrgGeneralPermissionActions.Delete, subject: OrgPermissionSubjects.Billing }
|
||||||
|
);
|
||||||
|
|
||||||
SettingsBilling.requireAuth = true;
|
Object.assign(SettingsBilling, { requireAuth: true });
|
||||||
|
|
||||||
|
export default SettingsBilling;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import * as Tabs from "@radix-ui/react-tabs";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -42,9 +43,22 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
UpgradePlanModal
|
UpgradePlanModal
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useSubscription, useUser, useWorkspace } from "@app/context";
|
|
||||||
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useRegisterUserAction,useUploadWsKey } from "@app/hooks/api";
|
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
|
import {
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
OrgWorkspacePermissionActions,
|
||||||
|
useSubscription,
|
||||||
|
useUser,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
|
import {
|
||||||
|
fetchOrgUsers,
|
||||||
|
useAddUserToWs,
|
||||||
|
useCreateWorkspace,
|
||||||
|
useRegisterUserAction,
|
||||||
|
useUploadWsKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto";
|
import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto";
|
||||||
@@ -301,9 +315,7 @@ const LearningItem = ({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (userAction && userAction !== "first_time_secrets_pushed") {
|
if (userAction && userAction !== "first_time_secrets_pushed") {
|
||||||
await registerUserAction.mutateAsync(
|
await registerUserAction.mutateAsync(userAction);
|
||||||
userAction
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${
|
className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${
|
||||||
@@ -446,7 +458,8 @@ type TAddProjectFormData = yup.InferType<typeof formSchema>;
|
|||||||
|
|
||||||
// #TODO: Update all the workspaceIds
|
// #TODO: Update all the workspaceIds
|
||||||
|
|
||||||
export default function Organization() {
|
const OrganizationPage = withPermission(
|
||||||
|
() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -576,7 +589,13 @@ export default function Organization() {
|
|||||||
onChange={(e) => setSearchFilter(e.target.value)}
|
onChange={(e) => setSearchFilter(e.target.value)}
|
||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
/>
|
/>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgWorkspacePermissionActions.Create}
|
||||||
|
an={OrgPermissionSubjects.Workspace}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
|
isDisabled={!isAllowed}
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -590,6 +609,8 @@ export default function Organization() {
|
|||||||
>
|
>
|
||||||
Add New Project
|
Add New Project
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
<div className="mt-4 grid w-full grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||||
{isWorkspaceLoading &&
|
{isWorkspaceLoading &&
|
||||||
@@ -722,7 +743,9 @@ export default function Organization() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green"}`}
|
className={`w-28 pr-4 text-right text-sm font-semibold ${
|
||||||
|
false && "text-green"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
About 2 min
|
About 2 min
|
||||||
</div>
|
</div>
|
||||||
@@ -759,7 +782,9 @@ export default function Organization() {
|
|||||||
{feature.description}
|
{feature.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full items-center">
|
<div className="flex w-full items-center">
|
||||||
<div className="text-[15px] font-light text-mineshaft-300">Setup time: 20 min</div>
|
<div className="text-[15px] font-light text-mineshaft-300">
|
||||||
|
Setup time: 20 min
|
||||||
|
</div>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -850,6 +875,13 @@ export default function Organization() {
|
|||||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
action: OrgWorkspacePermissionActions.Read,
|
||||||
|
subject: OrgPermissionSubjects.Workspace
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Organization.requireAuth = true;
|
Object.assign(OrganizationPage, { requireAuth: true });
|
||||||
|
|
||||||
|
export default OrganizationPage;
|
||||||
|
|||||||
@@ -1,48 +1,60 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
import { SecretScanningLogsTable } from "@app/views/SecretScanning/components";
|
import { SecretScanningLogsTable } from "@app/views/SecretScanning/components";
|
||||||
|
|
||||||
import createNewIntegrationSession from "../../../api/secret-scanning/createSecretScanningSession";
|
import createNewIntegrationSession from "../../../api/secret-scanning/createSecretScanningSession";
|
||||||
import getInstallationStatus from "../../../api/secret-scanning/getInstallationStatus";
|
import getInstallationStatus from "../../../api/secret-scanning/getInstallationStatus";
|
||||||
import linkGitAppInstallationWithOrganization from "../../../api/secret-scanning/linkGitAppInstallationWithOrganization";
|
import linkGitAppInstallationWithOrganization from "../../../api/secret-scanning/linkGitAppInstallationWithOrganization";
|
||||||
|
|
||||||
export default function SecretScanning() {
|
const SecretScanning = withPermission(
|
||||||
const router = useRouter()
|
() => {
|
||||||
const queryParams = router.query
|
const router = useRouter();
|
||||||
const [integrationEnabled, setIntegrationStatus] = useState(false)
|
const queryParams = router.query;
|
||||||
|
const [integrationEnabled, setIntegrationStatus] = useState(false);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
const linkInstallation = async () => {
|
const linkInstallation = async () => {
|
||||||
if (typeof queryParams.state === "string" && typeof queryParams.installation_id === "string"){
|
if (
|
||||||
|
typeof queryParams.state === "string" &&
|
||||||
|
typeof queryParams.installation_id === "string"
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const isLinked = await linkGitAppInstallationWithOrganization(queryParams.installation_id as string, queryParams.state as string)
|
const isLinked = await linkGitAppInstallationWithOrganization(
|
||||||
if (isLinked){
|
queryParams.installation_id as string,
|
||||||
router.reload()
|
queryParams.state as string
|
||||||
|
);
|
||||||
|
if (isLinked) {
|
||||||
|
router.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("installation verification complete")
|
console.log("installation verification complete");
|
||||||
}catch (e){
|
} catch (e) {
|
||||||
console.log("app installation is stale, start new session", e)
|
console.log("app installation is stale, start new session", e);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchInstallationStatus = async () => {
|
const fetchInstallationStatus = async () => {
|
||||||
const status = await getInstallationStatus(String(localStorage.getItem("orgData.id")))
|
const status = await getInstallationStatus(String(localStorage.getItem("orgData.id")));
|
||||||
setIntegrationStatus(status)
|
setIntegrationStatus(status);
|
||||||
}
|
};
|
||||||
|
|
||||||
fetchInstallationStatus()
|
fetchInstallationStatus();
|
||||||
linkInstallation()
|
linkInstallation();
|
||||||
},[queryParams.state, queryParams.installation_id])
|
}, [queryParams.state, queryParams.installation_id]);
|
||||||
|
|
||||||
const generateNewIntegrationSession = async () => {
|
const generateNewIntegrationSession = async () => {
|
||||||
const session = await createNewIntegrationSession(String(localStorage.getItem("orgData.id")))
|
const session = await createNewIntegrationSession(String(localStorage.getItem("orgData.id")));
|
||||||
router.push(`https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`)
|
router.push(
|
||||||
}
|
`https://github.com/apps/infisical-radar/installations/new?state=${session.sessionId}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -54,28 +66,56 @@ export default function SecretScanning() {
|
|||||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||||
<div className="max-w-7xl px-6 w-full">
|
<div className="max-w-7xl px-6 w-full">
|
||||||
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
|
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
|
||||||
<div className="mb-6 text-lg text-mineshaft-300">Automatically monitor your GitHub activity and prevent secret leaks</div>
|
<div className="mb-6 text-lg text-mineshaft-300">
|
||||||
|
Automatically monitor your GitHub activity and prevent secret leaks
|
||||||
|
</div>
|
||||||
<div className="relative flex justify-between bg-mineshaft-800 border border-mineshaft-600 rounded-md p-6 mb-6">
|
<div className="relative flex justify-between bg-mineshaft-800 border border-mineshaft-600 rounded-md p-6 mb-6">
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
<div className="flex flex-row mb-1">Secret Scanning Status: {integrationEnabled ? <p className="text-green ml-1.5 font-semibold">Enabled</p> : <p className="text-red ml-1.5 font-semibold">Not enabled</p>}</div>
|
<div className="flex flex-row mb-1">
|
||||||
<div>{integrationEnabled ? <p className="text-mineshaft-300">Your GitHub organization is connected to Infisical, and is being continuously monitored for secret leaks.</p> : <p className="text-mineshaft-300">Connect your GitHub organization to Infisical.</p>}</div>
|
Secret Scanning Status:{" "}
|
||||||
|
{integrationEnabled ? (
|
||||||
|
<p className="text-green ml-1.5 font-semibold">Enabled</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-red ml-1.5 font-semibold">Not enabled</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{integrationEnabled ? (
|
||||||
|
<p className="text-mineshaft-300">
|
||||||
|
Your GitHub organization is connected to Infisical, and is being continuously
|
||||||
|
monitored for secret leaks.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-mineshaft-300">
|
||||||
|
Connect your GitHub organization to Infisical.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{integrationEnabled ? (
|
{integrationEnabled ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="absolute right-[2.5rem] top-[2.5rem] animate-ping rounded-full h-6 w-6 bg-green flex items-center justify-center"/>
|
<div className="absolute right-[2.5rem] top-[2.5rem] animate-ping rounded-full h-6 w-6 bg-green flex items-center justify-center" />
|
||||||
<div className="absolute right-[2.63rem] top-[2.63rem] animate-ping rounded-full h-5 w-5 bg-green flex items-center justify-center"/>
|
<div className="absolute right-[2.63rem] top-[2.63rem] animate-ping rounded-full h-5 w-5 bg-green flex items-center justify-center" />
|
||||||
<div className="absolute right-[2.82rem] top-[2.82rem] animate-ping rounded-full h-3.5 w-3.5 bg-green flex items-center justify-center"/>
|
<div className="absolute right-[2.82rem] top-[2.82rem] animate-ping rounded-full h-3.5 w-3.5 bg-green flex items-center justify-center" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center h-[3.25rem]">
|
<div className="flex items-center h-[3.25rem]">
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Create}
|
||||||
|
a={OrgPermissionSubjects.SecretScanning}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
onClick={generateNewIntegrationSession}
|
onClick={generateNewIntegrationSession}
|
||||||
className="py-2 h-min"
|
className="py-2 h-min"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Integrate with GitHub
|
Integrate with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -84,6 +124,10 @@ export default function SecretScanning() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.SecretScanning }
|
||||||
|
);
|
||||||
|
|
||||||
SecretScanning.requireAuth = true;
|
Object.assign(SecretScanning, { requireAuth: true });
|
||||||
|
|
||||||
|
export default SecretScanning;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
import { useGetRoles } from "@app/hooks/api";
|
import { useGetRoles } from "@app/hooks/api";
|
||||||
|
|
||||||
import { OrgMembersTable } from "./components/OrgMembersTable";
|
import { OrgMembersTable } from "./components/OrgMembersTable";
|
||||||
@@ -14,7 +15,8 @@ enum TabSections {
|
|||||||
Roles = "roles"
|
Roles = "roles"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MembersPage = () => {
|
export const MembersPage = withPermission(
|
||||||
|
() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
@@ -55,4 +57,6 @@ export const MembersPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Member }
|
||||||
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric
|
||||||
@@ -41,7 +42,14 @@ import {
|
|||||||
Tr,
|
Tr,
|
||||||
UpgradePlanModal
|
UpgradePlanModal
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context";
|
import {
|
||||||
|
OrgGeneralPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useSubscription,
|
||||||
|
useUser,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useAddUserToOrg,
|
useAddUserToOrg,
|
||||||
@@ -297,7 +305,10 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
placeholder="Search members..."
|
placeholder="Search members..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Create} a={OrgPermissionSubjects.Member}>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
|
isDisabled={!isAllowed}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isLoadingSSOConfig && ssoConfig && ssoConfig.isActive) {
|
if (!isLoadingSSOConfig && ssoConfig && ssoConfig.isActive) {
|
||||||
@@ -318,6 +329,8 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
>
|
>
|
||||||
Add Member
|
Add Member
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
@@ -345,12 +358,18 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>{email}</Td>
|
<Td>{email}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Edit}
|
||||||
|
a={OrgPermissionSubjects.Member}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<>
|
||||||
{status === "accepted" && (
|
{status === "accepted" && (
|
||||||
<Select
|
<Select
|
||||||
defaultValue={
|
defaultValue={
|
||||||
role === "custom" ? findRoleFromId(customRole)?.slug : role
|
role === "custom" ? findRoleFromId(customRole)?.slug : role
|
||||||
}
|
}
|
||||||
isDisabled={userId === u?._id}
|
isDisabled={userId === u?._id || !isAllowed}
|
||||||
className="w-40 bg-mineshaft-600"
|
className="w-40 bg-mineshaft-600"
|
||||||
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
|
||||||
onValueChange={(selectedRole) =>
|
onValueChange={(selectedRole) =>
|
||||||
@@ -371,6 +390,7 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
{(status === "invited" || status === "verified") &&
|
{(status === "invited" || status === "verified") &&
|
||||||
serverDetails?.emailConfigured && (
|
serverDetails?.emailConfigured && (
|
||||||
<Button
|
<Button
|
||||||
|
isDisabled={!isAllowed}
|
||||||
className="w-40"
|
className="w-40"
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
@@ -382,11 +402,15 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
{status === "completed" && (
|
{status === "completed" && (
|
||||||
<Button
|
<Button
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
onClick={() => onGrantAccess(u?._id, u?.publicKey)}
|
onClick={() => onGrantAccess(u?._id, u?.publicKey)}
|
||||||
>
|
>
|
||||||
Grant Access
|
Grant Access
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{userWs ? (
|
{userWs ? (
|
||||||
@@ -428,10 +452,15 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{userId !== u?._id && (
|
{userId !== u?._id && (
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Member}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
ariaLabel="delete"
|
ariaLabel="delete"
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
isDisabled={userId === u?._id}
|
isDisabled={userId === u?._id || !isAllowed}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handlePopUpOpen("removeMember", { id: orgMembershipId })
|
handlePopUpOpen("removeMember", { id: orgMembershipId })
|
||||||
}
|
}
|
||||||
@@ -439,6 +468,8 @@ export const OrgMembersTable = ({ roles = [] }: Props) => {
|
|||||||
<FontAwesomeIcon icon={faTrash} />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
|
|||||||
+6
-4
@@ -38,10 +38,9 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) =
|
|||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
@@ -52,11 +51,14 @@ export const BillingPermission = ({ isNonEditable, setValue, control }: Props) =
|
|||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
val === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setValue(
|
setValue(
|
||||||
|
|||||||
+6
-8
@@ -38,10 +38,9 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }:
|
|||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
@@ -52,14 +51,16 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }:
|
|||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
val === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.incident-contact",
|
"permissions.incident-contact",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
@@ -67,7 +68,6 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }:
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.incident-contact",
|
"permissions.incident-contact",
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
@@ -75,7 +75,6 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }:
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.incident-contact",
|
"permissions.incident-contact",
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
@@ -83,7 +82,6 @@ export const IncidentContactPermission = ({ isNonEditable, setValue, control }:
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setIsCustom.on();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.incident-contact",
|
"permissions.incident-contact",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
|||||||
+10
-7
@@ -31,31 +31,34 @@ const PERMISSIONS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const MemberPermission = ({ isNonEditable, setValue, control }: Props) => {
|
export const MemberPermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||||
const memberRule = useWatch({
|
const rule = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "permissions.member"
|
name: "permissions.member"
|
||||||
});
|
});
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const actions = Object.keys(memberRule || {}) as Array<keyof typeof memberRule>;
|
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += memberRule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
if (score === totalActions) return Permission.FullAccess;
|
if (score === totalActions) return Permission.FullAccess;
|
||||||
if (score === 1 && memberRule.read) return Permission.ReadOnly;
|
if (score === 1 && rule.read) return Permission.ReadOnly;
|
||||||
|
|
||||||
return Permission.Custom;
|
return Permission.Custom;
|
||||||
}, [memberRule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setValue(
|
setValue(
|
||||||
|
|||||||
+10
-11
@@ -31,34 +31,36 @@ const PERMISSIONS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const RolePermission = ({ isNonEditable, setValue, control }: Props) => {
|
export const RolePermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||||
const roleRule = useWatch({
|
const rule = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "permissions.role"
|
name: "permissions.role"
|
||||||
});
|
});
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const actions = Object.keys(roleRule || {}) as Array<keyof typeof roleRule>;
|
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += roleRule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
if (score === totalActions) return Permission.FullAccess;
|
if (score === totalActions) return Permission.FullAccess;
|
||||||
if (score === 1 && roleRule.read) return Permission.ReadOnly;
|
if (score === 1 && rule.read) return Permission.ReadOnly;
|
||||||
|
|
||||||
return Permission.Custom;
|
return Permission.Custom;
|
||||||
}, [roleRule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.role",
|
"permissions.role",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
@@ -66,7 +68,6 @@ export const RolePermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.role",
|
"permissions.role",
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
@@ -74,7 +75,6 @@ export const RolePermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.role",
|
"permissions.role",
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
@@ -82,7 +82,6 @@ export const RolePermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setIsCustom.on();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.role",
|
"permissions.role",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
|||||||
+6
-7
@@ -38,10 +38,9 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr
|
|||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
@@ -52,13 +51,16 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr
|
|||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.secret-scanning",
|
"permissions.secret-scanning",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
@@ -66,7 +68,6 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.secret-scanning",
|
"permissions.secret-scanning",
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
@@ -74,7 +75,6 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.secret-scanning",
|
"permissions.secret-scanning",
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
@@ -82,7 +82,6 @@ export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Pr
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setIsCustom.on();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.secret-scanning",
|
"permissions.secret-scanning",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
|||||||
+6
-7
@@ -38,10 +38,9 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
@@ -52,13 +51,16 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.settings",
|
"permissions.settings",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
@@ -66,7 +68,6 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.settings",
|
"permissions.settings",
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
@@ -74,7 +75,6 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.settings",
|
"permissions.settings",
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
@@ -82,7 +82,6 @@ export const SettingsPermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setIsCustom.on();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.settings",
|
"permissions.settings",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
|||||||
+6
-7
@@ -38,10 +38,9 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
@@ -52,13 +51,16 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.sso",
|
"permissions.sso",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
@@ -66,7 +68,6 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.sso",
|
"permissions.sso",
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
@@ -74,7 +75,6 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setIsCustom.off();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.sso",
|
"permissions.sso",
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
@@ -82,7 +82,6 @@ export const SsoPermission = ({ isNonEditable, setValue, control }: Props) => {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setIsCustom.on();
|
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.sso",
|
"permissions.sso",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
|
|||||||
+6
-4
@@ -36,10 +36,9 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
let score = 0;
|
|
||||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
const totalActions = PERMISSIONS.length;
|
const totalActions = PERMISSIONS.length;
|
||||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
const score = actions.map((key) => (rule[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
|
||||||
|
|
||||||
if (isCustom) return Permission.Custom;
|
if (isCustom) return Permission.Custom;
|
||||||
if (score === 0) return Permission.NoAccess;
|
if (score === 0) return Permission.NoAccess;
|
||||||
@@ -50,11 +49,14 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
}, [rule, isCustom]);
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
}, [selectedPermissionCategory]);
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
const handlePermissionChange = (val: Permission) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
val === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
if (val === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true });
|
setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true });
|
||||||
|
|||||||
@@ -1,28 +1,47 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
import updateRiskStatus, { RiskStatus } from "@app/pages/api/secret-scanning/updateRiskStatus";
|
import updateRiskStatus, { RiskStatus } from "@app/pages/api/secret-scanning/updateRiskStatus";
|
||||||
|
|
||||||
export const RiskStatusSelection = ({riskId, currentSelection}: {riskId: any, currentSelection: any }) => {
|
export const RiskStatusSelection = ({
|
||||||
|
riskId,
|
||||||
|
currentSelection
|
||||||
|
}: {
|
||||||
|
riskId: any;
|
||||||
|
currentSelection: any;
|
||||||
|
}) => {
|
||||||
const [selectedRiskStatus, setSelectedRiskStatus] = useState(currentSelection);
|
const [selectedRiskStatus, setSelectedRiskStatus] = useState(currentSelection);
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if (currentSelection !== selectedRiskStatus){
|
if (currentSelection !== selectedRiskStatus) {
|
||||||
const updateSelection = async () =>{
|
const updateSelection = async () => {
|
||||||
await updateRiskStatus(String(localStorage.getItem("orgData.id")), riskId, selectedRiskStatus)
|
await updateRiskStatus(
|
||||||
|
String(localStorage.getItem("orgData.id")),
|
||||||
|
riskId,
|
||||||
|
selectedRiskStatus
|
||||||
|
);
|
||||||
|
};
|
||||||
|
updateSelection();
|
||||||
}
|
}
|
||||||
updateSelection()
|
}, [selectedRiskStatus]);
|
||||||
}
|
|
||||||
},[selectedRiskStatus])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.SecretScanning}>
|
||||||
|
{(isAllowed) => (
|
||||||
<select
|
<select
|
||||||
|
disabled={!isAllowed}
|
||||||
value={selectedRiskStatus}
|
value={selectedRiskStatus}
|
||||||
onChange={(e) => setSelectedRiskStatus(e.target.value)}
|
onChange={(e) => setSelectedRiskStatus(e.target.value)}
|
||||||
className="block w-full py-2 px-3 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
className="block w-full py-2 px-3 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||||
>
|
>
|
||||||
<option>Unresolved</option>
|
<option>Unresolved</option>
|
||||||
<option value={RiskStatus.RESOLVED_FALSE_POSITIVE}>This is a false positive, resolved</option>
|
<option value={RiskStatus.RESOLVED_FALSE_POSITIVE}>
|
||||||
|
This is a false positive, resolved
|
||||||
|
</option>
|
||||||
<option value={RiskStatus.RESOLVED_REVOKED}>I have rotated the secret, resolved</option>
|
<option value={RiskStatus.RESOLVED_REVOKED}>I have rotated the secret, resolved</option>
|
||||||
<option value={RiskStatus.RESOLVED_NOT_REVOKED}>No rotate needed, resolved</option>
|
<option value={RiskStatus.RESOLVED_NOT_REVOKED}>No rotate needed, resolved</option>
|
||||||
</select>
|
</select>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+45
-22
@@ -1,5 +1,11 @@
|
|||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
import { useOrganization,useSubscription } from "@app/context";
|
import {
|
||||||
|
OrgGeneralPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useSubscription
|
||||||
|
} from "@app/context";
|
||||||
import {
|
import {
|
||||||
useCreateCustomerPortalSession,
|
useCreateCustomerPortalSession,
|
||||||
useGetOrgPlanBillingInfo,
|
useGetOrgPlanBillingInfo,
|
||||||
@@ -16,18 +22,16 @@ export const PreviewSection = () => {
|
|||||||
const getOrgTrialUrl = useGetOrgTrialUrl();
|
const getOrgTrialUrl = useGetOrgTrialUrl();
|
||||||
const createCustomerPortalSession = useCreateCustomerPortalSession();
|
const createCustomerPortalSession = useCreateCustomerPortalSession();
|
||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["managePlan"] as const);
|
||||||
"managePlan"
|
|
||||||
] as const);
|
|
||||||
|
|
||||||
const formatAmount = (amount: number) => {
|
const formatAmount = (amount: number) => {
|
||||||
const formattedTotal = (Math.floor(amount) / 100).toLocaleString("en-US", {
|
const formattedTotal = (Math.floor(amount) / 100).toLocaleString("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD"
|
||||||
});
|
});
|
||||||
|
|
||||||
return formattedTotal;
|
return formattedTotal;
|
||||||
}
|
};
|
||||||
|
|
||||||
const formatDate = (date: number) => {
|
const formatDate = (date: number) => {
|
||||||
const createdDate = new Date(date * 1000);
|
const createdDate = new Date(date * 1000);
|
||||||
@@ -37,12 +41,10 @@ export const PreviewSection = () => {
|
|||||||
const formattedDate: string = `${day}/${month}/${year}`;
|
const formattedDate: string = `${day}/${month}/${year}`;
|
||||||
|
|
||||||
return formattedDate;
|
return formattedDate;
|
||||||
}
|
};
|
||||||
|
|
||||||
function formatPlanSlug(slug: string) {
|
function formatPlanSlug(slug: string) {
|
||||||
return slug
|
return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " ");
|
||||||
.replace(/(\b[a-z])/g, match => match.toUpperCase())
|
|
||||||
.replace(/-/g, " ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpgradeBtnClick = async () => {
|
const handleUpgradeBtnClick = async () => {
|
||||||
@@ -64,23 +66,35 @@ export const PreviewSection = () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{subscription && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && (
|
{subscription &&
|
||||||
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600">
|
subscription?.slug !== "enterprise" &&
|
||||||
|
subscription?.slug !== "pro" &&
|
||||||
|
subscription?.slug !== "pro-annual" && (
|
||||||
|
<div className="p-4 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h2 className="text-xl font-semibold text-mineshaft-50">Become Infisical</h2>
|
<h2 className="text-xl font-semibold text-mineshaft-50">Become Infisical</h2>
|
||||||
<p className="text-gray-400 mt-4">Unlimited members, projects, RBAC, smart alerts, and so much more</p>
|
<p className="text-gray-400 mt-4">
|
||||||
|
Unlimited members, projects, RBAC, smart alerts, and so much more
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Create}
|
||||||
|
a={OrgPermissionSubjects.Billing}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
// onClick={() => handlePopUpOpen("managePlan")}
|
|
||||||
onClick={() => handleUpgradeBtnClick()}
|
onClick={() => handleUpgradeBtnClick()}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
|
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isLoading && subscription && data && (
|
{!isLoading && subscription && data && (
|
||||||
@@ -88,8 +102,15 @@ export const PreviewSection = () => {
|
|||||||
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 mr-4 border border-mineshaft-600">
|
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 mr-4 border border-mineshaft-600">
|
||||||
<p className="mb-2 text-gray-400">Current plan</p>
|
<p className="mb-2 text-gray-400">Current plan</p>
|
||||||
<p className="text-2xl text-mineshaft-50 font-semibold mb-8">
|
<p className="text-2xl text-mineshaft-50 font-semibold mb-8">
|
||||||
{`${formatPlanSlug(subscription.slug)} ${subscription.status === "trialing" ? "(Trial)" : ""}`}
|
{`${formatPlanSlug(subscription.slug)} ${
|
||||||
|
subscription.status === "trialing" ? "(Trial)" : ""
|
||||||
|
}`}
|
||||||
</p>
|
</p>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Edit}
|
||||||
|
a={OrgPermissionSubjects.Billing}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -97,15 +118,20 @@ export const PreviewSection = () => {
|
|||||||
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg._id);
|
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg._id);
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}}
|
}}
|
||||||
|
disabled={!isAllowed}
|
||||||
className="text-primary"
|
className="text-primary"
|
||||||
>
|
>
|
||||||
Manage plan →
|
Manage plan →
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mr-4">
|
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mr-4">
|
||||||
<p className="mb-2 text-gray-400">Price</p>
|
<p className="mb-2 text-gray-400">Price</p>
|
||||||
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
|
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
|
||||||
{subscription.status === "trialing" ? "$0.00 / month" : `${formatAmount(data.amount)} / ${data.interval}`}
|
{subscription.status === "trialing"
|
||||||
|
? "$0.00 / month"
|
||||||
|
: `${formatAmount(data.amount)} / ${data.interval}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600">
|
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600">
|
||||||
@@ -116,10 +142,7 @@ export const PreviewSection = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ManagePlansModal
|
<ManagePlansModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
popUp={popUp}
|
|
||||||
handlePopUpToggle={handlePopUpToggle}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+17
-23
@@ -4,20 +4,16 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
Button,
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
FormControl,
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
Input
|
import { useGetOrgBillingDetails, useUpdateOrgBillingDetails } from "@app/hooks/api";
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useOrganization } from "@app/context";
|
|
||||||
import {
|
|
||||||
useGetOrgBillingDetails,
|
|
||||||
useUpdateOrgBillingDetails
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup
|
||||||
|
.object({
|
||||||
name: yup.string().required("Company name is required")
|
name: yup.string().required("Company name is required")
|
||||||
}).required();
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
export const CompanyNameSection = () => {
|
export const CompanyNameSection = () => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
@@ -59,40 +55,38 @@ export const CompanyNameSection = () => {
|
|||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">
|
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">Business name</h2>
|
||||||
Business name
|
|
||||||
</h2>
|
|
||||||
<div className="max-w-md">
|
<div className="max-w-md">
|
||||||
<Controller
|
<Controller
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input
|
<Input placeholder="Acme Corp" {...field} className="bg-mineshaft-800" />
|
||||||
placeholder="Acme Corp"
|
|
||||||
{...field}
|
|
||||||
className="bg-mineshaft-800"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isDisabled={isLoading}
|
isDisabled={isLoading || !isAllowed}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+17
-21
@@ -4,19 +4,16 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
Button,
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
FormControl,
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
Input} from "@app/components/v2";
|
import { useGetOrgBillingDetails, useUpdateOrgBillingDetails } from "@app/hooks/api";
|
||||||
import { useOrganization } from "@app/context";
|
|
||||||
import {
|
|
||||||
useGetOrgBillingDetails,
|
|
||||||
useUpdateOrgBillingDetails
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup
|
||||||
|
.object({
|
||||||
email: yup.string().required("Email is required")
|
email: yup.string().required("Email is required")
|
||||||
}).required();
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
export const InvoiceEmailSection = () => {
|
export const InvoiceEmailSection = () => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
@@ -59,39 +56,38 @@ export const InvoiceEmailSection = () => {
|
|||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-semibold flex-1 text-white mb-8">
|
<h2 className="text-xl font-semibold flex-1 text-white mb-8">Invoice email recipient</h2>
|
||||||
Invoice email recipient
|
|
||||||
</h2>
|
|
||||||
<div className="max-w-md">
|
<div className="max-w-md">
|
||||||
<Controller
|
<Controller
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input
|
<Input placeholder="[email protected]" {...field} className="bg-mineshaft-800" />
|
||||||
placeholder="[email protected]"
|
|
||||||
{...field}
|
|
||||||
className="bg-mineshaft-800"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
control={control}
|
control={control}
|
||||||
name="email"
|
name="email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={isLoading || !isAllowed}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+11
-10
@@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
Button
|
import { Button } from "@app/components/v2";
|
||||||
} from "@app/components/v2";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
import { useOrganization } from "@app/context";
|
|
||||||
import { useAddOrgPmtMethod } from "@app/hooks/api";
|
import { useAddOrgPmtMethod } from "@app/hooks/api";
|
||||||
|
|
||||||
import { PmtMethodsTable } from "./PmtMethodsTable";
|
import { PmtMethodsTable } from "./PmtMethodsTable";
|
||||||
@@ -23,24 +21,27 @@ export const PmtMethodsSection = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||||
<div className="flex items-center mb-8">
|
<div className="flex items-center mb-8">
|
||||||
<h2 className="text-xl font-semibold flex-1 text-white">
|
<h2 className="text-xl font-semibold flex-1 text-white">Payment methods</h2>
|
||||||
Payment methods
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Create} a={OrgPermissionSubjects.Billing}>
|
||||||
</h2>
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAddPmtMethodBtnClick}
|
onClick={handleAddPmtMethodBtnClick}
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Add method
|
Add method
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<PmtMethodsTable />
|
<PmtMethodsTable />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+10
-1
@@ -1,6 +1,7 @@
|
|||||||
import { faCreditCard, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faCreditCard, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
EmptyState,
|
EmptyState,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -13,7 +14,7 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
import { useDeleteOrgPmtMethod, useGetOrgPmtMethods } from "@app/hooks/api";
|
import { useDeleteOrgPmtMethod, useGetOrgPmtMethods } from "@app/hooks/api";
|
||||||
|
|
||||||
export const PmtMethodsTable = () => {
|
export const PmtMethodsTable = () => {
|
||||||
@@ -52,17 +53,25 @@ export const PmtMethodsTable = () => {
|
|||||||
<Td>{last4}</Td>
|
<Td>{last4}</Td>
|
||||||
<Td>{`${exp_month}/${exp_year}`}</Td>
|
<Td>{`${exp_month}/${exp_year}`}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Billing}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleDeletePmtMethodBtnClick(_id);
|
await handleDeletePmtMethodBtnClick(_id);
|
||||||
}}
|
}}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
ariaLabel="update"
|
ariaLabel="update"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faXmark} />
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+9
-5
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { TaxIDModal } from "./TaxIDModal";
|
import { TaxIDModal } from "./TaxIDModal";
|
||||||
@@ -16,16 +17,19 @@ export const TaxIDSection = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||||
<div className="flex items-center mb-8">
|
<div className="flex items-center mb-8">
|
||||||
<h2 className="text-xl font-semibold flex-1 text-white">
|
<h2 className="text-xl font-semibold flex-1 text-white">Tax ID</h2>
|
||||||
Tax ID
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||||
</h2>
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handlePopUpOpen("addTaxID")}
|
onClick={() => handlePopUpOpen("addTaxID")}
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
>
|
>
|
||||||
Add method
|
Add method
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<TaxIDTable />
|
<TaxIDTable />
|
||||||
<TaxIDModal
|
<TaxIDModal
|
||||||
@@ -35,4 +39,4 @@ export const TaxIDSection = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+10
-1
@@ -1,6 +1,7 @@
|
|||||||
import { faFileInvoice, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faFileInvoice, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
EmptyState,
|
EmptyState,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -13,7 +14,7 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
import { useDeleteOrgTaxId, useGetOrgTaxIds } from "@app/hooks/api";
|
import { useDeleteOrgTaxId, useGetOrgTaxIds } from "@app/hooks/api";
|
||||||
|
|
||||||
const taxIDTypeLabelMap: { [key: string]: string } = {
|
const taxIDTypeLabelMap: { [key: string]: string } = {
|
||||||
@@ -101,6 +102,11 @@ export const TaxIDTable = () => {
|
|||||||
<Td>{taxIDTypeLabelMap[type]}</Td>
|
<Td>{taxIDTypeLabelMap[type]}</Td>
|
||||||
<Td>{value}</Td>
|
<Td>{value}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Billing}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleDeleteTaxIdBtnClick(_id);
|
await handleDeleteTaxIdBtnClick(_id);
|
||||||
@@ -109,9 +115,12 @@ export const TaxIDTable = () => {
|
|||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
ariaLabel="update"
|
ariaLabel="update"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faXmark} />
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+13
-5
@@ -1,5 +1,8 @@
|
|||||||
import { Fragment } from "react"
|
import { Fragment } from "react";
|
||||||
import { Tab } from "@headlessui/react"
|
import { Tab } from "@headlessui/react";
|
||||||
|
|
||||||
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
import { BillingCloudTab } from "../BillingCloudTab";
|
import { BillingCloudTab } from "../BillingCloudTab";
|
||||||
import { BillingDetailsTab } from "../BillingDetailsTab";
|
import { BillingDetailsTab } from "../BillingDetailsTab";
|
||||||
@@ -13,7 +16,8 @@ const tabs = [
|
|||||||
{ name: "Billing details", key: "tab-billing-details" }
|
{ name: "Billing details", key: "tab-billing-details" }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BillingTabGroup = () => {
|
export const BillingTabGroup = withPermission(
|
||||||
|
() => {
|
||||||
return (
|
return (
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="mt-8 mb-6 border-b-2 border-mineshaft-800">
|
<Tab.List className="mt-8 mb-6 border-b-2 border-mineshaft-800">
|
||||||
@@ -22,7 +26,9 @@ export const BillingTabGroup = () => {
|
|||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"}`}
|
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
|
||||||
|
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{tab.name}
|
{tab.name}
|
||||||
</button>
|
</button>
|
||||||
@@ -46,4 +52,6 @@ export const BillingTabGroup = () => {
|
|||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Billing }
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
|
|
||||||
import { OrgSSOSection } from "./OrgSSOSection";
|
import { OrgSSOSection } from "./OrgSSOSection";
|
||||||
|
|
||||||
export const OrgAuthTab = () => {
|
export const OrgAuthTab = withPermission(
|
||||||
|
() => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<OrgSSOSection />
|
<OrgSSOSection />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
|
||||||
|
);
|
||||||
|
|||||||
+29
-15
@@ -2,13 +2,15 @@ import { faPlus } 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";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
||||||
import { useOrganization, useSubscription } from "@app/context";
|
|
||||||
import {
|
import {
|
||||||
useCreateSSOConfig,
|
OrgGeneralPermissionActions,
|
||||||
useGetSSOConfig,
|
OrgPermissionSubjects,
|
||||||
useUpdateSSOConfig
|
useOrganization,
|
||||||
} from "@app/hooks/api";
|
useSubscription
|
||||||
|
} from "@app/context";
|
||||||
|
import { useCreateSSOConfig, useGetSSOConfig, useUpdateSSOConfig } from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { SSOModal } from "./SSOModal";
|
import { SSOModal } from "./SSOModal";
|
||||||
@@ -17,7 +19,7 @@ const ssoAuthProviderMap: { [key: string]: string } = {
|
|||||||
"okta-saml": "Okta SAML",
|
"okta-saml": "Okta SAML",
|
||||||
"azure-saml": "Azure SAML",
|
"azure-saml": "Azure SAML",
|
||||||
"jumpcloud-saml": "JumpCloud SAML"
|
"jumpcloud-saml": "JumpCloud SAML"
|
||||||
}
|
};
|
||||||
|
|
||||||
export const OrgSSOSection = (): JSX.Element => {
|
export const OrgSSOSection = (): JSX.Element => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
@@ -52,7 +54,7 @@ export const OrgSSOSection = (): JSX.Element => {
|
|||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const addSSOBtnClick = async () => {
|
const addSSOBtnClick = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -77,50 +79,62 @@ export const OrgSSOSection = (): JSX.Element => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||||
<div className="flex items-center mb-8">
|
<div className="flex items-center mb-8">
|
||||||
<h2 className="text-xl font-semibold flex-1 text-white">
|
<h2 className="text-xl font-semibold flex-1 text-white">SAML SSO Configuration</h2>
|
||||||
SAML SSO Configuration
|
|
||||||
</h2>
|
|
||||||
{!isLoading && (
|
{!isLoading && (
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Create} a={OrgPermissionSubjects.Sso}>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
onClick={addSSOBtnClick}
|
onClick={addSSOBtnClick}
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
>
|
>
|
||||||
{data ? "Update SAML SSO" : "Set up SAML SSO"}
|
{data ? "Update SAML SSO" : "Set up SAML SSO"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{data && (
|
{data && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||||
|
{(isAllowed) => (
|
||||||
<Switch
|
<Switch
|
||||||
id="enable-saml-sso"
|
id="enable-saml-sso"
|
||||||
onCheckedChange={(value) => handleSamlSSOToggle(value)}
|
onCheckedChange={(value) => handleSamlSSOToggle(value)}
|
||||||
isChecked={data ? data.isActive : false}
|
isChecked={data ? data.isActive : false}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Enable SAML SSO
|
Enable SAML SSO
|
||||||
</Switch>
|
</Switch>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-mineshaft-400 text-sm">SSO identifier</h3>
|
<h3 className="text-mineshaft-400 text-sm">SSO identifier</h3>
|
||||||
<p className="text-gray-400 text-md">{(data && data._id !== "") ? data._id : "-"}</p>
|
<p className="text-gray-400 text-md">{data && data._id !== "" ? data._id : "-"}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-mineshaft-400 text-sm">Type</h3>
|
<h3 className="text-mineshaft-400 text-sm">Type</h3>
|
||||||
<p className="text-gray-400 text-md">{(data && data.authProvider !== "") ? ssoAuthProviderMap[data.authProvider] : "-"}</p>
|
<p className="text-gray-400 text-md">
|
||||||
|
{data && data.authProvider !== "" ? ssoAuthProviderMap[data.authProvider] : "-"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-mineshaft-400 text-sm">Entrypoint</h3>
|
<h3 className="text-mineshaft-400 text-sm">Entrypoint</h3>
|
||||||
<p className="text-gray-400 text-md">{(data && data.entryPoint !== "") ? data.entryPoint : "-"}</p>
|
<p className="text-gray-400 text-md">
|
||||||
|
{data && data.entryPoint !== "" ? data.entryPoint : "-"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-mineshaft-400 text-sm">Issuer</h3>
|
<h3 className="text-mineshaft-400 text-sm">Issuer</h3>
|
||||||
<p className="text-gray-400 text-md">{(data && data.issuer !== "") ? data.issuer : "-"}</p>
|
<p className="text-gray-400 text-md">{data && data.issuer !== "" ? data.issuer : "-"}</p>
|
||||||
</div>
|
</div>
|
||||||
<SSOModal
|
<SSOModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
|
|||||||
+1
-3
@@ -6,10 +6,8 @@ export const OrgGeneralTab = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<OrgNameChangeSection />
|
<OrgNameChangeSection />
|
||||||
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
|
|
||||||
<OrgServiceAccountsTable />
|
<OrgServiceAccountsTable />
|
||||||
</div>
|
|
||||||
<OrgIncidentContactsSection />
|
<OrgIncidentContactsSection />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+11
-26
@@ -3,17 +3,9 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useOrganization } from "@app/context";
|
import { useOrganization } from "@app/context";
|
||||||
import {
|
import { useAddIncidentContact } from "@app/hooks/api";
|
||||||
useAddIncidentContact
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -36,12 +28,10 @@ export const AddOrgIncidentContactModal = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { data: serverDetails } = useFetchServerStatus()
|
const { data: serverDetails } = useFetchServerStatus();
|
||||||
const {
|
const { control, handleSubmit, reset } = useForm<TAddContactForm>({
|
||||||
control,
|
resolver: yupResolver(addContactFormSchema)
|
||||||
handleSubmit,
|
});
|
||||||
reset
|
|
||||||
} = useForm<TAddContactForm>({ resolver: yupResolver(addContactFormSchema) });
|
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = useAddIncidentContact();
|
const { mutateAsync, isLoading } = useAddIncidentContact();
|
||||||
|
|
||||||
@@ -59,7 +49,7 @@ export const AddOrgIncidentContactModal = ({
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (serverDetails?.emailConfigured){
|
if (serverDetails?.emailConfigured) {
|
||||||
handlePopUpClose("addContact");
|
handlePopUpClose("addContact");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +61,7 @@ export const AddOrgIncidentContactModal = ({
|
|||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -96,13 +86,8 @@ export const AddOrgIncidentContactModal = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center space-x-4">
|
||||||
<Button
|
<Button size="sm" type="submit" isLoading={isLoading} isDisabled={isLoading}>
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isLoading}
|
|
||||||
isDisabled={isLoading}
|
|
||||||
>
|
|
||||||
Add Incident Contact
|
Add Incident Contact
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -117,4 +102,4 @@ export const AddOrgIncidentContactModal = ({
|
|||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+17
-6
@@ -2,15 +2,17 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import {
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
Button
|
import { Button } from "@app/components/v2";
|
||||||
} from "@app/components/v2";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
|
|
||||||
import { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal";
|
import { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal";
|
||||||
import { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
|
import { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
|
||||||
|
|
||||||
export const OrgIncidentContactsSection = () => {
|
export const OrgIncidentContactsSection = withPermission(
|
||||||
|
() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"addContact"
|
"addContact"
|
||||||
@@ -22,14 +24,22 @@ export const OrgIncidentContactsSection = () => {
|
|||||||
<p className="min-w-max text-xl font-semibold">
|
<p className="min-w-max text-xl font-semibold">
|
||||||
{t("section.incident.incident-contacts")}
|
{t("section.incident.incident-contacts")}
|
||||||
</p>
|
</p>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Create}
|
||||||
|
a={OrgPermissionSubjects.IncidentAccount}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => handlePopUpOpen("addContact")}
|
onClick={() => handlePopUpOpen("addContact")}
|
||||||
>
|
>
|
||||||
Add contact
|
Add contact
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<OrgIncidentContactsTable />
|
<OrgIncidentContactsTable />
|
||||||
<AddOrgIncidentContactModal
|
<AddOrgIncidentContactModal
|
||||||
@@ -39,5 +49,6 @@ export const OrgIncidentContactsSection = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.IncidentAccount }
|
||||||
|
);
|
||||||
|
|||||||
+10
-1
@@ -3,6 +3,7 @@ import { faContactBook, faMagnifyingGlass, faTrash } from "@fortawesome/free-sol
|
|||||||
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";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
@@ -17,7 +18,7 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api";
|
import { useDeleteIncidentContact, useGetOrgIncidentContact } from "@app/hooks/api";
|
||||||
|
|
||||||
@@ -83,13 +84,21 @@ export const OrgIncidentContactsTable = () => {
|
|||||||
<Tr key={email}>
|
<Tr key={email}>
|
||||||
<Td className="w-full">{email}</Td>
|
<Td className="w-full">{email}</Td>
|
||||||
<Td className="mr-4">
|
<Td className="mr-4">
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgGeneralPermissionActions.Delete}
|
||||||
|
an={OrgPermissionSubjects.IncidentAccount}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
<IconButton
|
<IconButton
|
||||||
ariaLabel="delete"
|
ariaLabel="delete"
|
||||||
colorSchema="danger"
|
colorSchema="danger"
|
||||||
onClick={() => handlePopUpOpen("removeContact", { email })}
|
onClick={() => handlePopUpOpen("removeContact", { email })}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrash} />
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+21
-11
@@ -4,8 +4,10 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
import { useRenameOrg } from "@app/hooks/api";
|
import { useRenameOrg } from "@app/hooks/api";
|
||||||
|
|
||||||
const formSchema = yup.object({
|
const formSchema = yup.object({
|
||||||
@@ -14,14 +16,13 @@ const formSchema = yup.object({
|
|||||||
|
|
||||||
type FormData = yup.InferType<typeof formSchema>;
|
type FormData = yup.InferType<typeof formSchema>;
|
||||||
|
|
||||||
export const OrgNameChangeSection = (): JSX.Element => {
|
export const OrgNameChangeSection = withPermission(
|
||||||
|
(): JSX.Element => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const {
|
const { handleSubmit, control, reset } = useForm<FormData>({
|
||||||
handleSubmit,
|
resolver: yupResolver(formSchema)
|
||||||
control,
|
});
|
||||||
reset
|
|
||||||
} = useForm<FormData>({ resolver: yupResolver(formSchema) });
|
|
||||||
const { mutateAsync, isLoading } = useRenameOrg();
|
const { mutateAsync, isLoading } = useRenameOrg();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -54,9 +55,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
|
||||||
>
|
>
|
||||||
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
|
<p className="text-xl font-semibold text-mineshaft-100 mb-4">Organization name</p>
|
||||||
Organization name
|
|
||||||
</p>
|
|
||||||
<div className="mb-2 max-w-md">
|
<div className="mb-2 max-w-md">
|
||||||
<Controller
|
<Controller
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
@@ -69,14 +68,25 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
|||||||
name="name"
|
name="name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
|
||||||
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
variant="outline_bg"
|
variant="outline_bg"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
{
|
||||||
|
action: OrgGeneralPermissionActions.Read,
|
||||||
|
subject: OrgPermissionSubjects.Settings,
|
||||||
|
containerClassName: "mb-4"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
+20
-5
@@ -34,7 +34,13 @@ import {
|
|||||||
THead,
|
THead,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization, useWorkspace } from "@app/context";
|
import {
|
||||||
|
OrgGeneralPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useWorkspace
|
||||||
|
} from "@app/context";
|
||||||
|
import { withPermission } from "@app/hoc";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
// useCreateServiceAccount,
|
// useCreateServiceAccount,
|
||||||
@@ -62,7 +68,8 @@ import // Controller,
|
|||||||
|
|
||||||
// type TAddServiceAccountForm = yup.InferType<typeof addServiceAccountFormSchema>;
|
// type TAddServiceAccountForm = yup.InferType<typeof addServiceAccountFormSchema>;
|
||||||
|
|
||||||
export const OrgServiceAccountsTable = () => {
|
export const OrgServiceAccountsTable = withPermission(
|
||||||
|
() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
@@ -139,7 +146,9 @@ export const OrgServiceAccountsTable = () => {
|
|||||||
|
|
||||||
const filteredServiceAccounts = useMemo(
|
const filteredServiceAccounts = useMemo(
|
||||||
() =>
|
() =>
|
||||||
serviceAccounts.filter(({ name }) => name.toLowerCase().includes(searchServiceAccountFilter)),
|
serviceAccounts.filter(({ name }) =>
|
||||||
|
name.toLowerCase().includes(searchServiceAccountFilter)
|
||||||
|
),
|
||||||
[serviceAccounts, searchServiceAccountFilter]
|
[serviceAccounts, searchServiceAccountFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -274,7 +283,7 @@ export const OrgServiceAccountsTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
|
||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Service Accounts</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Service Accounts</p>
|
||||||
<Button
|
<Button
|
||||||
@@ -371,4 +380,10 @@ export const OrgServiceAccountsTable = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
{
|
||||||
|
action: OrgGeneralPermissionActions.Read,
|
||||||
|
subject: OrgPermissionSubjects.Settings,
|
||||||
|
containerClassName: "mb-4"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,33 +1,14 @@
|
|||||||
import { Fragment } from "react"
|
import { Fragment } from "react";
|
||||||
import { Tab } from "@headlessui/react"
|
import { Tab } from "@headlessui/react";
|
||||||
|
|
||||||
import { useOrganization,useUser } from "@app/context";
|
|
||||||
import {
|
|
||||||
useGetOrgUsers
|
|
||||||
} from "@app/hooks/api";
|
|
||||||
|
|
||||||
import { OrgAuthTab } from "../OrgAuthTab";
|
import { OrgAuthTab } from "../OrgAuthTab";
|
||||||
import { OrgGeneralTab } from "../OrgGeneralTab";
|
import { OrgGeneralTab } from "../OrgGeneralTab";
|
||||||
|
|
||||||
export const OrgTabGroup = () => {
|
const tabs = [
|
||||||
const { currentOrg } = useOrganization();
|
|
||||||
const { user } = useUser();
|
|
||||||
const { data } = useGetOrgUsers(currentOrg?._id ?? "");
|
|
||||||
|
|
||||||
const isRoleSufficient = data?.some((orgUser) => {
|
|
||||||
return orgUser.role !== "member" && orgUser.user._id === user._id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ name: "General", key: "tab-org-general" },
|
{ name: "General", key: "tab-org-general" },
|
||||||
];
|
|
||||||
|
|
||||||
if (isRoleSufficient) {
|
|
||||||
tabs.push(
|
|
||||||
{ name: "Authentication", key: "tab-org-auth" }
|
{ name: "Authentication", key: "tab-org-auth" }
|
||||||
);
|
];
|
||||||
}
|
export const OrgTabGroup = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full">
|
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full">
|
||||||
@@ -36,7 +17,9 @@ export const OrgTabGroup = () => {
|
|||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"}`}
|
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
|
||||||
|
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{tab.name}
|
{tab.name}
|
||||||
</button>
|
</button>
|
||||||
@@ -48,12 +31,10 @@ export const OrgTabGroup = () => {
|
|||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<OrgGeneralTab />
|
<OrgGeneralTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
{isRoleSufficient && (
|
|
||||||
<Tab.Panel>
|
<Tab.Panel>
|
||||||
<OrgAuthTab />
|
<OrgAuthTab />
|
||||||
</Tab.Panel>
|
</Tab.Panel>
|
||||||
)}
|
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user