mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 14:27:59 +00:00
Merge pull request #4890 from Infisical/feat/ENG-3443
fix: group rotated secrets under rotation row on the UI dashboard
This commit is contained in:
@@ -685,12 +685,17 @@ const Page = () => {
|
||||
setDebouncedSearchFilter("");
|
||||
};
|
||||
|
||||
const getMergedSecretsWithPending = () => {
|
||||
const getMergedSecretsWithPending = (
|
||||
paramSecrets?: (SecretV3RawSanitized | null)[]
|
||||
): SecretV3RawSanitized[] => {
|
||||
const sanitizedParamSecrets = paramSecrets?.filter(Boolean) as
|
||||
| SecretV3RawSanitized[]
|
||||
| undefined;
|
||||
if (!isBatchMode || pendingChanges.secrets.length === 0) {
|
||||
return secrets;
|
||||
return sanitizedParamSecrets || secrets || [];
|
||||
}
|
||||
|
||||
const mergedSecrets = [...(secrets || [])] as (SecretV3RawSanitized & {
|
||||
const mergedSecrets = [...(sanitizedParamSecrets || secrets || [])] as (SecretV3RawSanitized & {
|
||||
originalKey?: string;
|
||||
})[];
|
||||
|
||||
@@ -1072,7 +1077,17 @@ const Page = () => {
|
||||
/>
|
||||
)}
|
||||
{canReadSecretRotations && Boolean(secretRotations?.length) && (
|
||||
<SecretRotationListView secretRotations={secretRotations} />
|
||||
<SecretRotationListView
|
||||
secretRotations={secretRotations}
|
||||
colWidth={colWidth}
|
||||
tags={tags}
|
||||
projectId={projectId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
importedBy={importedBy}
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
getMergedSecretsWithPending={getMergedSecretsWithPending}
|
||||
/>
|
||||
)}
|
||||
{canReadSecret && Boolean(mergedSecrets?.length) && (
|
||||
<SecretListView
|
||||
|
||||
+8
-2
@@ -733,7 +733,7 @@ export const SecretItem = memo(
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
ariaLabel="override-value"
|
||||
isDisabled={!isAllowed}
|
||||
isDisabled={!isAllowed || isRotatedSecret}
|
||||
variant="plain"
|
||||
size="sm"
|
||||
onClick={handleOverrideClick}
|
||||
@@ -742,7 +742,13 @@ export const SecretItem = memo(
|
||||
isOverridden && "w-5 text-primary"
|
||||
)}
|
||||
>
|
||||
<Tooltip content={`${isOverridden ? "Remove" : "Add"} Override`}>
|
||||
<Tooltip
|
||||
content={
|
||||
isRotatedSecret
|
||||
? "Unavailable for rotated secrets"
|
||||
: `${isOverridden ? "Remove" : "Add"} Override`
|
||||
}
|
||||
>
|
||||
<FontAwesomeSymbol
|
||||
symbolName={FontAwesomeSpriteName.Override}
|
||||
className="h-3.5 w-3.5"
|
||||
|
||||
+29
-22
@@ -52,6 +52,7 @@ type Props = {
|
||||
}[];
|
||||
}[];
|
||||
colWidth: number;
|
||||
excludePendingCreates?: boolean;
|
||||
};
|
||||
|
||||
export const SecretListView = ({
|
||||
@@ -64,7 +65,8 @@ export const SecretListView = ({
|
||||
isProtectedBranch = false,
|
||||
usedBySecretSyncs,
|
||||
importedBy,
|
||||
colWidth
|
||||
colWidth,
|
||||
excludePendingCreates = false
|
||||
}: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
@@ -580,27 +582,32 @@ export const SecretListView = ({
|
||||
{FontAwesomeSpriteSymbols.map(({ icon, symbol }) => (
|
||||
<FontAwesomeIcon icon={icon} symbol={symbol} key={`font-awesome-svg-spritie-${symbol}`} />
|
||||
))}
|
||||
{secrets.map((secret) => (
|
||||
<SecretItem
|
||||
colWidth={colWidth}
|
||||
environment={environment}
|
||||
secretPath={secretPath}
|
||||
tags={wsTags}
|
||||
isSelected={Boolean(selectedSecrets?.[secret.id])}
|
||||
onToggleSecretSelect={toggleSelectedSecret}
|
||||
isVisible={isVisible}
|
||||
secret={secret}
|
||||
key={secret.id}
|
||||
onSaveSecret={handleSaveSecret}
|
||||
onDeleteSecret={onDeleteSecret}
|
||||
onDetailViewSecret={onDetailViewSecret}
|
||||
importedBy={importedBy}
|
||||
onCreateTag={onCreateTag}
|
||||
onShareSecret={onShareSecret}
|
||||
isPending={secret.isPending}
|
||||
pendingAction={secret.pendingAction}
|
||||
/>
|
||||
))}
|
||||
{secrets
|
||||
.filter((secret) => {
|
||||
if (!excludePendingCreates) return true;
|
||||
return !secret.isPending || secret.pendingAction !== PendingAction.Create;
|
||||
})
|
||||
.map((secret) => (
|
||||
<SecretItem
|
||||
colWidth={colWidth}
|
||||
environment={environment}
|
||||
secretPath={secretPath}
|
||||
tags={wsTags}
|
||||
isSelected={Boolean(selectedSecrets?.[secret.id])}
|
||||
onToggleSecretSelect={toggleSelectedSecret}
|
||||
isVisible={isVisible}
|
||||
secret={secret}
|
||||
key={secret.id}
|
||||
onSaveSecret={handleSaveSecret}
|
||||
onDeleteSecret={onDeleteSecret}
|
||||
onDetailViewSecret={onDetailViewSecret}
|
||||
importedBy={importedBy}
|
||||
onCreateTag={onCreateTag}
|
||||
onShareSecret={onShareSecret}
|
||||
isPending={secret.isPending}
|
||||
pendingAction={secret.pendingAction}
|
||||
/>
|
||||
))}
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteSecret.isOpen}
|
||||
deleteKey={(popUp.deleteSecret?.data as SecretV3RawSanitized)?.key}
|
||||
|
||||
+60
-2
@@ -18,8 +18,11 @@ import { IconButton, Modal, ModalContent, TableContainer, Tag, Tooltip } from "@
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
||||
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
||||
import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
|
||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||
|
||||
import { SecretListView } from "../SecretListView";
|
||||
import { SecretRotationSecretRow } from "./SecretRotationSecretRow";
|
||||
|
||||
type Props = {
|
||||
@@ -28,6 +31,23 @@ type Props = {
|
||||
onRotate: () => void;
|
||||
onViewGeneratedCredentials: () => void;
|
||||
onDelete: () => void;
|
||||
projectId: string;
|
||||
secretPath?: string;
|
||||
tags?: WsTag[];
|
||||
isProtectedBranch?: boolean;
|
||||
usedBySecretSyncs?: UsedBySecretSyncs[];
|
||||
importedBy?: {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
colWidth: number;
|
||||
getMergedSecretsWithPending: (
|
||||
paramSecrets?: (SecretV3RawSanitized | null)[]
|
||||
) => SecretV3RawSanitized[];
|
||||
};
|
||||
|
||||
export const SecretRotationItem = ({
|
||||
@@ -35,16 +55,40 @@ export const SecretRotationItem = ({
|
||||
onEdit,
|
||||
onRotate,
|
||||
onViewGeneratedCredentials,
|
||||
onDelete
|
||||
onDelete,
|
||||
projectId,
|
||||
secretPath = "/",
|
||||
tags = [],
|
||||
isProtectedBranch = false,
|
||||
usedBySecretSyncs,
|
||||
importedBy,
|
||||
colWidth,
|
||||
getMergedSecretsWithPending
|
||||
}: Props) => {
|
||||
const { name, type, environment, folder, secrets, description } = secretRotation;
|
||||
|
||||
const { name: rotationType, image } = SECRET_ROTATION_MAP[type];
|
||||
const [showSecrets, setShowSecrets] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={twMerge("group flex border-b border-mineshaft-600 hover:bg-mineshaft-700")}>
|
||||
<div
|
||||
className={twMerge(
|
||||
"group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700"
|
||||
)}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setIsExpanded(!isExpanded);
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={isExpanded}
|
||||
aria-label={`${isExpanded ? "Collapse" : "Expand"} rotation secrets for ${name}`}
|
||||
>
|
||||
<div className="text- flex w-11 items-center py-2 pl-5 text-mineshaft-400">
|
||||
<FontAwesomeIcon icon={faRotate} />
|
||||
</div>
|
||||
@@ -198,6 +242,20 @@ export const SecretRotationItem = ({
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<SecretListView
|
||||
colWidth={colWidth}
|
||||
secrets={getMergedSecretsWithPending(secretRotation.secrets) || []}
|
||||
tags={tags}
|
||||
environment={environment.slug}
|
||||
projectId={projectId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
importedBy={importedBy}
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
excludePendingCreates
|
||||
/>
|
||||
)}
|
||||
<Modal onOpenChange={setShowSecrets} isOpen={showSecrets}>
|
||||
<ModalContent
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
|
||||
+38
-1
@@ -3,15 +3,44 @@ import { EditSecretRotationV2Modal } from "@app/components/secret-rotations-v2/E
|
||||
import { RotateSecretRotationV2Modal } from "@app/components/secret-rotations-v2/RotateSecretRotationV2Modal";
|
||||
import { ViewSecretRotationV2GeneratedCredentialsModal } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { UsedBySecretSyncs } from "@app/hooks/api/dashboard/types";
|
||||
import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
|
||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||
|
||||
import { SecretRotationItem } from "./SecretRotationItem";
|
||||
|
||||
type Props = {
|
||||
secretRotations?: TSecretRotationV2[];
|
||||
projectId: string;
|
||||
secretPath?: string;
|
||||
tags?: WsTag[];
|
||||
isProtectedBranch?: boolean;
|
||||
usedBySecretSyncs?: UsedBySecretSyncs[];
|
||||
importedBy?: {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
colWidth: number;
|
||||
getMergedSecretsWithPending: (
|
||||
secretParams?: (SecretV3RawSanitized | null)[]
|
||||
) => SecretV3RawSanitized[];
|
||||
};
|
||||
|
||||
export const SecretRotationListView = ({ secretRotations }: Props) => {
|
||||
export const SecretRotationListView = ({
|
||||
secretRotations,
|
||||
projectId,
|
||||
secretPath = "/",
|
||||
tags = [],
|
||||
isProtectedBranch = false,
|
||||
usedBySecretSyncs,
|
||||
importedBy,
|
||||
colWidth,
|
||||
getMergedSecretsWithPending
|
||||
}: Props) => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"editSecretRotation",
|
||||
"rotateSecretRotation",
|
||||
@@ -31,6 +60,14 @@ export const SecretRotationListView = ({ secretRotations }: Props) => {
|
||||
handlePopUpOpen("viewSecretRotationGeneratedCredentials", secretRotation)
|
||||
}
|
||||
onDelete={() => handlePopUpOpen("deleteSecretRotation", secretRotation)}
|
||||
colWidth={colWidth}
|
||||
tags={tags}
|
||||
projectId={projectId}
|
||||
secretPath={secretPath}
|
||||
isProtectedBranch={isProtectedBranch}
|
||||
importedBy={importedBy}
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
getMergedSecretsWithPending={getMergedSecretsWithPending}
|
||||
/>
|
||||
))}
|
||||
<EditSecretRotationV2Modal
|
||||
|
||||
Reference in New Issue
Block a user