mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-18 00:51:15 +00:00
@@ -12,6 +12,7 @@ import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||
import { Checkbox, ContentLoader, Pagination, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
@@ -37,7 +38,7 @@ import { ActionBar } from "./components/ActionBar";
|
||||
import { CreateSecretForm } from "./components/CreateSecretForm";
|
||||
import { PitDrawer } from "./components/PitDrawer";
|
||||
import { SecretDropzone } from "./components/SecretDropzone";
|
||||
import { SecretListView } from "./components/SecretListView";
|
||||
import { SecretListView, SecretNoAccessListView } from "./components/SecretListView";
|
||||
import { SnapshotView } from "./components/SnapshotView";
|
||||
import {
|
||||
StoreProvider,
|
||||
@@ -83,8 +84,24 @@ const SecretMainPageContent = () => {
|
||||
const secretPath = (router.query.secretPath as string) || "/";
|
||||
const canReadSecret = permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})
|
||||
);
|
||||
|
||||
const canReadSecretImports = permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
|
||||
);
|
||||
|
||||
const canReadDynamicSecret = permission.can(
|
||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
|
||||
);
|
||||
|
||||
const canDoReadRollback = permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
@@ -93,11 +110,12 @@ const SecretMainPageContent = () => {
|
||||
const defaultFilterState = {
|
||||
tags: {},
|
||||
searchFilter: (router.query.searchFilter as string) || "",
|
||||
// these should always be on by default for the UI, they will be disabled for the query below based off permissions
|
||||
include: {
|
||||
[RowType.Folder]: true,
|
||||
[RowType.Import]: canReadSecret,
|
||||
[RowType.DynamicSecret]: canReadSecret,
|
||||
[RowType.Secret]: canReadSecret
|
||||
[RowType.Import]: true,
|
||||
[RowType.DynamicSecret]: true,
|
||||
[RowType.Secret]: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,19 +123,6 @@ const SecretMainPageContent = () => {
|
||||
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(filter.searchFilter);
|
||||
const [filterHistory, setFilterHistory] = useState<Map<string, Filter>>(new Map());
|
||||
|
||||
// change filters if permissions change at different paths/env
|
||||
useEffect(() => {
|
||||
setFilter((prev) => ({
|
||||
...prev,
|
||||
include: {
|
||||
[RowType.Folder]: true,
|
||||
[RowType.Import]: canReadSecret,
|
||||
[RowType.DynamicSecret]: canReadSecret,
|
||||
[RowType.Secret]: canReadSecret
|
||||
}
|
||||
}));
|
||||
}, [canReadSecret]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isWorkspaceLoading &&
|
||||
@@ -145,9 +150,9 @@ const SecretMainPageContent = () => {
|
||||
orderBy,
|
||||
search: debouncedSearchFilter,
|
||||
orderDirection,
|
||||
includeImports: canReadSecret && filter.include.import,
|
||||
includeImports: canReadSecretImports && filter.include.import,
|
||||
includeFolders: filter.include.folder,
|
||||
includeDynamicSecrets: canReadSecret && filter.include.dynamic,
|
||||
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
|
||||
includeSecrets: canReadSecret && filter.include.secret,
|
||||
tags: filter.tags
|
||||
});
|
||||
@@ -210,8 +215,20 @@ const SecretMainPageContent = () => {
|
||||
isPaused: !canDoReadRollback
|
||||
});
|
||||
|
||||
const noAccessSecretCount = Math.max(
|
||||
(page * perPage > totalCount ? totalCount % perPage : perPage) -
|
||||
(imports?.length || 0) -
|
||||
(folders?.length || 0) -
|
||||
(secrets?.length || 0) -
|
||||
(dynamicSecrets?.length || 0),
|
||||
0
|
||||
);
|
||||
const isNotEmpty = Boolean(
|
||||
secrets?.length || folders?.length || imports?.length || dynamicSecrets?.length
|
||||
secrets?.length ||
|
||||
folders?.length ||
|
||||
imports?.length ||
|
||||
dynamicSecrets?.length ||
|
||||
noAccessSecretCount
|
||||
);
|
||||
|
||||
const handleSortToggle = () =>
|
||||
@@ -330,7 +347,6 @@ const SecretMainPageContent = () => {
|
||||
setFilter(defaultFilterState);
|
||||
setDebouncedSearchFilter("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
<SecretV2MigrationSection />
|
||||
@@ -411,49 +427,53 @@ const SecretMainPageContent = () => {
|
||||
</div>
|
||||
<div className="flex-grow px-4 py-2">Value</div>
|
||||
</div>
|
||||
)}
|
||||
{canReadSecret && imports?.length && (
|
||||
<SecretImportListView
|
||||
searchTerm={debouncedSearchFilter}
|
||||
secretImports={imports}
|
||||
isFetching={isDetailsFetching}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
importedSecrets={importedSecrets}
|
||||
/>
|
||||
)}
|
||||
{folders?.length && (
|
||||
<FolderListView
|
||||
folders={folders}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
onNavigateToFolder={handleResetFilter}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && dynamicSecrets?.length && (
|
||||
<DynamicSecretListView
|
||||
environment={environment}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
dynamicSecrets={dynamicSecrets}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && secrets?.length && (
|
||||
<SecretListView
|
||||
secrets={secrets}
|
||||
tags={tags}
|
||||
isVisible={isVisible}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
)}
|
||||
{!canReadSecret && folders?.length === 0 && <PermissionDeniedBanner />}
|
||||
)}
|
||||
{canReadSecretImports && Boolean(imports?.length) && (
|
||||
<SecretImportListView
|
||||
searchTerm={debouncedSearchFilter}
|
||||
secretImports={imports}
|
||||
isFetching={isDetailsFetching}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
importedSecrets={importedSecrets}
|
||||
/>
|
||||
)}
|
||||
{Boolean(folders?.length) && (
|
||||
<FolderListView
|
||||
folders={folders}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
onNavigateToFolder={handleResetFilter}
|
||||
/>
|
||||
)}
|
||||
{canReadDynamicSecret && Boolean(dynamicSecrets?.length) && (
|
||||
<DynamicSecretListView
|
||||
environment={environment}
|
||||
projectSlug={projectSlug}
|
||||
secretPath={secretPath}
|
||||
dynamicSecrets={dynamicSecrets}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && Boolean(secrets?.length) && (
|
||||
<SecretListView
|
||||
secrets={secrets}
|
||||
tags={tags}
|
||||
isVisible={isVisible}
|
||||
environment={environment}
|
||||
workspaceId={workspaceId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && <SecretNoAccessListView count={noAccessSecretCount} />}
|
||||
{!canReadSecret &&
|
||||
!canReadDynamicSecret &&
|
||||
!canReadSecretImports &&
|
||||
folders?.length === 0 && <PermissionDeniedBanner />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!isDetailsLoading && totalCount > 0 && (
|
||||
<Pagination
|
||||
startAdornment={
|
||||
|
||||
@@ -47,8 +47,8 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
@@ -123,12 +123,6 @@ export const ActionBar = ({
|
||||
const { reset: resetSelectedSecret } = useSelectedSecretActions();
|
||||
const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length);
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const shouldCheckFolderPermission = permission.rules.some((rule) =>
|
||||
(rule.subject as ProjectPermissionSub[]).includes(ProjectPermissionSub.SecretFolders)
|
||||
);
|
||||
|
||||
const handleFolderCreate = async (folderName: string) => {
|
||||
try {
|
||||
await createFolder({
|
||||
@@ -436,7 +430,12 @@ export const ActionBar = ({
|
||||
<div className="flex items-center">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -467,12 +466,7 @@ export const ActionBar = ({
|
||||
<div className="flex flex-col space-y-1 p-1.5">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(
|
||||
shouldCheckFolderPermission
|
||||
? ProjectPermissionSub.SecretFolders
|
||||
: ProjectPermissionSub.Secrets,
|
||||
{ environment, secretPath }
|
||||
)}
|
||||
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -491,8 +485,13 @@ export const ActionBar = ({
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
I={ProjectPermissionDynamicSecretActions.CreateRootCredential}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -516,7 +515,10 @@ export const ActionBar = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.SecretImports, {
|
||||
environment,
|
||||
secretPath
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -556,7 +558,12 @@ export const ActionBar = ({
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
renderTooltip
|
||||
allowedLabel="Move"
|
||||
>
|
||||
@@ -575,7 +582,12 @@ export const ActionBar = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
renderTooltip
|
||||
allowedLabel="Delete"
|
||||
>
|
||||
|
||||
+23
-11
@@ -26,7 +26,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useGetDynamicSecretLeases, useRevokeDynamicSecretLease } from "@app/hooks/api";
|
||||
import { DynamicSecretLeaseStatus } from "@app/hooks/api/dynamicSecretLease/types";
|
||||
@@ -60,7 +60,6 @@ export const DynamicSecretLease = ({
|
||||
path: secretPath,
|
||||
dynamicSecretName
|
||||
});
|
||||
|
||||
|
||||
const deleteDynamicSecretLease = useRevokeDynamicSecretLease();
|
||||
|
||||
@@ -140,8 +139,8 @@ export const DynamicSecretLease = ({
|
||||
<Td>
|
||||
<div className="flex items-center space-x-4">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Renew"
|
||||
>
|
||||
@@ -159,8 +158,8 @@ export const DynamicSecretLease = ({
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Delete"
|
||||
>
|
||||
@@ -179,8 +178,11 @@ export const DynamicSecretLease = ({
|
||||
</ProjectPermissionCan>
|
||||
{status === DynamicSecretLeaseStatus.FailedDeletion && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||
environment,
|
||||
secretPath
|
||||
})}
|
||||
renderTooltip
|
||||
allowedLabel="Force Delete. This action will remove the secret from internal storage, but it will remain in external systems."
|
||||
>
|
||||
@@ -209,9 +211,19 @@ export const DynamicSecretLease = ({
|
||||
</TableContainer>
|
||||
{!isLeaseLoading && Boolean(leases?.length) && (
|
||||
<div className="mt-6 flex items-center space-x-4">
|
||||
<Button onClick={onClickNewLease} size="xs">
|
||||
New Lease
|
||||
</Button>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||
environment,
|
||||
secretPath
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button onClick={onClickNewLease} size="xs" isDisabled={!isAllowed}>
|
||||
New Lease
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<Button onClick={onClose} variant="plain" colorSchema="secondary" size="xs">
|
||||
Close
|
||||
</Button>
|
||||
|
||||
+25
-15
@@ -18,7 +18,7 @@ import {
|
||||
Tag,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteDynamicSecret } from "@app/hooks/api";
|
||||
import {
|
||||
@@ -132,17 +132,27 @@ export const DynamicSecretListView = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 px-4 py-2">
|
||||
<Button
|
||||
size="xs"
|
||||
className="m-0 py-0.5 px-2 opacity-0 group-hover:opacity-100"
|
||||
isDisabled={isRevoking}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
handlePopUpOpen("createDynamicSecretLease", secret);
|
||||
}}
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Edit"
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
size="xs"
|
||||
className="m-0 py-0.5 px-2 opacity-0 group-hover:opacity-100"
|
||||
isDisabled={isRevoking || !isAllowed}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
handlePopUpOpen("createDynamicSecretLease", secret);
|
||||
}}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
|
||||
{secret.status === DynamicSecretStatus.FailedDeletion && (
|
||||
<Tooltip content="This action will remove the secret from internal storage, but it will remain in external systems. Use this option only after you've confirmed that your external leases are handled.">
|
||||
<Button
|
||||
@@ -165,8 +175,8 @@ export const DynamicSecretListView = ({
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
I={ProjectPermissionDynamicSecretActions.EditRootCredential}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Edit"
|
||||
>
|
||||
@@ -187,8 +197,8 @@ export const DynamicSecretListView = ({
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
I={ProjectPermissionDynamicSecretActions.DeleteRootCredential}
|
||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Delete"
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { DeleteActionModal, IconButton, Modal, ModalContent } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api";
|
||||
import { TSecretFolder } from "@app/hooks/api/secretFolders/types";
|
||||
@@ -33,11 +33,6 @@ export const FolderListView = ({
|
||||
"deleteFolder"
|
||||
] as const);
|
||||
const router = useRouter();
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const shouldCheckFolderPermission = permission.rules.some((rule) =>
|
||||
(rule.subject as ProjectPermissionSub[]).includes(ProjectPermissionSub.SecretFolders)
|
||||
);
|
||||
|
||||
const { mutateAsync: updateFolder } = useUpdateFolder();
|
||||
const { mutateAsync: deleteFolder } = useDeleteFolder();
|
||||
@@ -126,12 +121,7 @@ export const FolderListView = ({
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(
|
||||
shouldCheckFolderPermission
|
||||
? ProjectPermissionSub.SecretFolders
|
||||
: ProjectPermissionSub.Secrets,
|
||||
{ environment, secretPath }
|
||||
)}
|
||||
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Edit"
|
||||
>
|
||||
@@ -150,12 +140,7 @@ export const FolderListView = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(
|
||||
shouldCheckFolderPermission
|
||||
? ProjectPermissionSub.SecretFolders
|
||||
: ProjectPermissionSub.Secrets,
|
||||
{ environment, secretPath }
|
||||
)}
|
||||
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Delete"
|
||||
>
|
||||
|
||||
+6
-1
@@ -142,7 +142,12 @@ export const CopySecretsFromBoard = ({
|
||||
<div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
|
||||
@@ -250,7 +250,12 @@ export const SecretDropzone = ({
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<input
|
||||
@@ -287,7 +292,12 @@ export const SecretDropzone = ({
|
||||
{!isSmaller && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: "*",
|
||||
secretTags: ["*"]
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
|
||||
+7
-4
@@ -67,7 +67,7 @@ export const SecretImportItem = ({
|
||||
isReplicationExpand,
|
||||
importedSecrets = [],
|
||||
searchTerm = "",
|
||||
secretPath,
|
||||
secretPath = "/",
|
||||
environment,
|
||||
secretImport,
|
||||
onExpandReplicateSecrets: onExpandReplicate
|
||||
@@ -209,7 +209,7 @@ export const SecretImportItem = ({
|
||||
{isReplication && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.SecretImports, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Resync replicated secrets"
|
||||
>
|
||||
@@ -235,7 +235,10 @@ export const SecretImportItem = ({
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-4 py-2">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.SecretImports, {
|
||||
environment,
|
||||
secretPath: secretPath || "/"
|
||||
})}
|
||||
renderTooltip
|
||||
allowedLabel="Change order"
|
||||
>
|
||||
@@ -256,7 +259,7 @@ export const SecretImportItem = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.SecretImports, { environment, secretPath })}
|
||||
renderTooltip
|
||||
allowedLabel="Delete"
|
||||
>
|
||||
|
||||
+61
-15
@@ -84,26 +84,41 @@ export const SecretDetailSidebar = ({
|
||||
resolver: zodResolver(formSchema),
|
||||
values: secret
|
||||
});
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
const cannotEditSecret = permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
const isReadOnly =
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
) && cannotEditSecret;
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "tags"
|
||||
});
|
||||
|
||||
const secretKey = secret?.key || "";
|
||||
const selectedTags = watch("tags", []) || [];
|
||||
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
|
||||
(prev, curr) => ({ ...prev, [curr.id]: true }),
|
||||
{}
|
||||
);
|
||||
const selectTagSlugs = selectedTags.map((i) => i.slug);
|
||||
|
||||
const cannotEditSecret = permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})
|
||||
);
|
||||
const isReadOnly =
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})
|
||||
) && cannotEditSecret;
|
||||
|
||||
const overrideAction = watch("overrideAction");
|
||||
const isOverridden =
|
||||
@@ -194,7 +209,12 @@ export const SecretDetailSidebar = ({
|
||||
</FormControl>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
@@ -221,7 +241,12 @@ export const SecretDetailSidebar = ({
|
||||
<div className="mb-2 border-b border-mineshaft-600 pb-4">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
@@ -277,7 +302,12 @@ export const SecretDetailSidebar = ({
|
||||
<DropdownMenu>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -367,6 +397,7 @@ export const SecretDetailSidebar = ({
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faClock} />}
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
isDisabled={cannotEditSecret}
|
||||
>
|
||||
Create Reminder
|
||||
</Button>
|
||||
@@ -388,7 +419,12 @@ export const SecretDetailSidebar = ({
|
||||
render={({ field: { value, onChange, onBlur } }) => (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
@@ -450,7 +486,12 @@ export const SecretDetailSidebar = ({
|
||||
<div className="flex items-center space-x-4">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -465,7 +506,12 @@ export const SecretDetailSidebar = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretKey,
|
||||
secretTags: selectTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button colorSchema="danger" isDisabled={!isAllowed} onClick={onDeleteSecret}>
|
||||
|
||||
@@ -81,15 +81,6 @@ export const SecretItem = memo(
|
||||
}: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { permission } = useProjectPermission();
|
||||
const isReadOnly =
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
) &&
|
||||
permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
@@ -107,6 +98,8 @@ export const SecretItem = memo(
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const secretName = watch("key");
|
||||
|
||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||
const secretReminderNote = watch("reminderNote");
|
||||
|
||||
@@ -118,11 +111,33 @@ export const SecretItem = memo(
|
||||
(prev, curr) => ({ ...prev, [curr.id]: true }),
|
||||
{}
|
||||
);
|
||||
const selectedTagSlugs = selectedTags.map((i) => i.slug);
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "tags"
|
||||
});
|
||||
|
||||
const isReadOnly =
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: selectedTagSlugs
|
||||
})
|
||||
) &&
|
||||
permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: selectedTagSlugs
|
||||
})
|
||||
);
|
||||
|
||||
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
||||
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
||||
useEffect(() => {
|
||||
@@ -309,7 +324,12 @@ export const SecretItem = memo(
|
||||
<DropdownMenu>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: selectedTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuTrigger asChild disabled={!isAllowed}>
|
||||
@@ -384,7 +404,12 @@ export const SecretItem = memo(
|
||||
</DropdownMenu>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: selectedTagSlugs
|
||||
})}
|
||||
renderTooltip
|
||||
allowedLabel="Override"
|
||||
>
|
||||
@@ -440,7 +465,12 @@ export const SecretItem = memo(
|
||||
<Popover>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: selectedTagSlugs
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<PopoverTrigger asChild disabled={!isAllowed}>
|
||||
@@ -519,7 +549,12 @@ export const SecretItem = memo(
|
||||
</Tooltip>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName,
|
||||
secretTags: selectedTagSlugs
|
||||
})}
|
||||
renderTooltip
|
||||
allowedLabel="Delete"
|
||||
>
|
||||
|
||||
@@ -16,7 +16,6 @@ import { WsTag } from "@app/hooks/api/types";
|
||||
import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal";
|
||||
|
||||
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
|
||||
import { Filter } from "../../SecretMainPage.types";
|
||||
import { SecretDetailSidebar } from "./SecretDetaiSidebar";
|
||||
import { SecretItem } from "./SecretItem";
|
||||
import { FontAwesomeSpriteSymbols } from "./SecretListView.utils";
|
||||
@@ -31,16 +30,6 @@ type Props = {
|
||||
isProtectedBranch?: boolean;
|
||||
};
|
||||
|
||||
export const filterSecrets = (secrets: SecretV3RawSanitized[], filter: Filter) =>
|
||||
secrets.filter(({ key, value, tags }) => {
|
||||
const isTagFilterActive = Boolean(Object.keys(filter.tags).length);
|
||||
const searchTerm = filter.searchFilter.toLowerCase();
|
||||
return (
|
||||
(!isTagFilterActive || tags?.some(({ id }) => filter.tags?.[id])) &&
|
||||
(key.toLowerCase().includes(searchTerm) || value?.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
});
|
||||
|
||||
export const SecretListView = ({
|
||||
secrets = [],
|
||||
environment,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
faCopy,
|
||||
faEllipsis,
|
||||
faKey,
|
||||
faLock,
|
||||
faShare,
|
||||
faTags
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -71,7 +72,8 @@ export enum FontAwesomeSpriteName {
|
||||
Close = "close",
|
||||
CheckedCircle = "check-circle",
|
||||
ReplicatedSecretKey = "secret-replicated",
|
||||
ShareSecret = "share-secret"
|
||||
ShareSecret = "share-secret",
|
||||
KeyLock = "key-lock"
|
||||
}
|
||||
|
||||
// this is an optimization technique
|
||||
@@ -88,5 +90,6 @@ export const FontAwesomeSpriteSymbols = [
|
||||
{ icon: faClose, symbol: FontAwesomeSpriteName.Close },
|
||||
{ icon: faCheckCircle, symbol: FontAwesomeSpriteName.CheckedCircle },
|
||||
{ icon: faClone, symbol: FontAwesomeSpriteName.ReplicatedSecretKey },
|
||||
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret }
|
||||
{ icon: faShare, symbol: FontAwesomeSpriteName.ShareSecret },
|
||||
{ icon: faLock, symbol: FontAwesomeSpriteName.KeyLock }
|
||||
];
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { FontAwesomeSymbol, Input, Tooltip } from "@app/components/v2";
|
||||
|
||||
import { FontAwesomeSpriteName } from "./SecretListView.utils";
|
||||
|
||||
type Props = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
export const SecretNoAccessListView = ({ count }: Props) => {
|
||||
return (
|
||||
<>
|
||||
{Array.from(Array(count)).map((_, i) => (
|
||||
<Tooltip
|
||||
className="max-w-sm"
|
||||
asChild
|
||||
content="You do not have permission to view this secret"
|
||||
key={`no-access-secret-${i + 1}`}
|
||||
>
|
||||
<div className="flex border-b border-mineshaft-600 bg-mineshaft-800 shadow-none hover:bg-mineshaft-700">
|
||||
<div className="flex h-11 w-11 items-center justify-center px-4 py-3">
|
||||
<FontAwesomeSymbol
|
||||
className="ml-3 block h-3.5 w-3.5"
|
||||
symbolName={FontAwesomeSpriteName.KeyLock}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex h-11 w-80 flex-shrink-0 items-center px-4 py-2">
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isReadOnly
|
||||
variant="plain"
|
||||
value="NO ACCESS"
|
||||
isDisabled
|
||||
className="w-full px-0 blur-sm placeholder:text-red-500 focus:text-bunker-100 focus:ring-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex w-80 flex-grow items-center border-x border-mineshaft-600 py-1 pl-4 pr-2"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
<span className="blur">********</span>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export { SecretListView } from "./SecretListView";
|
||||
export { SecretNoAccessListView } from "./SecretNoAccessListView";
|
||||
|
||||
Reference in New Issue
Block a user