import { useCallback } from "react"; import { faBan, faCheck, faCopy, faEllipsisV, faExpand, faInfoCircle, faSearch, faWarning } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { formatDistance } from "date-fns"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { SecretScanningScanStatusBadge } from "@app/components/secret-scanning"; import { Badge, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, IconButton, Td, Tooltip, Tr } from "@app/components/v2"; import { ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; import { RESOURCE_DESCRIPTION_HELPER } from "@app/helpers/secretScanningV2"; import { useToggle } from "@app/hooks"; import { SecretScanningFindingStatus, SecretScanningScanStatus, TSecretScanningDataSource, TSecretScanningResourceWithDetails, useTriggerSecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; type Props = { resource: TSecretScanningResourceWithDetails; dataSource: TSecretScanningDataSource; }; 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(); const handleTriggerScan = async () => { try { await triggerDataSourceScan.mutateAsync({ dataSourceId: dataSource.id, type: dataSource.type, projectId: dataSource.projectId, resourceId: id }); createNotification({ text: `Successfully triggered scan for ${name}`, type: "success" }); } catch { createNotification({ text: `Failed to trigger scan for ${name}`, type: "error" }); } }; const [isIdCopied, setIsIdCopied] = useToggle(false); const handleCopyId = useCallback( (idToCopy: string) => { setIsIdCopied.on(); navigator.clipboard.writeText(idToCopy); createNotification({ text: "Resource ID copied to clipboard", type: "info" }); const timer = setTimeout(() => setIsIdCopied.off(), 2000); // eslint-disable-next-line consistent-return return () => clearTimeout(timer); }, [isIdCopied] ); const resourceDetails = RESOURCE_DESCRIPTION_HELPER[dataSource.type]; return (

{name}

{/* eslint-disable-next-line no-nested-ternary */} {lastScanStatus?.match(/queued|scanning/) ? ( Scanning For Leaks ) : // eslint-disable-next-line no-nested-ternary lastScannedAt ? ( // eslint-disable-next-line no-nested-ternary unresolvedFindings ? ( navigate({ to: "/projects/$projectId/secret-scanning/findings", params: { projectId: dataSource.projectId }, search: { search: name, status: SecretScanningFindingStatus.Unresolved } }) } variant="primary" className="flex h-5 w-min cursor-pointer items-center gap-1.5 whitespace-nowrap" > {unresolvedFindings} Secret{unresolvedFindings > 1 ? "s" : ""} Detected ) : lastScanStatus === SecretScanningScanStatus.Failed ? ( No findings ) : ( No Secrets Detected ) ) : ( No findings )} {/* eslint-disable-next-line no-nested-ternary */} {lastScanStatus?.match(/queued|scanning|failed/) ? ( ) : lastScannedAt ? ( formatDistance(new Date(lastScannedAt), new Date(), { addSuffix: true }) ) : ( No scans )}
{!isActive && (
Inactive
)} } onClick={(e) => { e.stopPropagation(); handleCopyId(id); }} > Copy Resource ID {(isAllowed) => ( } onClick={(e) => { e.stopPropagation(); handleTriggerScan(); }} >
Trigger Scan
)}
); };