mirror of
https://github.com/Infisical/infisical.git
synced 2025-09-05 21:11:19 +00:00
Compare commits
5 Commits
navbar-org
...
access-req
Author | SHA1 | Date | |
---|---|---|---|
|
61ca617616 | ||
|
3fe41f81fe | ||
|
621bfe3e60 | ||
|
67ec00d46b | ||
|
d5043fdba4 |
@@ -17,8 +17,8 @@ export type SubscriptionPlan = {
|
||||
rbac: boolean;
|
||||
secretVersioning: boolean;
|
||||
slug: string;
|
||||
secretApproval: string;
|
||||
secretRotation: string;
|
||||
secretApproval: boolean;
|
||||
secretRotation: boolean;
|
||||
tier: number;
|
||||
workspaceLimit: number;
|
||||
workspacesUsed: number;
|
||||
|
60
frontend/src/hooks/usePathAccessPolicies.tsx
Normal file
60
frontend/src/hooks/usePathAccessPolicies.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useSubscription, useWorkspace } from "@app/context";
|
||||
import { useGetAccessApprovalPolicies } from "@app/hooks/api";
|
||||
|
||||
const matchesPath = (folderPath: string, pattern: string) => {
|
||||
const normalizedPath = folderPath === "/" ? "/" : folderPath.replace(/\/$/, "");
|
||||
const normalizedPattern = pattern === "/" ? "/" : pattern.replace(/\/$/, "");
|
||||
|
||||
if (normalizedPath === normalizedPattern) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (normalizedPattern.endsWith("/**")) {
|
||||
const basePattern = normalizedPattern.slice(0, -3); // Remove "/**"
|
||||
|
||||
// Handle root wildcard "/**"
|
||||
if (basePattern === "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if path starts with the base pattern
|
||||
if (normalizedPath === basePattern) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if path is a subdirectory of the base pattern
|
||||
return normalizedPath.startsWith(`${basePattern}/`);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
type Params = {
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
};
|
||||
|
||||
export const usePathAccessPolicies = ({ secretPath, environment }: Params) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { subscription } = useSubscription();
|
||||
const { data: policies } = useGetAccessApprovalPolicies({
|
||||
projectSlug: currentWorkspace.slug,
|
||||
options: {
|
||||
enabled: subscription.secretApproval
|
||||
}
|
||||
});
|
||||
|
||||
return useMemo(() => {
|
||||
const pathPolicies = policies?.filter(
|
||||
(policy) =>
|
||||
policy.environment.slug === environment && matchesPath(secretPath, policy.secretPath)
|
||||
);
|
||||
|
||||
return {
|
||||
hasPathPolicies: subscription.secretApproval && Boolean(pathPolicies?.length),
|
||||
pathPolicies
|
||||
};
|
||||
}, [secretPath, environment, policies, subscription.secretApproval]);
|
||||
};
|
@@ -79,10 +79,14 @@ type TSecretPermissionForm = z.infer<typeof secretPermissionSchema>;
|
||||
export const SpecificPrivilegeSecretForm = ({
|
||||
privilege,
|
||||
policies,
|
||||
onClose
|
||||
onClose,
|
||||
selectedActions = [],
|
||||
secretPath: initialSecretPath
|
||||
}: {
|
||||
privilege?: TProjectUserPrivilege;
|
||||
policies?: TAccessApprovalPolicy[];
|
||||
selectedActions?: ProjectPermissionActions[];
|
||||
secretPath?: string;
|
||||
onClose?: () => void;
|
||||
}) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -126,10 +130,11 @@ export const SpecificPrivilegeSecretForm = ({
|
||||
}
|
||||
: {
|
||||
environmentSlug: currentWorkspace.environments?.[0]?.slug,
|
||||
read: false,
|
||||
edit: false,
|
||||
create: false,
|
||||
delete: false,
|
||||
secretPath: initialSecretPath,
|
||||
read: selectedActions.includes(ProjectPermissionActions.Read),
|
||||
edit: selectedActions.includes(ProjectPermissionActions.Edit),
|
||||
create: selectedActions.includes(ProjectPermissionActions.Create),
|
||||
delete: selectedActions.includes(ProjectPermissionActions.Delete),
|
||||
temporaryAccess: {
|
||||
isTemporary: false
|
||||
}
|
||||
@@ -281,6 +286,8 @@ export const SpecificPrivilegeSecretForm = ({
|
||||
isDisabled={isMemberEditDisabled}
|
||||
className="w-full bg-mineshaft-900 hover:bg-mineshaft-800"
|
||||
onValueChange={(e) => onChange(e)}
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{currentWorkspace?.environments?.map(({ slug, id, name }) => (
|
||||
<SelectItem value={slug} key={id}>
|
||||
@@ -309,6 +316,8 @@ export const SpecificPrivilegeSecretForm = ({
|
||||
className="w-full hover:bg-mineshaft-800"
|
||||
placeholder="Select a secret path"
|
||||
onValueChange={(e) => field.onChange(e)}
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{selectablePaths.map((path) => (
|
||||
<SelectItem value={path} key={path}>
|
||||
@@ -636,6 +645,7 @@ export const SpecificPrivilegeSecretForm = ({
|
||||
{!!policies && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline_bg"
|
||||
isLoading={privilegeForm.formState.isSubmitting || requestAccess.isPending}
|
||||
isDisabled={
|
||||
isMemberEditDisabled ||
|
||||
@@ -647,7 +657,7 @@ export const SpecificPrivilegeSecretForm = ({
|
||||
className="mt-4"
|
||||
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
|
||||
>
|
||||
Request access
|
||||
Request Access
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
|
@@ -1,15 +1,19 @@
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { ProjectPermissionActions } from "@app/context";
|
||||
import { TAccessApprovalPolicy } from "@app/hooks/api/types";
|
||||
import { SpecificPrivilegeSecretForm } from "@app/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection";
|
||||
|
||||
export const RequestAccessModal = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
policies
|
||||
policies,
|
||||
...props
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
policies: TAccessApprovalPolicy[];
|
||||
selectedActions?: ProjectPermissionActions[];
|
||||
secretPath?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
@@ -18,7 +22,11 @@ export const RequestAccessModal = ({
|
||||
title="Request Access"
|
||||
subTitle="Request access to any secrets and resources based on the predefined policies."
|
||||
>
|
||||
<SpecificPrivilegeSecretForm onClose={() => onOpenChange(false)} policies={policies} />
|
||||
<SpecificPrivilegeSecretForm
|
||||
onClose={() => onOpenChange(false)}
|
||||
policies={policies}
|
||||
{...props}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faArrowDown, faArrowUp } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faArrowDown, faArrowUp, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -10,10 +10,12 @@ import { twMerge } from "tailwind-merge";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
ContentLoader,
|
||||
Modal,
|
||||
ModalContent,
|
||||
PageHeader,
|
||||
Pagination,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
@@ -46,7 +48,9 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard";
|
||||
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
||||
import { useGetFolderCommitsCount } from "@app/hooks/api/folderCommits";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { usePathAccessPolicies } from "@app/hooks/usePathAccessPolicies";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||
import { RequestAccessModal } from "@app/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/RequestAccessModal";
|
||||
import { SecretRotationListView } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretRotationListView";
|
||||
|
||||
import { SecretTableResourceCount } from "../OverviewPage/components/SecretTableResourceCount";
|
||||
@@ -114,7 +118,10 @@ const Page = () => {
|
||||
|
||||
const [snapshotId, setSnapshotId] = useState<string | null>(null);
|
||||
const isRollbackMode = Boolean(snapshotId);
|
||||
const { popUp, handlePopUpClose, handlePopUpToggle } = usePopUp(["snapshots"] as const);
|
||||
const { popUp, handlePopUpClose, handlePopUpToggle, handlePopUpOpen } = usePopUp([
|
||||
"snapshots",
|
||||
"requestAccess"
|
||||
] as const);
|
||||
|
||||
// env slug
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
@@ -132,6 +139,26 @@ const Page = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const canEditSecrets = permission.can(
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})
|
||||
);
|
||||
|
||||
const canDeleteSecrets = permission.can(
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})
|
||||
);
|
||||
|
||||
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
@@ -257,6 +284,8 @@ const Page = () => {
|
||||
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags) ? workspaceId : ""
|
||||
);
|
||||
|
||||
const { pathPolicies, hasPathPolicies } = usePathAccessPolicies({ secretPath, environment });
|
||||
|
||||
const { data: boardPolicy } = useGetSecretApprovalPolicyOfABoard({
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -476,8 +505,55 @@ const Page = () => {
|
||||
setFilter(defaultFilterState);
|
||||
setDebouncedSearchFilter("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto flex max-w-7xl flex-col text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
<PageHeader
|
||||
title={
|
||||
currentWorkspace.environments.find((env) => env.slug === environment)?.name ?? environment
|
||||
}
|
||||
description={
|
||||
<p className="text-md text-bunker-300">
|
||||
Inject your secrets using
|
||||
<a
|
||||
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
|
||||
href="https://infisical.com/docs/cli/overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Infisical CLI
|
||||
</a>
|
||||
,
|
||||
<a
|
||||
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
|
||||
href="https://infisical.com/docs/documentation/getting-started/api"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Infisical API
|
||||
</a>
|
||||
,
|
||||
<a
|
||||
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
|
||||
href="https://infisical.com/docs/sdks/overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Infisical SDKs
|
||||
</a>
|
||||
, and
|
||||
<a
|
||||
className="ml-1 text-mineshaft-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
|
||||
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
more
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<SecretV2MigrationSection />
|
||||
{!isRollbackMode ? (
|
||||
<>
|
||||
@@ -500,8 +576,15 @@ const Page = () => {
|
||||
importedBy={importedBy}
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
isPITEnabled={isPITEnabled}
|
||||
hasPathPolicies={hasPathPolicies}
|
||||
onRequestAccess={(params) => handlePopUpOpen("requestAccess", params)}
|
||||
/>
|
||||
<div className="thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md rounded-b-none bg-mineshaft-800 text-left text-sm text-bunker-300">
|
||||
<div
|
||||
className={twMerge(
|
||||
"thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md bg-mineshaft-800 text-left text-sm text-bunker-300",
|
||||
isNotEmpty && "rounded-b-none"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col" id="dashboard">
|
||||
{isNotEmpty && (
|
||||
<div
|
||||
@@ -548,6 +631,62 @@ const Page = () => {
|
||||
<div className="flex-grow px-4 py-2">Value</div>
|
||||
</div>
|
||||
)}
|
||||
{hasPathPolicies &&
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
(!canReadSecret ? (
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex border-l-2 border-l-primary bg-mineshaft-700 px-4 py-2",
|
||||
isNotEmpty ? "border-b border-b-mineshaft-600" : ""
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center text-sm">
|
||||
<FontAwesomeIcon
|
||||
icon={faInfoCircle}
|
||||
className="ml-[0.15rem] mr-[1.65rem] text-primary"
|
||||
/>
|
||||
<span>You do not have permission to read secrets in this folder</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
size="xs"
|
||||
className="ml-auto"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("requestAccess", [ProjectPermissionActions.Read])
|
||||
}
|
||||
>
|
||||
Request Access
|
||||
</Button>
|
||||
</div>
|
||||
) : !canEditSecrets || !canDeleteSecrets ? (
|
||||
<div className="flex border-b border-l-2 border-b-mineshaft-600 border-l-primary bg-mineshaft-700 px-4 py-2">
|
||||
<div className="flex items-center text-sm">
|
||||
<FontAwesomeIcon
|
||||
icon={faInfoCircle}
|
||||
className="ml-[0.15rem] mr-[1.65rem] text-primary"
|
||||
/>
|
||||
<span>
|
||||
You do not have permission to {!canEditSecrets ? "edit" : ""}
|
||||
{!canEditSecrets && !canDeleteSecrets ? " or " : ""}
|
||||
{!canDeleteSecrets ? "delete" : ""} secrets in this folder
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
size="xs"
|
||||
className="ml-auto"
|
||||
onClick={() =>
|
||||
handlePopUpOpen("requestAccess", [
|
||||
...(!canEditSecrets ? [ProjectPermissionActions.Edit] : []),
|
||||
...(!canDeleteSecrets ? [ProjectPermissionActions.Delete] : [])
|
||||
])
|
||||
}
|
||||
>
|
||||
Request Access
|
||||
</Button>
|
||||
</div>
|
||||
) : null)}
|
||||
|
||||
{canReadSecretImports && Boolean(imports?.length) && (
|
||||
<SecretImportListView
|
||||
searchTerm={debouncedSearchFilter}
|
||||
@@ -636,6 +775,17 @@ const Page = () => {
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{!!pathPolicies && (
|
||||
<RequestAccessModal
|
||||
policies={pathPolicies}
|
||||
isOpen={popUp.requestAccess.isOpen}
|
||||
onOpenChange={() => {
|
||||
handlePopUpClose("requestAccess");
|
||||
}}
|
||||
selectedActions={popUp.requestAccess.data}
|
||||
secretPath={pathPolicies?.[0]?.secretPath}
|
||||
/>
|
||||
)}
|
||||
<SecretDropzone
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
|
@@ -46,6 +46,7 @@ import {
|
||||
DropdownSubMenuTrigger,
|
||||
IconButton,
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
@@ -53,11 +54,13 @@ import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useSubscription,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import {
|
||||
ProjectPermissionCommitsActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSecretRotationActions
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
@@ -127,6 +130,8 @@ type Props = {
|
||||
}[];
|
||||
}[];
|
||||
isPITEnabled: boolean;
|
||||
onRequestAccess: (actions: ProjectPermissionActions[]) => void;
|
||||
hasPathPolicies: boolean;
|
||||
};
|
||||
|
||||
export const ActionBar = ({
|
||||
@@ -147,7 +152,9 @@ export const ActionBar = ({
|
||||
protectedBranchPolicyName,
|
||||
importedBy,
|
||||
isPITEnabled = false,
|
||||
usedBySecretSyncs
|
||||
usedBySecretSyncs,
|
||||
onRequestAccess,
|
||||
hasPathPolicies
|
||||
}: Props) => {
|
||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||
"addFolder",
|
||||
@@ -159,7 +166,8 @@ export const ActionBar = ({
|
||||
"misc",
|
||||
"upgradePlan",
|
||||
"replicateFolder",
|
||||
"confirmUpload"
|
||||
"confirmUpload",
|
||||
"requestAccess"
|
||||
] as const);
|
||||
const isProtectedBranch = Boolean(protectedBranchPolicyName);
|
||||
const { subscription } = useSubscription();
|
||||
@@ -180,6 +188,7 @@ export const ActionBar = ({
|
||||
const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length);
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const handleFolderCreate = async (folderName: string, description: string | null) => {
|
||||
try {
|
||||
@@ -807,27 +816,50 @@ export const ActionBar = ({
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => openPopUp(PopUpNames.CreateSecretForm)}
|
||||
className="h-10 rounded-r-none"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Secret
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
{hasPathPolicies ? (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() =>
|
||||
permission.can(
|
||||
ProjectPermissionSecretActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})
|
||||
)
|
||||
? openPopUp(PopUpNames.CreateSecretForm)
|
||||
: handlePopUpOpen("requestAccess", [ProjectPermissionActions.Create])
|
||||
}
|
||||
className="h-10 rounded-r-none"
|
||||
>
|
||||
Add Secret
|
||||
</Button>
|
||||
) : (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => openPopUp(PopUpNames.CreateSecretForm)}
|
||||
className="h-10 rounded-r-none"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Secret
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
<DropdownMenu
|
||||
open={popUp.misc.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("misc", isOpen)}
|
||||
@@ -1166,6 +1198,27 @@ export const ActionBar = ({
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal
|
||||
isOpen={popUp?.requestAccess?.isOpen}
|
||||
onOpenChange={(open) => handlePopUpToggle("requestAccess", open)}
|
||||
>
|
||||
<ModalContent title="Access Restricted">
|
||||
<p className="mb-2 text-bunker-300">You do not have permission to perform this action.</p>
|
||||
<p className="text-bunker-300">Request access to perform this action in this folder.</p>
|
||||
<div className="mt-8 flex items-center gap-4">
|
||||
<ModalClose asChild>
|
||||
<Button onClick={() => onRequestAccess(popUp?.requestAccess.data)}>
|
||||
Request Access
|
||||
</Button>
|
||||
</ModalClose>
|
||||
<ModalClose asChild>
|
||||
<Button variant="plain" colorSchema="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -262,7 +262,7 @@ export const SecretDropzone = ({
|
||||
className={twMerge(
|
||||
"relative mx-0.5 mb-4 mt-4 flex cursor-pointer items-center justify-center rounded-md bg-mineshaft-900 px-2 py-4 text-sm text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100",
|
||||
isDragActive && "opacity-100",
|
||||
!isSmaller && "mx-auto w-full max-w-3xl flex-col space-y-4 py-20",
|
||||
!isSmaller && "mx-auto mt-40 w-full max-w-3xl flex-col space-y-4 py-20",
|
||||
isLoading && "bg-bunker-800"
|
||||
)}
|
||||
>
|
||||
|
Reference in New Issue
Block a user