Rework of secret imports on overview page

This commit is contained in:
carlosmonastyrski
2025-03-18 16:26:15 -03:00
parent e67a8f9c05
commit 633a2ae985
9 changed files with 243 additions and 236 deletions
+2 -1
View File
@@ -813,7 +813,8 @@ export const DASHBOARD = {
search: "The text string to filter secret keys and folder names by.", search: "The text string to filter secret keys and folder names by.",
includeSecrets: "Whether to include project secrets in the response.", includeSecrets: "Whether to include project secrets in the response.",
includeFolders: "Whether to include project folders in the response.", includeFolders: "Whether to include project folders in the response.",
includeDynamicSecrets: "Whether to include dynamic project secrets in the response." includeDynamicSecrets: "Whether to include dynamic project secrets in the response.",
includeImports: "Whether to include project secret imports in the response."
}, },
SECRET_DETAILS_LIST: { SECRET_DETAILS_LIST: {
projectId: "The ID of the project to list secrets/folders from.", projectId: "The ID of the project to list secrets/folders from.",
@@ -109,6 +109,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
search: z.string().trim().describe(DASHBOARD.SECRET_OVERVIEW_LIST.search).optional(), search: z.string().trim().describe(DASHBOARD.SECRET_OVERVIEW_LIST.search).optional(),
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets), includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets),
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders), includeFolders: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders),
includeImports: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeImports),
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets) includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets)
}), }),
response: { response: {
@@ -124,9 +125,17 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
}) })
.array() .array()
.optional(), .optional(),
imports: SecretImportsSchema.omit({ importEnv: true })
.extend({
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }),
environment: z.string()
})
.array()
.optional(),
totalFolderCount: z.number().optional(), totalFolderCount: z.number().optional(),
totalDynamicSecretCount: z.number().optional(), totalDynamicSecretCount: z.number().optional(),
totalSecretCount: z.number().optional(), totalSecretCount: z.number().optional(),
totalImportCount: z.number().optional(),
totalCount: z.number() totalCount: z.number()
}) })
} }
@@ -143,6 +152,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
orderDirection, orderDirection,
includeFolders, includeFolders,
includeSecrets, includeSecrets,
includeImports,
includeDynamicSecrets includeDynamicSecrets
} = req.query; } = req.query;
@@ -159,6 +169,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
let remainingLimit = limit; let remainingLimit = limit;
let adjustedOffset = offset; let adjustedOffset = offset;
let imports: Awaited<ReturnType<typeof server.services.secretImport.getImportsMultiEnv>> | undefined;
let folders: Awaited<ReturnType<typeof server.services.folder.getFoldersMultiEnv>> | undefined; let folders: Awaited<ReturnType<typeof server.services.folder.getFoldersMultiEnv>> | undefined;
let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRawMultiEnv>> | undefined; let secrets: Awaited<ReturnType<typeof server.services.secret.getSecretsRawMultiEnv>> | undefined;
let dynamicSecrets: let dynamicSecrets:
@@ -168,6 +179,53 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
let totalFolderCount: number | undefined; let totalFolderCount: number | undefined;
let totalDynamicSecretCount: number | undefined; let totalDynamicSecretCount: number | undefined;
let totalSecretCount: number | undefined; let totalSecretCount: number | undefined;
let totalImportCount: number | undefined;
if (includeImports) {
totalImportCount = await server.services.secretImport.getProjectImportMultiEnvCount({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId,
environments,
path: secretPath,
search
});
if (remainingLimit > 0 && totalImportCount > adjustedOffset) {
imports = await server.services.secretImport.getImportsMultiEnv({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId,
environments,
path: secretPath,
search,
limit: remainingLimit,
offset: adjustedOffset
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.projectId,
event: {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: environments.join(","),
folderId: imports?.[0]?.folderId,
numberOfImports: imports.length
}
}
});
remainingLimit -= imports.length;
adjustedOffset = 0;
} else {
adjustedOffset = Math.max(0, adjustedOffset - totalImportCount);
}
}
if (includeFolders) { if (includeFolders) {
// this is the unique count, ie duplicate folders across envs only count as 1 // this is the unique count, ie duplicate folders across envs only count as 1
@@ -345,8 +403,10 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
folders, folders,
dynamicSecrets, dynamicSecrets,
secrets, secrets,
imports,
totalFolderCount, totalFolderCount,
totalDynamicSecretCount, totalDynamicSecretCount,
totalImportCount,
totalSecretCount, totalSecretCount,
totalCount: (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0) totalCount: (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0)
}; };
@@ -469,6 +469,43 @@ export const secretImportServiceFactory = ({
return count; return count;
}; };
const getProjectImportMultiEnvCount = async ({
path: secretPath,
environments,
projectId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
search
}: Omit<TGetSecretImportsDTO, "environment"> & { environments: string[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
for (const environment of environments) {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
);
}
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, secretPath);
if (!folders?.length)
throw new NotFoundError({
message: `Folder with path '${secretPath}' not found on environments with slugs '${environments.join(", ")}'`
});
const counts = await Promise.all(
folders.map((folder) => secretImportDAL.getProjectImportCount({ folderId: folder.id, search }))
);
return counts.reduce((sum, count) => sum + count, 0);
};
const getImports = async ({ const getImports = async ({
path: secretPath, path: secretPath,
environment, environment,
@@ -688,6 +725,51 @@ export const secretImportServiceFactory = ({
})); }));
}; };
const getImportsMultiEnv = async ({
path: secretPath,
environments,
projectId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
search,
limit,
offset
}: Omit<TGetSecretImportsDTO, "environment"> & { environments: string[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
for (const environment of environments) {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
);
}
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, secretPath);
if (!folders?.length)
throw new NotFoundError({
message: `Folder with path '${secretPath}' not found on environments with slugs '${environments.join(", ")}'`
});
const secImportsArrays = await Promise.all(
folders.map(async (folder) => {
const imports = await secretImportDAL.find({ folderId: folder.id, search, limit, offset });
return imports.map((importItem) => ({
...importItem,
environment: folder.environment.slug
}));
})
);
return secImportsArrays.flat();
};
return { return {
createImport, createImport,
updateImport, updateImport,
@@ -698,6 +780,8 @@ export const secretImportServiceFactory = ({
getRawSecretsFromImports, getRawSecretsFromImports,
resyncSecretImportReplication, resyncSecretImportReplication,
getProjectImportCount, getProjectImportCount,
fnSecretsFromImports fnSecretsFromImports,
getProjectImportMultiEnvCount,
getImportsMultiEnv
}; };
}; };
+7 -1
View File
@@ -143,6 +143,7 @@ export const useGetProjectSecretsOverview = (
search = "", search = "",
includeSecrets, includeSecrets,
includeFolders, includeFolders,
includeImports,
includeDynamicSecrets, includeDynamicSecrets,
environments environments
}: TGetDashboardProjectSecretsOverviewDTO, }: TGetDashboardProjectSecretsOverviewDTO,
@@ -170,6 +171,7 @@ export const useGetProjectSecretsOverview = (
projectId, projectId,
includeSecrets, includeSecrets,
includeFolders, includeFolders,
includeImports,
includeDynamicSecrets, includeDynamicSecrets,
environments environments
}), }),
@@ -184,6 +186,7 @@ export const useGetProjectSecretsOverview = (
projectId, projectId,
includeSecrets, includeSecrets,
includeFolders, includeFolders,
includeImports,
includeDynamicSecrets, includeDynamicSecrets,
environments environments
}), }),
@@ -197,12 +200,15 @@ export const useGetProjectSecretsOverview = (
? unique(select.dynamicSecrets, (i) => i.name) ? unique(select.dynamicSecrets, (i) => i.name)
: []; : [];
const uniqueSecretImports = select.imports ? unique(select.imports, (i) => i.id) : [];
return { return {
...select, ...select,
secrets: secrets ? mergePersonalSecrets(secrets) : undefined, secrets: secrets ? mergePersonalSecrets(secrets) : undefined,
totalUniqueSecretsInPage: uniqueSecrets.length, totalUniqueSecretsInPage: uniqueSecrets.length,
totalUniqueDynamicSecretsInPage: uniqueDynamicSecrets.length, totalUniqueDynamicSecretsInPage: uniqueDynamicSecrets.length,
totalUniqueFoldersInPage: uniqueFolders.length totalUniqueFoldersInPage: uniqueFolders.length,
totalUniqueSecretImportsInPage: uniqueSecretImports.length
}; };
}, []), }, []),
placeholderData: (previousData) => previousData placeholderData: (previousData) => previousData
@@ -9,13 +9,16 @@ export type DashboardProjectSecretsOverviewResponse = {
folders?: (TSecretFolder & { environment: string })[]; folders?: (TSecretFolder & { environment: string })[];
dynamicSecrets?: (TDynamicSecret & { environment: string })[]; dynamicSecrets?: (TDynamicSecret & { environment: string })[];
secrets?: SecretV3Raw[]; secrets?: SecretV3Raw[];
imports?: TSecretImport[];
totalSecretCount?: number; totalSecretCount?: number;
totalFolderCount?: number; totalFolderCount?: number;
totalDynamicSecretCount?: number; totalDynamicSecretCount?: number;
totalImportCount?: number;
totalCount: number; totalCount: number;
totalUniqueSecretsInPage: number; totalUniqueSecretsInPage: number;
totalUniqueDynamicSecretsInPage: number; totalUniqueDynamicSecretsInPage: number;
totalUniqueFoldersInPage: number; totalUniqueFoldersInPage: number;
totalUniqueSecretImportsInPage: number;
}; };
export type DashboardProjectSecretsDetailsResponse = { export type DashboardProjectSecretsDetailsResponse = {
@@ -63,6 +66,7 @@ export type TGetDashboardProjectSecretsOverviewDTO = {
includeSecrets?: boolean; includeSecrets?: boolean;
includeFolders?: boolean; includeFolders?: boolean;
includeDynamicSecrets?: boolean; includeDynamicSecrets?: boolean;
includeImports?: boolean;
environments: string[]; environments: string[];
}; };
+1 -28
View File
@@ -14,6 +14,7 @@ export type TSecretImport = {
isReplicationSuccess?: boolean; isReplicationSuccess?: boolean;
replicationStatus?: string; replicationStatus?: string;
lastReplicated?: string; lastReplicated?: string;
environment?: string;
}; };
export type TGetImportedFoldersByEnvDTO = { export type TGetImportedFoldersByEnvDTO = {
@@ -90,31 +91,3 @@ export type TDeleteSecretImportDTO = {
environment: string; environment: string;
path?: string; path?: string;
}; };
export type TSecretImportMultiEnvData = {
currentEnv: string;
environment: string;
secretPath: string;
environmentInfo: WorkspaceEnv;
folderId: string;
secrets: {
id: string;
env: string;
key: string;
value: string;
secretValueHidden: boolean;
tags?: {
id: string;
slug: string;
color?: string;
projectId: string;
createdAt: string;
updatedAt: string;
__v: number;
}[];
comment?: string;
createdAt: string;
updatedAt: string;
version: number;
}[];
};
@@ -1,7 +1,6 @@
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { DashboardProjectSecretsOverview } from "@app/hooks/api/dashboard/types"; import { DashboardProjectSecretsOverview } from "@app/hooks/api/dashboard/types";
import { TSecretImportMultiEnvData } from "@app/hooks/api/secretImports/types";
type FolderNameAndDescription = { type FolderNameAndDescription = {
name: string; name: string;
@@ -97,37 +96,3 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr
return { secKeys, getSecretByKey, getEnvSecretKeyCount }; return { secKeys, getSecretByKey, getEnvSecretKeyCount };
}; };
export const useSecretImportOverview = (secretImports: TSecretImportMultiEnvData[] | undefined) => {
const uniqueEnvSecretPaths = useMemo(() => {
const uniqueMap = new Map<string, TSecretImportMultiEnvData>();
secretImports?.forEach((importData) => {
if (importData) {
const key = `${importData.environment}-${importData.secretPath}`;
if (!uniqueMap.has(key)) {
uniqueMap.set(key, importData);
}
}
});
return Array.from(uniqueMap.values());
}, [secretImports]);
const isSecretImportPresent = useCallback(
(sourceEnv: string, targetEnv: string, secretPath: string) => {
return (
secretImports?.some(
(importData) =>
importData?.currentEnv === sourceEnv &&
importData?.environment === targetEnv &&
importData?.secretPath === secretPath
) ?? false
);
},
[secretImports]
);
return {
uniqueEnvSecretPaths,
isSecretImportPresent
};
};
@@ -68,15 +68,9 @@ import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
import { TSecretImportMultiEnvData } from "@app/hooks/api/secretImports/types";
import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
useDynamicSecretOverview,
useFolderOverview,
useSecretImportOverview,
useSecretOverview
} from "@app/hooks/utils";
import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm"; import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm";
import { CreateSecretForm } from "./components/CreateSecretForm"; import { CreateSecretForm } from "./components/CreateSecretForm";
@@ -195,33 +189,12 @@ export const OverviewPage = () => {
setVisibleEnvs(userAvailableEnvs); setVisibleEnvs(userAvailableEnvs);
}, [userAvailableEnvs]); }, [userAvailableEnvs]);
const { const { isImportedSecretPresentInEnv, getImportedSecretByKey, getEnvImportedSecretKeyCount } =
secretImports, useGetImportedSecretsAllEnvs({
isImportedSecretPresentInEnv, projectId: workspaceId,
getImportedSecretByKey, path: secretPath,
getEnvImportedSecretKeyCount environments: (userAvailableEnvs || []).map(({ slug }) => slug)
} = useGetImportedSecretsAllEnvs({ });
projectId: workspaceId,
path: secretPath,
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
});
const secretImportsData = useMemo(
() =>
(secretImports?.map((s) => s.data as TSecretImportMultiEnvData[]) ?? [])
?.flatMap((s) => s ?? [])
?.filter((secretImport) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, {
environment: secretImport.currentEnv,
secretPath
})
)
),
[secretImports]
);
const { uniqueEnvSecretPaths, isSecretImportPresent } =
useSecretImportOverview(secretImportsData);
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview( const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
{ {
@@ -233,6 +206,7 @@ export const OverviewPage = () => {
includeFolders: filter.folder, includeFolders: filter.folder,
includeDynamicSecrets: filter.dynamic, includeDynamicSecrets: filter.dynamic,
includeSecrets: filter.secret, includeSecrets: filter.secret,
includeImports: filter.import,
search: debouncedSearchFilter, search: debouncedSearchFilter,
limit, limit,
offset offset
@@ -244,15 +218,29 @@ export const OverviewPage = () => {
secrets, secrets,
folders, folders,
dynamicSecrets, dynamicSecrets,
imports,
totalFolderCount, totalFolderCount,
totalSecretCount, totalSecretCount,
totalDynamicSecretCount, totalDynamicSecretCount,
totalImportCount,
totalCount = 0, totalCount = 0,
totalUniqueFoldersInPage, totalUniqueFoldersInPage,
totalUniqueSecretsInPage, totalUniqueSecretsInPage,
totalUniqueSecretImportsInPage,
totalUniqueDynamicSecretsInPage totalUniqueDynamicSecretsInPage
} = overview ?? {}; } = overview ?? {};
const importsShaped = imports
?.filter((el) => !el.isReserved)
?.map(({ importPath, importEnv }) => ({ importPath, importEnv }))
.filter(
(el, index, self) =>
index ===
self.findIndex(
(item) => item.importPath === el.importPath && item.importEnv.slug === el.importEnv.slug
)
);
useResetPageHelper({ useResetPageHelper({
totalCount, totalCount,
offset, offset,
@@ -693,7 +681,6 @@ export const OverviewPage = () => {
<SecretV2MigrationSection /> <SecretV2MigrationSection />
</div> </div>
); );
return ( return (
<div className=""> <div className="">
<Helmet> <Helmet>
@@ -1099,16 +1086,14 @@ export const OverviewPage = () => {
/> />
))} ))}
{filter.import && {filter.import &&
Object.entries(uniqueEnvSecretPaths).map(([key, secretImportsAllEnvs]) => ( importsShaped &&
importsShaped?.length > 0 &&
importsShaped?.map((item, index) => (
<SecretOverviewImportListView <SecretOverviewImportListView
secretImport={secretImportsAllEnvs} secretImport={item}
isImportedSecretPresentInEnv={isSecretImportPresent}
environments={visibleEnvs} environments={visibleEnvs}
key={`overview-${key}`} key={`overview-secret-input-${index + 1}`}
scrollOffset={debouncedScrollOffset} allSecretImports={imports}
allSecretImports={(
secretImportsData?.flatMap((s) => s ?? []) ?? []
).filter(Boolean)}
/> />
))} ))}
{secKeys.map((key, index) => ( {secKeys.map((key, index) => (
@@ -1134,7 +1119,8 @@ export const OverviewPage = () => {
(page * perPage > totalCount ? totalCount % perPage : perPage) - (page * perPage > totalCount ? totalCount % perPage : perPage) -
(totalUniqueFoldersInPage || 0) - (totalUniqueFoldersInPage || 0) -
(totalUniqueDynamicSecretsInPage || 0) - (totalUniqueDynamicSecretsInPage || 0) -
(totalUniqueSecretsInPage || 0), (totalUniqueSecretsInPage || 0) -
(totalUniqueSecretImportsInPage || 0),
0 0
)} )}
/> />
@@ -1174,6 +1160,7 @@ export const OverviewPage = () => {
dynamicSecretCount={totalDynamicSecretCount} dynamicSecretCount={totalDynamicSecretCount}
secretCount={totalSecretCount} secretCount={totalSecretCount}
folderCount={totalFolderCount} folderCount={totalFolderCount}
importCount={totalImportCount}
/> />
} }
className="rounded-b-md border-t border-solid border-t-mineshaft-600" className="rounded-b-md border-t border-solid border-t-mineshaft-600"
@@ -1,149 +1,76 @@
import { faCheck, faFileImport, faKey, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faFileImport, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { EmptyState, SecretInput, TableContainer, Td, Tr } from "@app/components/v2"; import { Td, Tr } from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { TSecretImport, WorkspaceEnv } from "@app/hooks/api/types";
import { TSecretImportMultiEnvData } from "@app/hooks/api/secretImports/types";
import { EnvFolderIcon } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem"; import { EnvFolderIcon } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem";
import { computeImportedSecretRows } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView";
type Props = { type Props = {
secretImport: TSecretImportMultiEnvData; secretImport: { importPath: string; importEnv: WorkspaceEnv };
environments: { name: string; slug: string }[]; environments: { name: string; slug: string }[];
isImportedSecretPresentInEnv: ( allSecretImports?: TSecretImport[];
sourceEnv: string,
targetEnv: string,
secretPath: string
) => boolean;
scrollOffset: number;
allSecretImports: TSecretImportMultiEnvData[];
}; };
export const SecretOverviewImportListView = ({ export const SecretOverviewImportListView = ({
secretImport, secretImport,
environments = [], environments = [],
isImportedSecretPresentInEnv,
scrollOffset,
allSecretImports = [] allSecretImports = []
}: Props) => { }: Props) => {
const [isFormExpanded, setIsFormExpanded] = useToggle(); const isSecretPresentInEnv = (envSlug: string) => {
const environmentImportDetails = secretImport.environmentInfo; return allSecretImports.some((item) => {
const totalCols = environments.length + 1; if (item.isReplication) {
const reservedItem = allSecretImports.find((element) =>
element.importPath.includes(`__reserve_replication_${item.id}`)
);
// If the reserved item exists, check if the envSlug matches
if (reservedItem) {
return reservedItem.environment === envSlug;
}
} else {
// If the item is not replication, check if the envSlug matches directly
return item.environment === envSlug;
}
return false;
});
};
const computeImportedSecrets =
allSecretImports.length > 0
? computeImportedSecretRows(
environmentImportDetails.slug,
secretImport.secretPath,
allSecretImports
)
: [];
return ( return (
<> <Tr className="group">
<Tr <Td className="sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding px-0 py-0 group-hover:bg-mineshaft-700">
isHoverable <div className="group flex cursor-pointer">
isSelectable <div className="flex w-11 items-center py-2 pl-5 text-green-700">
onClick={() => setIsFormExpanded.toggle()} <FontAwesomeIcon icon={faFileImport} />
className={`group ${isFormExpanded ? "border-t-2 border-mineshaft-500" : ""}`}
>
<Td className="sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding px-0 py-0 group-hover:bg-mineshaft-700">
<div className="group flex cursor-pointer">
<div className="flex w-11 items-center py-2 pl-5 text-green-700">
<FontAwesomeIcon icon={faFileImport} />
</div>
<div className="flex flex-grow items-center py-2 pl-4 pr-2">
<EnvFolderIcon
env={environmentImportDetails.slug || ""}
secretPath={secretImport.secretPath || ""}
/>
</div>
</div> </div>
</Td> <div className="flex flex-grow items-center py-2 pl-4 pr-2">
{environments.map(({ slug }, i) => { <EnvFolderIcon
const isPresent = isImportedSecretPresentInEnv( env={secretImport.importEnv.slug || ""}
slug, secretPath={secretImport.importPath || ""}
secretImport.environment, />
secretImport.secretPath </div>
); </div>
</Td>
return ( {environments.map(({ slug }, i) => {
<Td const isPresent = isSecretPresentInEnv(slug);
key={`sec-overview-${slug}-${i + 1}-value`} return (
className={twMerge(
"px-0 py-0 group-hover:bg-mineshaft-700",
isPresent ? "text-green-600" : "text-red-600"
)}
>
<div className="h-full w-full border-r border-mineshaft-600 px-5 py-[0.85rem]">
<div className="flex justify-center">
<FontAwesomeIcon
// eslint-disable-next-line no-nested-ternary
icon={
isImportedSecretPresentInEnv(
slug,
secretImport.environment,
secretImport.secretPath
)
? faCheck
: faXmark
}
/>
</div>
</div>
</Td>
);
})}
</Tr>
{isFormExpanded && (
<Tr>
<Td <Td
colSpan={totalCols} key={`sec-overview-${slug}-${i + 1}-value`}
className={`bg-bunker-600 px-0 py-0 ${ className={twMerge(
isFormExpanded && "border-b-2 border-mineshaft-500" "px-0 py-0 group-hover:bg-mineshaft-700",
}`} isPresent ? "text-green-600" : "text-red-600"
)}
> >
<div <div className="h-full w-full border-r border-mineshaft-600 px-5 py-[0.85rem]">
className="ml-2 p-2" <div className="flex justify-center">
style={{ <FontAwesomeIcon
marginLeft: scrollOffset, // eslint-disable-next-line no-nested-ternary
width: "100%" icon={isSecretPresentInEnv(slug) ? faCheck : faXmark}
}} />
> </div>
<TableContainer>
<table className="secret-table">
<thead>
<tr>
<td style={{ padding: "0.25rem 1rem" }}>Key</td>
<td style={{ padding: "0.25rem 1rem" }}>Value</td>
{/* <td style={{ padding: "0.25rem 1rem" }}>Override</td> */}
</tr>
</thead>
<tbody>
{computeImportedSecrets?.length === 0 && (
<tr>
<td colSpan={3}>
<EmptyState title="No secrets found" icon={faKey} />
</td>
</tr>
)}
{computeImportedSecrets.map(({ key, value }, index) => (
<tr key={`${key}-${index + 1}`}>
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
{key}
</td>
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
<SecretInput value={value} isReadOnly />
</td>
</tr>
))}
</tbody>
</table>
</TableContainer>
</div> </div>
</Td> </Td>
</Tr> );
)} })}
</> </Tr>
); );
}; };