improvements: address feedback and setup queue worker profiles

This commit is contained in:
Scott Wilson
2025-06-02 14:40:06 -07:00
parent 3ae2ec1f51
commit d924580599
31 changed files with 356 additions and 254 deletions
@@ -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;
@@ -0,0 +1 @@
export * from "./DataSourceConfigDisplay";
@@ -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;
@@ -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>
);
@@ -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>
@@ -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
@@ -45,8 +45,6 @@ import {
import { SecretScanningDataSourceRow } from "./SecretScanningDataSourceRow";
// import { getSecretSyncDestinationColValues } from "./helpers";
enum DataSourcesOrderBy {
Findings = "findings",
Name = "name",
@@ -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);
@@ -1,9 +0,0 @@
import { ProjectSshConfigCasSection } from "./components";
export const ProjectSshTab = () => {
return (
<div>
<ProjectSshConfigCasSection />
</div>
);
};
@@ -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 +0,0 @@
export { ProjectSshConfigCasSection } from "./ProjectSshConfigCasSection";
@@ -1 +0,0 @@
export { ProjectSshTab } from "./ProjectSshTab";