mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 14:27:59 +00:00
Add filter imports and minor improvements to allSecretImports logic
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { SecretV3Raw } from "../secrets/types";
|
import { SecretV3Raw } from "../secrets/types";
|
||||||
import { WsTag } from "../types";
|
|
||||||
import { WorkspaceEnv } from "../workspace/types";
|
import { WorkspaceEnv } from "../workspace/types";
|
||||||
|
|
||||||
export type TSecretImport = {
|
export type TSecretImport = {
|
||||||
@@ -92,7 +91,7 @@ export type TDeleteSecretImportDTO = {
|
|||||||
path?: string;
|
path?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretImportData = {
|
export type TSecretImportMultiEnvData = {
|
||||||
currentEnv: string;
|
currentEnv: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
@@ -104,7 +103,15 @@ export type SecretImportData = {
|
|||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
secretValueHidden: boolean;
|
secretValueHidden: boolean;
|
||||||
tags?: WsTag[];
|
tags?: {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
color?: string;
|
||||||
|
projectId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
__v: number;
|
||||||
|
}[];
|
||||||
comment?: string;
|
comment?: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
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";
|
||||||
import { SecretImportData } from "../api/secretImports/types";
|
|
||||||
|
|
||||||
type FolderNameAndDescription = {
|
type FolderNameAndDescription = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -99,31 +98,28 @@ export const useSecretOverview = (secrets: DashboardProjectSecretsOverview["secr
|
|||||||
return { secKeys, getSecretByKey, getEnvSecretKeyCount };
|
return { secKeys, getSecretByKey, getEnvSecretKeyCount };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSecretImportOverview = (secretImports: SecretImportData[][] | undefined) => {
|
export const useSecretImportOverview = (secretImports: TSecretImportMultiEnvData[] | undefined) => {
|
||||||
const uniqueEnvSecretPaths = useMemo(() => {
|
const uniqueEnvSecretPaths = useMemo(() => {
|
||||||
const uniqueMap: Record<string, SecretImportData[]> = {};
|
const uniqueMap = new Map<string, TSecretImportMultiEnvData>();
|
||||||
secretImports?.forEach((importData) => {
|
secretImports?.forEach((importData) => {
|
||||||
importData?.forEach((envImport) => {
|
if (importData) {
|
||||||
if (envImport) {
|
const key = `${importData.environment}-${importData.secretPath}`;
|
||||||
const key = `${envImport.environment}-${envImport.secretPath}`;
|
if (!uniqueMap.has(key)) {
|
||||||
const existing = uniqueMap[key];
|
uniqueMap.set(key, importData);
|
||||||
uniqueMap[key] = existing ? [...existing, envImport] : [envImport];
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
return uniqueMap;
|
return Array.from(uniqueMap.values());
|
||||||
}, [secretImports]);
|
}, [secretImports]);
|
||||||
|
|
||||||
const isSecretImportPresent = useCallback(
|
const isSecretImportPresent = useCallback(
|
||||||
(sourceEnv: string, targetEnv: string, secretPath: string) => {
|
(sourceEnv: string, targetEnv: string, secretPath: string) => {
|
||||||
return (
|
return (
|
||||||
secretImports?.some((importData) =>
|
secretImports?.some(
|
||||||
importData?.some(
|
(importData) =>
|
||||||
(envImport) =>
|
importData?.currentEnv === sourceEnv &&
|
||||||
envImport?.currentEnv === sourceEnv &&
|
importData?.environment === targetEnv &&
|
||||||
envImport?.environment === targetEnv &&
|
importData?.secretPath === secretPath
|
||||||
envImport?.secretPath === secretPath
|
|
||||||
)
|
|
||||||
) ?? false
|
) ?? false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
faAngleDown,
|
faAngleDown,
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
|
faFileImport,
|
||||||
faFingerprint,
|
faFingerprint,
|
||||||
faFolder,
|
faFolder,
|
||||||
faFolderBlank,
|
faFolderBlank,
|
||||||
@@ -67,7 +68,7 @@ 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 { SecretImportData } from "@app/hooks/api/secretImports/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 {
|
||||||
@@ -100,7 +101,8 @@ export enum EntryType {
|
|||||||
enum RowType {
|
enum RowType {
|
||||||
Folder = "folder",
|
Folder = "folder",
|
||||||
DynamicSecret = "dynamic",
|
DynamicSecret = "dynamic",
|
||||||
Secret = "secret"
|
Secret = "secret",
|
||||||
|
Import = "import"
|
||||||
}
|
}
|
||||||
|
|
||||||
type Filter = {
|
type Filter = {
|
||||||
@@ -110,7 +112,8 @@ type Filter = {
|
|||||||
const DEFAULT_FILTER_STATE = {
|
const DEFAULT_FILTER_STATE = {
|
||||||
[RowType.Folder]: true,
|
[RowType.Folder]: true,
|
||||||
[RowType.DynamicSecret]: true,
|
[RowType.DynamicSecret]: true,
|
||||||
[RowType.Secret]: true
|
[RowType.Secret]: true,
|
||||||
|
[RowType.Import]: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OverviewPage = () => {
|
export const OverviewPage = () => {
|
||||||
@@ -203,7 +206,18 @@ export const OverviewPage = () => {
|
|||||||
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
|
environments: (userAvailableEnvs || []).map(({ slug }) => slug)
|
||||||
});
|
});
|
||||||
const secretImportsData = useMemo(
|
const secretImportsData = useMemo(
|
||||||
() => secretImports?.map((s) => s.data as SecretImportData[]) ?? [],
|
() =>
|
||||||
|
(secretImports?.map((s) => s.data as TSecretImportMultiEnvData[]) ?? [])
|
||||||
|
?.flatMap((s) => s ?? [])
|
||||||
|
?.filter((secretImport) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.SecretImports, {
|
||||||
|
environment: secretImport.currentEnv,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
[secretImports]
|
[secretImports]
|
||||||
);
|
);
|
||||||
const { uniqueEnvSecretPaths, isSecretImportPresent } =
|
const { uniqueEnvSecretPaths, isSecretImportPresent } =
|
||||||
@@ -768,6 +782,19 @@ export const OverviewPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem> */}
|
</DropdownMenuItem> */}
|
||||||
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleToggleRowType(RowType.Import);
|
||||||
|
}}
|
||||||
|
icon={filter[RowType.Import] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
|
iconPos="right"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FontAwesomeIcon icon={faFileImport} className="text-green-700" />
|
||||||
|
<span>Imports</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1071,16 +1098,19 @@ export const OverviewPage = () => {
|
|||||||
key={`overview-${dynamicSecretName}-${index + 1}`}
|
key={`overview-${dynamicSecretName}-${index + 1}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{Object.entries(uniqueEnvSecretPaths).map(([key, secretImportsAllEnvs]) => (
|
{filter.import &&
|
||||||
<SecretOverviewImportListView
|
Object.entries(uniqueEnvSecretPaths).map(([key, secretImportsAllEnvs]) => (
|
||||||
secretImport={secretImportsAllEnvs[0]}
|
<SecretOverviewImportListView
|
||||||
isImportedSecretPresentInEnv={isSecretImportPresent}
|
secretImport={secretImportsAllEnvs}
|
||||||
environments={visibleEnvs}
|
isImportedSecretPresentInEnv={isSecretImportPresent}
|
||||||
key={`overview-${key}`}
|
environments={visibleEnvs}
|
||||||
scrollOffset={debouncedScrollOffset}
|
key={`overview-${key}`}
|
||||||
allSecretImports={secretImportsData}
|
scrollOffset={debouncedScrollOffset}
|
||||||
/>
|
allSecretImports={(
|
||||||
))}
|
secretImportsData?.flatMap((s) => s ?? []) ?? []
|
||||||
|
).filter(Boolean)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{secKeys.map((key, index) => (
|
{secKeys.map((key, index) => (
|
||||||
<SecretOverviewTableRow
|
<SecretOverviewTableRow
|
||||||
isSelected={Boolean(selectedEntries.secret[key])}
|
isSelected={Boolean(selectedEntries.secret[key])}
|
||||||
|
|||||||
+8
-19
@@ -1,17 +1,15 @@
|
|||||||
import { subject } from "@casl/ability";
|
|
||||||
import { faCheck, faFileImport, faKey, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faFileImport, faKey, 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 { EmptyState, SecretInput, TableContainer, Td, Tr } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { SecretImportData } from "@app/hooks/api/secretImports/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";
|
import { computeImportedSecretRows } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretImport: SecretImportData;
|
secretImport: TSecretImportMultiEnvData;
|
||||||
environments: { name: string; slug: string }[];
|
environments: { name: string; slug: string }[];
|
||||||
isImportedSecretPresentInEnv: (
|
isImportedSecretPresentInEnv: (
|
||||||
sourceEnv: string,
|
sourceEnv: string,
|
||||||
@@ -19,7 +17,7 @@ type Props = {
|
|||||||
secretPath: string
|
secretPath: string
|
||||||
) => boolean;
|
) => boolean;
|
||||||
scrollOffset: number;
|
scrollOffset: number;
|
||||||
allSecretImports: SecretImportData[][] | undefined;
|
allSecretImports: TSecretImportMultiEnvData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretOverviewImportListView = ({
|
export const SecretOverviewImportListView = ({
|
||||||
@@ -27,27 +25,18 @@ export const SecretOverviewImportListView = ({
|
|||||||
environments = [],
|
environments = [],
|
||||||
isImportedSecretPresentInEnv,
|
isImportedSecretPresentInEnv,
|
||||||
scrollOffset,
|
scrollOffset,
|
||||||
allSecretImports
|
allSecretImports = []
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isFormExpanded, setIsFormExpanded] = useToggle();
|
const [isFormExpanded, setIsFormExpanded] = useToggle();
|
||||||
const { permission } = useProjectPermission();
|
|
||||||
const environmentImportDetails = secretImport.environmentInfo;
|
const environmentImportDetails = secretImport.environmentInfo;
|
||||||
const totalCols = environments.length + 1;
|
const totalCols = environments.length + 1;
|
||||||
|
|
||||||
const canReadSecretImports = permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.SecretImports, {
|
|
||||||
environment: environmentImportDetails.slug,
|
|
||||||
secretPath: secretImport.secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const computeImportedSecrets =
|
const computeImportedSecrets =
|
||||||
canReadSecretImports && allSecretImports
|
allSecretImports.length > 0
|
||||||
? computeImportedSecretRows(
|
? computeImportedSecretRows(
|
||||||
environmentImportDetails.slug,
|
environmentImportDetails.slug,
|
||||||
secretImport.secretPath,
|
secretImport.secretPath,
|
||||||
(allSecretImports?.flatMap((s) => s ?? []) ?? []).filter(Boolean) as SecretImportData[]
|
allSecretImports
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
return (
|
return (
|
||||||
@@ -55,7 +44,7 @@ export const SecretOverviewImportListView = ({
|
|||||||
<Tr
|
<Tr
|
||||||
isHoverable
|
isHoverable
|
||||||
isSelectable
|
isSelectable
|
||||||
onClick={() => canReadSecretImports && setIsFormExpanded.toggle()}
|
onClick={() => setIsFormExpanded.toggle()}
|
||||||
className={`group ${isFormExpanded ? "border-t-2 border-mineshaft-500" : ""}`}
|
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">
|
<Td className="sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding px-0 py-0 group-hover:bg-mineshaft-700">
|
||||||
@@ -106,7 +95,7 @@ export const SecretOverviewImportListView = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Tr>
|
</Tr>
|
||||||
{canReadSecretImports && isFormExpanded && (
|
{isFormExpanded && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td
|
<Td
|
||||||
colSpan={totalCols}
|
colSpan={totalCols}
|
||||||
|
|||||||
Reference in New Issue
Block a user