mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
improvements: address feedback and setup queue worker profiles
This commit is contained in:
+1
-1
@@ -102,7 +102,7 @@ export const GitHubDataSourceConfigFields = () => {
|
||||
isLoading={areRepositoriesLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
isMulti
|
||||
value={repositories?.find((project) => value.includes(project.name))}
|
||||
value={repositories?.filter((repository) => value.includes(repository.name))}
|
||||
onChange={(newValue) => {
|
||||
onChange(
|
||||
newValue
|
||||
|
||||
@@ -145,7 +145,7 @@ export const useUpdateSecretScanningConfig = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ projectId, ...params }: TSecretScanningConfig) => {
|
||||
const { data } = await apiRequest.patch<TGetSecretScanningConfigResponse>(
|
||||
`/api/v2/secret-scanning/configs/${projectId}`,
|
||||
`/api/v2/secret-scanning/configs?projectId=${projectId}`,
|
||||
params
|
||||
);
|
||||
|
||||
|
||||
@@ -237,7 +237,8 @@ export const useGetSecretScanningConfig = (
|
||||
queryKey: secretScanningV2Keys.configByProjectId(projectId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TGetSecretScanningConfigResponse>(
|
||||
`/api/v2/secret-scanning/configs/${projectId}`
|
||||
"/api/v2/secret-scanning/configs",
|
||||
{ params: { projectId } }
|
||||
);
|
||||
|
||||
return data.config;
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./DataSourceConfigDisplay";
|
||||
|
||||
+2
-1
@@ -9,7 +9,8 @@ import { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionSecretScanningDataSourceActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TSecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||
import { DataSourceConfigDisplay } from "@app/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/DataSourceConfigDisplay";
|
||||
|
||||
import { DataSourceConfigDisplay } from "./DataSourceConfigDisplay";
|
||||
|
||||
type Props = {
|
||||
dataSource: TSecretScanningDataSource;
|
||||
|
||||
+77
-54
@@ -1,5 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
faBan,
|
||||
faCheck,
|
||||
faCopy,
|
||||
faEllipsisV,
|
||||
@@ -50,6 +51,13 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => {
|
||||
const { id, name, lastScannedAt, lastScanStatus, unresolvedFindings, lastScanStatusMessage } =
|
||||
resource;
|
||||
|
||||
const {
|
||||
config: { includeRepos }
|
||||
} = dataSource;
|
||||
|
||||
// scott: will need to be differentiated by type once other data sources are available
|
||||
const isActive = includeRepos.includes("*") || includeRepos.includes(name);
|
||||
|
||||
const triggerDataSourceScan = useTriggerSecretScanningDataSource();
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -175,61 +183,76 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => {
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<Tooltip className="max-w-sm text-center" content="Options">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="Options"
|
||||
colorSchema="secondary"
|
||||
className="w-6"
|
||||
variant="plain"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent sideOffset={2} align="end">
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCopyId(id);
|
||||
}}
|
||||
>
|
||||
Copy Resource ID
|
||||
</DropdownMenuItem>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretScanningDataSourceActions.TriggerScans}
|
||||
a={ProjectPermissionSub.SecretScanningDataSources}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faExpand} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleTriggerScan();
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
position="left"
|
||||
sideOffset={42}
|
||||
content={`Manually trigger a scan for this ${resourceDetails.singularNoun}.`}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{!isActive && (
|
||||
<Tooltip
|
||||
className="text-xs"
|
||||
content={`This ${resourceDetails.singularNoun} will not be scanned due to exclusion in Data Source configuration.`}
|
||||
>
|
||||
<div className="ml-auto">
|
||||
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||
<FontAwesomeIcon icon={faBan} />
|
||||
<span>Inactive</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip className="max-w-sm text-center" content="Options">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="Options"
|
||||
colorSchema="secondary"
|
||||
className="w-6"
|
||||
variant="plain"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent sideOffset={2} align="end">
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCopyId(id);
|
||||
}}
|
||||
>
|
||||
Copy Resource ID
|
||||
</DropdownMenuItem>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretScanningDataSourceActions.TriggerScans}
|
||||
a={ProjectPermissionSub.SecretScanningDataSources}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed || !isActive}
|
||||
icon={<FontAwesomeIcon icon={faExpand} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleTriggerScan();
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||
<span> Trigger Scan</span>
|
||||
<FontAwesomeIcon
|
||||
className="text-bunker-300"
|
||||
size="sm"
|
||||
icon={faInfoCircle}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
position="left"
|
||||
sideOffset={42}
|
||||
content={`Manually trigger a scan for this ${resourceDetails.singularNoun}.`}
|
||||
>
|
||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||
<span> Trigger Scan</span>
|
||||
<FontAwesomeIcon
|
||||
className="text-bunker-300"
|
||||
size="sm"
|
||||
icon={faInfoCircle}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
||||
+104
-9
@@ -1,8 +1,12 @@
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
faArrowDown,
|
||||
faArrowUp,
|
||||
faBan,
|
||||
faBullseye,
|
||||
faCheckCircle,
|
||||
faFilter,
|
||||
faInfoCircle,
|
||||
faMagnifyingGlass,
|
||||
faSearch
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -10,6 +14,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Input,
|
||||
@@ -20,6 +29,7 @@ import {
|
||||
TBody,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
@@ -45,6 +55,15 @@ type Props = {
|
||||
dataSource: TSecretScanningDataSource;
|
||||
};
|
||||
|
||||
enum ResourceStatus {
|
||||
Active = "active",
|
||||
Inactive = "inactive"
|
||||
}
|
||||
|
||||
type ResourceFilters = {
|
||||
status: ResourceStatus[];
|
||||
};
|
||||
|
||||
export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
@@ -53,6 +72,10 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
ProjectPermissionSub.SecretScanningDataSources
|
||||
);
|
||||
|
||||
const [filters, setFilters] = useState<ResourceFilters>({
|
||||
status: [ResourceStatus.Active]
|
||||
});
|
||||
|
||||
const { data: resources = [], isPending: isResourcesPending } = useListSecretScanningResources(
|
||||
{ dataSourceId: dataSource.id, type: dataSource.type },
|
||||
{
|
||||
@@ -81,6 +104,19 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
resources
|
||||
.filter((resource) => {
|
||||
const { name } = resource;
|
||||
const {
|
||||
config: { includeRepos }
|
||||
} = dataSource;
|
||||
|
||||
// scott: will need to be differentiated by type once other data sources are available
|
||||
const isActive = includeRepos.includes("*") || includeRepos.includes(name);
|
||||
|
||||
if (filters.status.length === 1) {
|
||||
if (filters.status.includes(ResourceStatus.Active)) {
|
||||
return isActive;
|
||||
}
|
||||
return !isActive;
|
||||
}
|
||||
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
|
||||
@@ -114,7 +150,7 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
return resourceOne.name.toLowerCase().localeCompare(resourceTwo.name.toLowerCase());
|
||||
}
|
||||
}),
|
||||
[resources, orderDirection, search, orderBy]
|
||||
[resources, orderDirection, search, orderBy, filters, dataSource]
|
||||
);
|
||||
|
||||
useResetPageHelper({
|
||||
@@ -141,15 +177,74 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
|
||||
const resourceDetails = RESOURCE_DESCRIPTION_HELPER[dataSource.type];
|
||||
|
||||
const isTableFiltered = Boolean(filters.status.length);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder={`Search ${resourceDetails.pluralNoun}...`}
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder={`Search ${resourceDetails.pluralNoun}...`}
|
||||
className="flex-1"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="Filter data sources"
|
||||
variant="plain"
|
||||
size="sm"
|
||||
className={twMerge(
|
||||
"flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10",
|
||||
isTableFiltered && "border-primary/50 text-primary"
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFilter} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="thin-scrollbar max-h-[70vh] overflow-y-auto" align="end">
|
||||
<DropdownMenuLabel className="flex w-full items-center justify-between">
|
||||
Status
|
||||
<Tooltip
|
||||
content={`Inactive ${resourceDetails.pluralNoun} will not be scanned due to exclusion in Data Source configuration.`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="text-mineshaft-400" />
|
||||
</Tooltip>
|
||||
</DropdownMenuLabel>
|
||||
{Object.values(ResourceStatus).map((status) => (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
status: prev.status.includes(status)
|
||||
? prev.status.filter((s) => s !== status)
|
||||
: [...prev.status, status]
|
||||
}));
|
||||
}}
|
||||
key={status}
|
||||
icon={
|
||||
filters.status.includes(status) && (
|
||||
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||
)
|
||||
}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={status === ResourceStatus.Active ? faBullseye : faBan}
|
||||
className={
|
||||
status === ResourceStatus.Active ? "text-primary" : "text-mineshaft-400"
|
||||
}
|
||||
/>
|
||||
<span className="capitalize">{status.replace("-", " ")}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
|
||||
+4
-3
@@ -24,10 +24,11 @@ export const SecretScanningDataSourcesSection = () => {
|
||||
|
||||
const { data: dataSources = [], isPending: isDataSourcesPending } =
|
||||
useListSecretScanningDataSources(currentWorkspace.id, {
|
||||
refetchInterval: 30000
|
||||
refetchInterval: 30000,
|
||||
enabled: subscription.secretScanning
|
||||
});
|
||||
|
||||
if (isDataSourcesPending)
|
||||
if (subscription.secretScanning && isDataSourcesPending)
|
||||
return (
|
||||
<div className="flex h-[60vh] flex-col items-center justify-center gap-2">
|
||||
<Spinner />
|
||||
@@ -57,7 +58,7 @@ export const SecretScanningDataSourcesSection = () => {
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-sm text-bunker-300">
|
||||
Use App Connections to scan for secret leaks from third-party services.
|
||||
Configure Data Sources to scan for secret leaks from third-party services.
|
||||
</p>
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
|
||||
-2
@@ -45,8 +45,6 @@ import {
|
||||
|
||||
import { SecretScanningDataSourceRow } from "./SecretScanningDataSourceRow";
|
||||
|
||||
// import { getSecretSyncDestinationColValues } from "./helpers";
|
||||
|
||||
enum DataSourcesOrderBy {
|
||||
Findings = "findings",
|
||||
Name = "name",
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ export const DeleteProjectSection = () => {
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
to: `/organization/${ProjectType.SecretScanning}/overview` as const
|
||||
});
|
||||
handlePopUpClose("deleteWorkspace");
|
||||
} catch (err) {
|
||||
@@ -123,7 +123,7 @@ export const DeleteProjectSection = () => {
|
||||
});
|
||||
|
||||
navigate({
|
||||
to: `/organization/${ProjectType.SecretManager}/overview` as const
|
||||
to: `/organization/${ProjectType.SecretScanning}/overview` as const
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { ProjectSshConfigCasSection } from "./components";
|
||||
|
||||
export const ProjectSshTab = () => {
|
||||
return (
|
||||
<div>
|
||||
<ProjectSshConfigCasSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useGetProjectSshConfig,
|
||||
useListWorkspaceSshCas,
|
||||
useUpdateProjectSshConfig
|
||||
} from "@app/hooks/api";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
defaultUserSshCaId: z.string(),
|
||||
defaultHostSshCaId: z.string()
|
||||
})
|
||||
.required();
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
||||
export const ProjectSshConfigCasSection = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data: sshConfig } = useGetProjectSshConfig(currentWorkspace.id);
|
||||
const { data: sshCas } = useListWorkspaceSshCas(currentWorkspace.id);
|
||||
const { mutate: updateProjectSshConfig } = useUpdateProjectSshConfig();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (sshConfig) {
|
||||
reset({
|
||||
defaultUserSshCaId: sshConfig.defaultUserSshCaId || "",
|
||||
defaultHostSshCaId: sshConfig.defaultHostSshCaId || ""
|
||||
});
|
||||
}
|
||||
}, [sshConfig]);
|
||||
|
||||
const onFormSubmit = async ({ defaultUserSshCaId, defaultHostSshCaId }: FormData) => {
|
||||
try {
|
||||
await updateProjectSshConfig({
|
||||
projectId: currentWorkspace.id,
|
||||
defaultUserSshCaId: defaultUserSshCaId || undefined,
|
||||
defaultHostSshCaId: defaultHostSshCaId || undefined
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated SSH project settings",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to update SSH project settings",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<p className="mb-8 text-xl font-semibold">Certificate Authorities</p>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="defaultUserSshCaId"
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Default User CA"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="min-w-[20rem]"
|
||||
>
|
||||
{sshCas?.map(({ id, friendlyName }) => (
|
||||
<SelectItem value={String(id || "")} key={friendlyName}>
|
||||
{friendlyName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="defaultHostSshCaId"
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Default Host CA"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="min-w-[20rem]"
|
||||
>
|
||||
{sshCas?.map(({ id, friendlyName }) => (
|
||||
<SelectItem value={String(id || "")} key={friendlyName}>
|
||||
{friendlyName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Project}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isAllowed}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
-1
@@ -1 +0,0 @@
|
||||
export { ProjectSshConfigCasSection } from "./ProjectSshConfigCasSection";
|
||||
@@ -1 +0,0 @@
|
||||
export { ProjectSshTab } from "./ProjectSshTab";
|
||||
Reference in New Issue
Block a user