mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 09:28:06 +00:00
UI improvements on secret deletion warning
This commit is contained in:
@@ -187,8 +187,9 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
|||||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||||
.select(
|
.select(
|
||||||
knexInstance.ref("name").withSchema(TableName.Environment).as("envName"),
|
knexInstance.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
|
knexInstance.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
knexInstance.ref("name").withSchema(TableName.SecretFolder).as("folderName"),
|
knexInstance.ref("name").withSchema(TableName.SecretFolder).as("folderName"),
|
||||||
knexInstance.ref("parentId").withSchema(TableName.SecretFolder).as("parentFolderId")
|
knexInstance.ref("id").withSchema(TableName.SecretFolder).as("folderId")
|
||||||
);
|
);
|
||||||
|
|
||||||
const secretReferences = await knexInstance(TableName.SecretReferenceV2)
|
const secretReferences = await knexInstance(TableName.SecretReferenceV2)
|
||||||
@@ -201,18 +202,23 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
|||||||
knexInstance.ref("key").withSchema(TableName.SecretV2).as("secretId"),
|
knexInstance.ref("key").withSchema(TableName.SecretV2).as("secretId"),
|
||||||
knexInstance.ref("name").withSchema(TableName.SecretFolder).as("folderName"),
|
knexInstance.ref("name").withSchema(TableName.SecretFolder).as("folderName"),
|
||||||
knexInstance.ref("name").withSchema(TableName.Environment).as("envName"),
|
knexInstance.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
knexInstance.ref("parentId").withSchema(TableName.SecretFolder).as("parentFolderId")
|
knexInstance.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
|
knexInstance.ref("id").withSchema(TableName.SecretFolder).as("folderId")
|
||||||
);
|
);
|
||||||
|
|
||||||
const folderResults = folderImports.map(({ envName, folderName, parentFolderId }) => ({
|
const folderResults = folderImports.map(({ envName, envSlug, folderName, folderId }) => ({
|
||||||
envName,
|
envName,
|
||||||
folderName: parentFolderId ? folderName : "/"
|
envSlug,
|
||||||
|
folderName,
|
||||||
|
folderId
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const secretResults = secretReferences.map(({ envName, secretId, folderName, parentFolderId }) => ({
|
const secretResults = secretReferences.map(({ envName, envSlug, secretId, folderName, folderId }) => ({
|
||||||
envName,
|
envName,
|
||||||
|
envSlug,
|
||||||
secretId,
|
secretId,
|
||||||
folderName: parentFolderId ? folderName : "/"
|
folderName,
|
||||||
|
folderId
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type ResultItem = FolderResult | SecretResult;
|
type ResultItem = FolderResult | SecretResult;
|
||||||
@@ -220,8 +226,12 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
type EnvFolderMap = {
|
type EnvFolderMap = {
|
||||||
[envName: string]: {
|
[envName: string]: {
|
||||||
[folderName: string]: {
|
envSlug: string;
|
||||||
secrets: string[];
|
folders: {
|
||||||
|
[folderName: string]: {
|
||||||
|
secrets: string[];
|
||||||
|
folderId: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -229,19 +239,23 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
|||||||
const groupedByEnv = allResults.reduce<EnvFolderMap>((acc, item) => {
|
const groupedByEnv = allResults.reduce<EnvFolderMap>((acc, item) => {
|
||||||
const env = item.envName;
|
const env = item.envName;
|
||||||
const folder = item.folderName;
|
const folder = item.folderName;
|
||||||
|
const { envSlug } = item;
|
||||||
|
|
||||||
const updatedAcc = { ...acc };
|
const updatedAcc = { ...acc };
|
||||||
|
|
||||||
if (!updatedAcc[env]) {
|
if (!updatedAcc[env]) {
|
||||||
updatedAcc[env] = {};
|
updatedAcc[env] = {
|
||||||
|
envSlug,
|
||||||
|
folders: {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!updatedAcc[env][folder]) {
|
if (!updatedAcc[env].folders[folder]) {
|
||||||
updatedAcc[env][folder] = { secrets: [] };
|
updatedAcc[env].folders[folder] = { secrets: [], folderId: item.folderId };
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("secretId" in item && item.secretId) {
|
if ("secretId" in item && item.secretId) {
|
||||||
updatedAcc[env][folder].secrets = [...updatedAcc[env][folder].secrets, item.secretId];
|
updatedAcc[env].folders[folder].secrets = [...updatedAcc[env].folders[folder].secrets, item.secretId];
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedAcc;
|
return updatedAcc;
|
||||||
@@ -250,18 +264,20 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
|||||||
const formattedResult: EnvInfo[] = Object.keys(groupedByEnv).map((envName) => {
|
const formattedResult: EnvInfo[] = Object.keys(groupedByEnv).map((envName) => {
|
||||||
const envData = groupedByEnv[envName];
|
const envData = groupedByEnv[envName];
|
||||||
|
|
||||||
const folders: FolderInfo[] = Object.keys(envData).map((folderName) => {
|
const folders: FolderInfo[] = Object.keys(envData.folders).map((folderName) => {
|
||||||
const folderData = envData[folderName];
|
const folderData = envData.folders[folderName];
|
||||||
const hasSecrets = folderData.secrets.length > 0;
|
const hasSecrets = folderData.secrets.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
folderName,
|
folderName,
|
||||||
|
folderId: folderData.folderId,
|
||||||
...(hasSecrets && { secrets: folderData.secrets })
|
...(hasSecrets && { secrets: folderData.secrets })
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
envName,
|
envName,
|
||||||
|
envSlug: envData.envSlug,
|
||||||
folders
|
folders
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { decryptSecretRaw } from "../secret/secret-fns";
|
|||||||
import { TSecretQueueFactory } from "../secret/secret-queue";
|
import { TSecretQueueFactory } from "../secret/secret-queue";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { recursivelyGetSecretPaths } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretImportDALFactory } from "./secret-import-dal";
|
import { TSecretImportDALFactory } from "./secret-import-dal";
|
||||||
import { fnSecretsFromImports, fnSecretsV2FromImports } from "./secret-import-fns";
|
import { fnSecretsFromImports, fnSecretsV2FromImports } from "./secret-import-fns";
|
||||||
import {
|
import {
|
||||||
@@ -38,7 +39,6 @@ import {
|
|||||||
TResyncSecretImportReplicationDTO,
|
TResyncSecretImportReplicationDTO,
|
||||||
TUpdateSecretImportDTO
|
TUpdateSecretImportDTO
|
||||||
} from "./secret-import-types";
|
} from "./secret-import-types";
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
type TSecretImportServiceFactoryDep = {
|
type TSecretImportServiceFactoryDep = {
|
||||||
secretImportDAL: TSecretImportDALFactory;
|
secretImportDAL: TSecretImportDALFactory;
|
||||||
@@ -823,8 +823,30 @@ export const secretImportServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const importedBy = await secretImportDAL.getFolderIsImportedBy(secretPath, folder.envId, environment, projectId);
|
const importedBy = await secretImportDAL.getFolderIsImportedBy(secretPath, folder.envId, environment, projectId);
|
||||||
logger.info("importedBy", JSON.stringify(importedBy));
|
|
||||||
return importedBy;
|
const deepPaths: { path: string; folderId: string }[] = [];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
importedBy.map(async (el) => {
|
||||||
|
const envDeepPaths = await recursivelyGetSecretPaths({
|
||||||
|
folderDAL,
|
||||||
|
projectEnvDAL,
|
||||||
|
projectId,
|
||||||
|
environment: el.envSlug,
|
||||||
|
currentPath: "/"
|
||||||
|
});
|
||||||
|
deepPaths.push(...envDeepPaths);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return importedBy.map((el) => ({
|
||||||
|
...el,
|
||||||
|
folders: el.folders.map((folderItem) => ({
|
||||||
|
folderId: folderItem.folderId,
|
||||||
|
secrets: folderItem.secrets,
|
||||||
|
folderName: deepPaths.find((p) => p.folderId === folderItem.folderId)?.path || `...${folderItem.folderName}`
|
||||||
|
}))
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -49,20 +49,27 @@ export type TGetSecretsFromImportDTO = {
|
|||||||
export type FolderResult = {
|
export type FolderResult = {
|
||||||
envName: string;
|
envName: string;
|
||||||
folderName: string;
|
folderName: string;
|
||||||
|
folderId: string;
|
||||||
|
envSlug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretResult = {
|
export type SecretResult = {
|
||||||
envName: string;
|
envName: string;
|
||||||
secretId: string;
|
secretId: string;
|
||||||
folderName: string;
|
folderName: string;
|
||||||
|
folderId: string;
|
||||||
|
envSlug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FolderInfo = {
|
export type FolderInfo = {
|
||||||
folderName: string;
|
folderName: string;
|
||||||
secrets?: string[];
|
secrets?: string[];
|
||||||
|
folderId: string;
|
||||||
|
envSlug?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EnvInfo = {
|
export type EnvInfo = {
|
||||||
envName: string;
|
envName: string;
|
||||||
|
envSlug: string;
|
||||||
folders: FolderInfo[];
|
folders: FolderInfo[];
|
||||||
};
|
};
|
||||||
|
|||||||
+34
-20
@@ -41,6 +41,7 @@ interface FolderProps {
|
|||||||
isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
level: number;
|
level: number;
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeViewProps {
|
interface TreeViewProps {
|
||||||
@@ -48,6 +49,7 @@ interface TreeViewProps {
|
|||||||
basePath?: string;
|
basePath?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onChange: (items: SecretItem[]) => void;
|
onChange: (items: SecretItem[]) => void;
|
||||||
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllItemsInFolder = (folder: FolderStructure): SecretItem[] => {
|
const getAllItemsInFolder = (folder: FolderStructure): SecretItem[] => {
|
||||||
@@ -81,7 +83,8 @@ const Folder: React.FC<FolderProps> = ({
|
|||||||
onFolderSelect,
|
onFolderSelect,
|
||||||
isExpanded = false,
|
isExpanded = false,
|
||||||
level,
|
level,
|
||||||
basePath
|
basePath,
|
||||||
|
isDisabled = false
|
||||||
}) => {
|
}) => {
|
||||||
const [open, setOpen] = useState(isExpanded);
|
const [open, setOpen] = useState(isExpanded);
|
||||||
const displayName = useMemo(() => getDisplayName(name), [name]);
|
const displayName = useMemo(() => getDisplayName(name), [name]);
|
||||||
@@ -127,13 +130,16 @@ const Folder: React.FC<FolderProps> = ({
|
|||||||
className={`h-4 w-4 text-${level === 0 ? "mineshaft-300" : "yellow"}`}
|
className={`h-4 w-4 text-${level === 0 ? "mineshaft-300" : "yellow"}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
{!isDisabled && (
|
||||||
id="folder-root"
|
<Checkbox
|
||||||
className="data-[state=indeterminate]:bg-secondary data-[state=checked]:bg-primary"
|
id="folder-root"
|
||||||
isChecked={allSelected || someSelected}
|
className="data-[state=indeterminate]:bg-secondary data-[state=checked]:bg-primary"
|
||||||
onCheckedChange={handleFolderSelect}
|
isChecked={allSelected || someSelected}
|
||||||
isIndeterminate={someSelected && !allSelected}
|
onCheckedChange={handleFolderSelect}
|
||||||
/>
|
isIndeterminate={someSelected && !allSelected}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<label
|
<label
|
||||||
htmlFor={`folder-${path}`}
|
htmlFor={`folder-${path}`}
|
||||||
@@ -158,12 +164,15 @@ const Folder: React.FC<FolderProps> = ({
|
|||||||
<div className="ml-6 mr-2">
|
<div className="ml-6 mr-2">
|
||||||
<FontAwesomeIcon icon={faKey} className="h-3 w-3" />
|
<FontAwesomeIcon icon={faKey} className="h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
{!isDisabled && (
|
||||||
id={`folder-${item.id}`}
|
<Checkbox
|
||||||
className="data-[state=indeterminate]:bg-secondary data-[state=checked]:bg-primary"
|
id={`folder-${item.id}`}
|
||||||
isChecked={selectedItemIds.includes(item.id)}
|
className="data-[state=indeterminate]:bg-secondary data-[state=checked]:bg-primary"
|
||||||
onCheckedChange={(checked) => onItemSelect(item, !!checked)}
|
isChecked={selectedItemIds.includes(item.id)}
|
||||||
/>
|
onCheckedChange={(checked) => onItemSelect(item, !!checked)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<label
|
<label
|
||||||
htmlFor={item.id}
|
htmlFor={item.id}
|
||||||
className="ml-2 flex-1 cursor-pointer truncate"
|
className="ml-2 flex-1 cursor-pointer truncate"
|
||||||
@@ -184,6 +193,7 @@ const Folder: React.FC<FolderProps> = ({
|
|||||||
onItemSelect={onItemSelect}
|
onItemSelect={onItemSelect}
|
||||||
onFolderSelect={onFolderSelect}
|
onFolderSelect={onFolderSelect}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -197,7 +207,8 @@ export const SecretTreeView: React.FC<TreeViewProps> = ({
|
|||||||
data,
|
data,
|
||||||
basePath = "/",
|
basePath = "/",
|
||||||
className = "",
|
className = "",
|
||||||
onChange
|
onChange,
|
||||||
|
isDisabled = false
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedItems, setSelectedItems] = useState<SecretItem[]>([]);
|
const [selectedItems, setSelectedItems] = useState<SecretItem[]>([]);
|
||||||
const rootPath = "/";
|
const rootPath = "/";
|
||||||
@@ -305,16 +316,19 @@ export const SecretTreeView: React.FC<TreeViewProps> = ({
|
|||||||
onFolderSelect={handleFolderSelect}
|
onFolderSelect={handleFolderSelect}
|
||||||
isExpanded
|
isExpanded
|
||||||
level={0}
|
level={0}
|
||||||
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end pb-2 pr-2 pt-2">
|
{!isDisabled && (
|
||||||
<h3 className="flex items-center text-mineshaft-400">
|
<div className="flex justify-end pb-2 pr-2 pt-2">
|
||||||
{selectedItems.length} Item{selectedItems.length === 1 ? "" : "s"} Selected
|
<h3 className="flex items-center text-mineshaft-400">
|
||||||
</h3>
|
{selectedItems.length} Item{selectedItems.length === 1 ? "" : "s"} Selected
|
||||||
</div>
|
</h3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+108
-84
@@ -1,29 +1,111 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { faFolder, faKey, faServer, faWarning } from "@fortawesome/free-solid-svg-icons";
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Select, SelectItem } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { SecretTreeView } from "../ActionBar/ReplicateFolderFromBoard/SecretTreeView";
|
||||||
|
|
||||||
interface Folder {
|
interface Folder {
|
||||||
folderName: string;
|
folderName: string;
|
||||||
secrets?: string[];
|
secrets?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportedByEnvironment {
|
interface Environment {
|
||||||
envName: string;
|
envName: string;
|
||||||
folders: Folder[];
|
folders: Folder[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SecretDeletionImpactProps {
|
interface SecretDeletionImpactProps {
|
||||||
importedBy?: ImportedByEnvironment[];
|
importedBy?: Environment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretItem {
|
||||||
|
id: string;
|
||||||
|
secretKey: string;
|
||||||
|
secretValue?: string;
|
||||||
|
secretPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderStructure {
|
||||||
|
items: SecretItem[];
|
||||||
|
subFolders: {
|
||||||
|
[key: string]: FolderStructure;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SecretDeletionImpact: React.FC<SecretDeletionImpactProps> = ({ importedBy = [] }) => {
|
export const SecretDeletionImpact: React.FC<SecretDeletionImpactProps> = ({ importedBy = [] }) => {
|
||||||
const truncateId = (id: string): string => {
|
const [treeData, setTreeData] = useState<FolderStructure | null>(null);
|
||||||
if (id.length <= 16) return id;
|
const [selectedEnv, setSelectedEnv] = useState<string>(importedBy[0]?.envName || "");
|
||||||
return `${id.substring(0, 8)}...${id.substring(id.length - 8)}`;
|
|
||||||
|
const handleEnvironmentChange = (value: string) => {
|
||||||
|
setSelectedEnv(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function transformImportedDataToTreeView(
|
||||||
|
environments: Environment[],
|
||||||
|
selectedEnvironment: string
|
||||||
|
): FolderStructure | null {
|
||||||
|
const environment = environments.find((env) => env.envName === selectedEnvironment);
|
||||||
|
if (!environment) return null;
|
||||||
|
|
||||||
|
const rootStructure: FolderStructure = {
|
||||||
|
items: [],
|
||||||
|
subFolders: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.folders.forEach((folder) => {
|
||||||
|
const path = folder.folderName;
|
||||||
|
const pathParts = path.split("/").filter((part) => part !== "");
|
||||||
|
|
||||||
|
if (pathParts.length === 0) {
|
||||||
|
if (folder.secrets && folder.secrets.length > 0) {
|
||||||
|
folder.secrets.forEach((secret) => {
|
||||||
|
rootStructure.items.push({
|
||||||
|
id: `${selectedEnvironment}:${path}:${secret}`,
|
||||||
|
secretKey: secret,
|
||||||
|
secretPath: path
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = rootStructure;
|
||||||
|
|
||||||
|
pathParts.forEach((part, index) => {
|
||||||
|
if (!current.subFolders[part]) {
|
||||||
|
current.subFolders[part] = {
|
||||||
|
items: [],
|
||||||
|
subFolders: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.subFolders[part];
|
||||||
|
|
||||||
|
const isLastPart = index === pathParts.length - 1;
|
||||||
|
if (isLastPart && folder.secrets && folder.secrets.length > 0) {
|
||||||
|
folder.secrets.forEach((secret) => {
|
||||||
|
current.items.push({
|
||||||
|
id: `${selectedEnvironment}:${path}:${secret}`,
|
||||||
|
secretKey: secret,
|
||||||
|
secretPath: path
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return rootStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const transformedData = transformImportedDataToTreeView(importedBy, selectedEnv);
|
||||||
|
setTreeData(transformedData);
|
||||||
|
}, [selectedEnv, importedBy]);
|
||||||
|
|
||||||
return (
|
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="mt-4 max-h-[50vh] 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="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">
|
<div className="flex-shrink-0 text-red-400">
|
||||||
<FontAwesomeIcon icon={faWarning} className="h-5 w-5" />
|
<FontAwesomeIcon icon={faWarning} className="h-5 w-5" />
|
||||||
@@ -35,83 +117,25 @@ export const SecretDeletionImpact: React.FC<SecretDeletionImpactProps> = ({ impo
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{importedBy.map((envData, envIndex) => (
|
<SecretTreeView data={treeData} basePath="/" onChange={() => {}} isDisabled />
|
||||||
<div
|
</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="mt-4 flex justify-end">
|
||||||
<div className="mb-5">
|
<Select
|
||||||
<div className="mb-2 text-xs font-medium uppercase tracking-wider text-gray-400">
|
className="w-44 rounded-md border border-mineshaft-600 bg-mineshaft-700 text-gray-200"
|
||||||
Affected Folders
|
onValueChange={handleEnvironmentChange}
|
||||||
</div>
|
defaultValue={selectedEnv}
|
||||||
<div className="grid grid-cols-2 gap-2">
|
>
|
||||||
{envData.folders
|
{importedBy.map((env) => (
|
||||||
.filter((folder) => !folder.secrets || folder.secrets.length === 0)
|
<SelectItem
|
||||||
.map((folder, folderIndex) => (
|
value={env.envName}
|
||||||
<div
|
key={env.envName}
|
||||||
key={`folder-${folderIndex + 1}-${envData.envName}`}
|
className="data-[highlighted]:bg-mineshaft-600"
|
||||||
className="flex items-center rounded-md border border-mineshaft-500 px-3 py-2 transition-colors"
|
>
|
||||||
>
|
<div className="flex items-center gap-2 text-gray-200">{env.envName}</div>
|
||||||
<FontAwesomeIcon icon={faFolder} className="mr-2 h-4 w-4 text-yellow-700" />
|
</SelectItem>
|
||||||
<span className="text-sm">{folder.folderName}</span>
|
))}
|
||||||
</div>
|
</Select>
|
||||||
))}
|
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user