mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 06:35:21 +00:00
UI improvements on secret deletion warning
This commit is contained in:
@@ -31,7 +31,7 @@ export type DashboardProjectSecretsDetailsResponse = {
|
||||
totalDynamicSecretCount?: number;
|
||||
totalSecretCount?: number;
|
||||
totalCount: number;
|
||||
importedBy?: { envName: string; folderName: string }[];
|
||||
importedBy?: { envName: string; folders: { folderName: string; secrets?: string[] }[] }[];
|
||||
};
|
||||
|
||||
export type DashboardProjectSecretsByKeys = {
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import React from "react";
|
||||
import { faFolder, faKey, faServer, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
interface Folder {
|
||||
folderName: string;
|
||||
secrets?: string[];
|
||||
}
|
||||
|
||||
interface ImportedByEnvironment {
|
||||
envName: string;
|
||||
folders: Folder[];
|
||||
}
|
||||
|
||||
interface SecretDeletionImpactProps {
|
||||
importedBy?: ImportedByEnvironment[];
|
||||
}
|
||||
|
||||
export const SecretDeletionImpact: React.FC<SecretDeletionImpactProps> = ({ importedBy = [] }) => {
|
||||
const truncateId = (id: string): string => {
|
||||
if (id.length <= 16) return id;
|
||||
return `${id.substring(0, 8)}...${id.substring(id.length - 8)}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 max-h-[40vh] overflow-y-auto rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-5 shadow-lg">
|
||||
<div className="mb-4 flex items-start gap-3 rounded-md border border-red-800 bg-red-900/20 p-3">
|
||||
<div className="flex-shrink-0 text-red-400">
|
||||
<FontAwesomeIcon icon={faWarning} className="h-5 w-5" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-red-300">
|
||||
Warning: This secret is currently being imported by another folder, so deletion will
|
||||
affect both locations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{importedBy.map((envData, envIndex) => (
|
||||
<div
|
||||
key={`${envData.envName}-${envIndex + 1}`}
|
||||
className="rounded-lg border border-mineshaft-600 bg-mineshaft-700 p-4 shadow-sm"
|
||||
>
|
||||
<h5 className="mb-3 flex items-center text-sm font-semibold text-gray-300">
|
||||
<FontAwesomeIcon icon={faServer} className="mr-2 h-4 w-4 text-mineshaft-400" />
|
||||
{envData.envName} Environment
|
||||
</h5>
|
||||
|
||||
{envData.folders.length > 0 && (
|
||||
<div className="mb-5">
|
||||
<div className="mb-2 text-xs font-medium uppercase tracking-wider text-gray-400">
|
||||
Affected Folders
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{envData.folders
|
||||
.filter((folder) => !folder.secrets || folder.secrets.length === 0)
|
||||
.map((folder, folderIndex) => (
|
||||
<div
|
||||
key={`folder-${folderIndex + 1}-${envData.envName}`}
|
||||
className="flex items-center rounded-md border border-mineshaft-500 px-3 py-2 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faFolder} className="mr-2 h-4 w-4 text-yellow-700" />
|
||||
<span className="text-sm">{folder.folderName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{envData.folders.some((folder) => folder.secrets && folder.secrets.length > 0) && (
|
||||
<div>
|
||||
<div className="mb-2 text-xs font-medium uppercase tracking-wider text-gray-400">
|
||||
Affected Secrets
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{envData.folders
|
||||
.filter((folder) => folder.secrets && folder.secrets.length > 0)
|
||||
.map((folder) => (
|
||||
<div
|
||||
key={`folder-secrets-${folder.folderName}-${envData.envName}`}
|
||||
className="rounded-md bg-mineshaft-600/70 p-3"
|
||||
>
|
||||
<div className="mb-2 flex items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
className="mr-2 h-3.5 w-3.5 text-yellow-700"
|
||||
/>
|
||||
<span className="text-xs font-medium">{folder.folderName}</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 border-l-2 border-mineshaft-400 pl-2">
|
||||
{(folder.secrets || []).map((secret) => (
|
||||
<div
|
||||
key={`secret-${secret}-${envData.envName}`}
|
||||
className="flex items-center rounded-md border border-mineshaft-400 px-3 py-2 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faKey}
|
||||
className="mr-2 h-4 w-4 text-mineshaft-400"
|
||||
/>
|
||||
<div>
|
||||
<span className="block font-mono text-xs font-medium">
|
||||
{truncateId(secret)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+3
-46
@@ -1,5 +1,4 @@
|
||||
import { useCallback } from "react";
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
@@ -20,6 +19,7 @@ import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPa
|
||||
import { SecretDetailSidebar } from "./SecretDetailSidebar";
|
||||
import { SecretItem } from "./SecretItem";
|
||||
import { FontAwesomeSpriteSymbols } from "./SecretListView.utils";
|
||||
import { SecretDeletionImpact } from "./SecretDeletionImpact";
|
||||
|
||||
type Props = {
|
||||
secrets?: SecretV3RawSanitized[];
|
||||
@@ -29,11 +29,9 @@ type Props = {
|
||||
tags?: WsTag[];
|
||||
isVisible?: boolean;
|
||||
isProtectedBranch?: boolean;
|
||||
importedBy?: { envName: string; folderName: string }[];
|
||||
importedBy?: { envName: string; folders: { folderName: string; secrets?: string[] }[] }[];
|
||||
};
|
||||
|
||||
type GroupedFolders = Record<string, string[]>;
|
||||
|
||||
export const SecretListView = ({
|
||||
secrets = [],
|
||||
environment,
|
||||
@@ -358,48 +356,7 @@ export const SecretListView = ({
|
||||
onDeleteApproved={handleSecretDelete}
|
||||
buttonText="Delete Secret"
|
||||
>
|
||||
{importedBy && importedBy.length > 0 && (
|
||||
<div className="mb-4 mt-4 rounded-md border border-red-500/50 bg-red-900/30 p-3">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<div className="mt-0.5 flex-shrink-0 text-red-500">
|
||||
<FontAwesomeIcon icon={faWarning} />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<p className="mb-3 text-sm text-red-400">
|
||||
Warning: This secret is currently being imported by another folder, so deletion
|
||||
will affect both locations.
|
||||
</p>
|
||||
|
||||
<div className="flex w-full">
|
||||
{Object.entries(
|
||||
importedBy.reduce<GroupedFolders>((acc, { envName, folderName }) => {
|
||||
if (!acc[envName]) acc[envName] = [];
|
||||
acc[envName].push(folderName);
|
||||
return acc;
|
||||
}, {})
|
||||
).map(([envName, folders], index, array) => (
|
||||
<div
|
||||
key={envName}
|
||||
className={`${index !== array.length - 1 ? "mr-16" : ""} flex-1`}
|
||||
>
|
||||
<div className="mb-1 text-sm font-medium text-red-300">{envName}</div>
|
||||
<div className="border-l border-red-500/30 pl-2">
|
||||
{folders.map((folderName, idx) => (
|
||||
<div
|
||||
key={`imported-folder-${idx + 1}`}
|
||||
className="truncate py-0.5 text-xs text-red-300"
|
||||
>
|
||||
{folderName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{importedBy && importedBy.length > 0 && <SecretDeletionImpact importedBy={importedBy} />}
|
||||
</DeleteActionModal>
|
||||
<SecretDetailSidebar
|
||||
environment={environment}
|
||||
|
||||
Reference in New Issue
Block a user