diff --git a/src/Pages-Devtron-2.0/SecurityCenter/Overview/Overview.tsx b/src/Pages-Devtron-2.0/SecurityCenter/Overview/Overview.tsx deleted file mode 100644 index a39a994aeb..0000000000 --- a/src/Pages-Devtron-2.0/SecurityCenter/Overview/Overview.tsx +++ /dev/null @@ -1 +0,0 @@ -export const Overview = () =>
Overview
diff --git a/src/Pages-Devtron-2.0/SecurityCenter/Overview/index.ts b/src/Pages-Devtron-2.0/SecurityCenter/Overview/index.ts deleted file mode 100644 index bb81fdde18..0000000000 --- a/src/Pages-Devtron-2.0/SecurityCenter/Overview/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Overview' diff --git a/src/components/Navigation/constants.ts b/src/components/Navigation/constants.ts index ea263de3d8..b92662d50b 100644 --- a/src/components/Navigation/constants.ts +++ b/src/components/Navigation/constants.ts @@ -359,10 +359,17 @@ export const NAVIGATION_LIST: NavigationGroupType[] = [ icon: 'ic-chart-line-up', }, { - title: 'Security Scans', - dataTestId: 'security-scans', - id: 'security-center-security-scans', - href: COMMON_URLS.SECURITY_CENTER_SCANS, + title: 'Vulnerabilities', + dataTestId: 'security-vulnerabilities', + id: 'security-center-security-vulnerabilities', + href: COMMON_URLS.SECURITY_CENTER_VULNERABILITIES, + icon: 'ic-bug', + }, + { + title: 'Security Enablement', + dataTestId: 'security-enablement', + id: 'security-center-security-enablement', + href: COMMON_URLS.SECURITY_CENTER_SECURITY_ENABLEMENT, icon: 'ic-security-scan', }, { @@ -486,8 +493,8 @@ export const NAVIGATION_LIST: NavigationGroupType[] = [ href: `${URLS.GLOBAL_CONFIG_AUTH}/${Routes.SSO_LOGIN_SERVICES}`, }, { - title: 'Host URLS', - dataTestId: 'host-urls', + title: 'Host URL', + dataTestId: 'host-url', id: 'global-configuration-host-urls', icon: 'ic-link', href: URLS.GLOBAL_CONFIG_HOST_URL, diff --git a/src/components/security/AddCVEPolicy/VulnerabilityExposure.tsx b/src/components/security/AddCVEPolicy/VulnerabilityExposure.tsx index 62c188f4ab..6e95e90af6 100644 --- a/src/components/security/AddCVEPolicy/VulnerabilityExposure.tsx +++ b/src/components/security/AddCVEPolicy/VulnerabilityExposure.tsx @@ -92,18 +92,18 @@ const ExposureListContainer = ({ urlFilters }: ExposureListContainerProps) => { ) } - const clusterListOptions: SelectPickerOptionType[] = filtersResponse?.filters.clusters || [] - const environmentListOptions: SelectPickerOptionType[] = filtersResponse?.filters.environments || [] + const clusterListOptions: SelectPickerOptionType[] = filtersResponse?.cluster || [] + const environmentListOptions: SelectPickerOptionType[] = filtersResponse?.environment || [] const getFilterLabelFromValue = (filterKey: string, filterValue: string) => { if (filterKey === VulnerabilityExposureFilterKeys.cluster) { return ( - filtersResponse?.filters.clusters.find((clusterOption) => clusterOption.value === filterValue)?.label ?? - filterValue + (filtersResponse?.cluster.find((clusterOption) => clusterOption.value === filterValue) + ?.label as string) ?? filterValue ) } return ( - filtersResponse?.filters.environments.find((envOption) => envOption.value === filterValue)?.label ?? + (filtersResponse?.environment.find((envOption) => envOption.value === filterValue)?.label as string) ?? filterValue ) } @@ -114,7 +114,7 @@ const ExposureListContainer = ({ urlFilters }: ExposureListContainerProps) => { })) const selectedEnvironments: SelectPickerOptionType[] = environment.map((envId) => ({ - label: getFilterLabelFromValue(VulnerabilityExposureFilterKeys.cluster, envId), + label: getFilterLabelFromValue(VulnerabilityExposureFilterKeys.environment, envId), value: envId, })) diff --git a/src/components/security/AddCveModal.tsx b/src/components/security/AddCveModal.tsx index 7632c2b180..9df7bb64a6 100644 --- a/src/components/security/AddCveModal.tsx +++ b/src/components/security/AddCveModal.tsx @@ -16,7 +16,6 @@ import React, { Component } from 'react' import { Progressing, VisibleModal, CustomInput } from '@devtron-labs/devtron-fe-common-lib' -import { getCVEPolicies } from './security.service' import { CVE_ID_NOT_FOUND, ViewType } from '../../config' import { AddCveModalProps, AddCveModalState, ClusterEnvironment, VulnerabilityAction } from './security.types' import { ReactComponent as Close } from '../../assets/icons/ic-close.svg' @@ -33,7 +32,6 @@ export class AddCveModal extends Component { } this.handleCveChange = this.handleCveChange.bind(this) this.handlePolicyChange = this.handlePolicyChange.bind(this) - this.searchCVE = this.searchCVE.bind(this) } handleCveChange(event: React.ChangeEvent): void { @@ -54,21 +52,6 @@ export class AddCveModal extends Component { } } - searchCVE(event): void { - this.setState({ view: ViewType.LOADING }) - getCVEPolicies(this.state.cve) - .then((response) => { - this.setState({ - cve: this.state.cve, - clusters: response.result.clusters, - view: ViewType.FORM, - }) - }) - .catch((error) => { - this.setState({ view: ViewType.FORM, isCveError: true }) - }) - } - renderHeader() { return (
diff --git a/src/components/security/Security.tsx b/src/components/security/Security.tsx index 27b60575eb..c3cbdcb7c8 100644 --- a/src/components/security/Security.tsx +++ b/src/components/security/Security.tsx @@ -20,12 +20,13 @@ import { URLS } from '@devtron-labs/devtron-fe-common-lib' import { importComponentFromFELibrary } from '@Components/common' -import { SecurityScansTab } from './SecurityScansTab/SecurityScansTab' import { SecurityPoliciesTab } from './SecurityPoliciesTab' +import { VulnerabilitiesRouter } from './Vulnerabilities' import './security.scss' const SecurityCenterOverview = importComponentFromFELibrary('SecurityCenterOverview', null, 'function') +const SecurityEnablement = importComponentFromFELibrary('SecurityEnablement', null, 'function') export const Security = () => ( @@ -34,12 +35,17 @@ export const Security = () => ( )} - - + + + {SecurityEnablement && ( + + + + )} - + ) diff --git a/src/components/security/SecurityPageHeader.tsx b/src/components/security/SecurityPageHeader.tsx deleted file mode 100644 index 0df87cce71..0000000000 --- a/src/components/security/SecurityPageHeader.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useLocation } from 'react-router-dom' - -import { BreadCrumb, PageHeader, useBreadcrumb } from '@devtron-labs/devtron-fe-common-lib' - -import { getSecurityBreadcrumbAlias, getTippyContent } from './security.util' - -export const SecurityPageHeader = () => { - const { pathname } = useLocation() - const { breadcrumbs } = useBreadcrumb(getSecurityBreadcrumbAlias(pathname), [pathname]) - - const renderBreadcrumbs = () => - - return ( - - ) -} diff --git a/src/components/security/SecurityPoliciesTab.tsx b/src/components/security/SecurityPoliciesTab.tsx index 15f6e05305..65dc4b14f6 100644 --- a/src/components/security/SecurityPoliciesTab.tsx +++ b/src/components/security/SecurityPoliciesTab.tsx @@ -15,15 +15,35 @@ */ import { Switch, Route, Redirect, NavLink, useRouteMatch } from 'react-router-dom' + +import { + BreadCrumb, + BreadcrumbText, + getSecurityCenterBreadcrumb, + PageHeader, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + import { SecurityPolicyGlobal } from './SecurityPolicyGlobal' import { SecurityPolicyCluster } from './SecurityPolicyCluster' import { SecurityPolicyApp } from './SecurityPolicyApp' import { SecurityPolicyEnvironment } from './SecurityPolicyEnvironment' import { VulnerabilityExposure } from './AddCVEPolicy' -import { SecurityPageHeader } from './SecurityPageHeader' export const SecurityPoliciesTab = () => { const { path } = useRouteMatch() + + const { breadcrumbs } = useBreadcrumb({ + alias: { + ...getSecurityCenterBreadcrumb(), + policies: { + component: , + }, + }, + }) + + const renderBreadcrumbs = () => + const renderRouter = () => { return ( @@ -39,7 +59,8 @@ export const SecurityPoliciesTab = () => { return (
- + +
{ +const SecurityScansTab = () => { + const { push } = useHistory() const urlFilters = useUrlFilters>({ parseSearchParams, initialSortKey: SecurityListSortableKeys.APP_NAME, @@ -79,9 +83,9 @@ export const SecurityScansTab = () => { severity, environment, cluster, - searchType, sortBy, sortOrder, + scanStatus, handleSorting, changePage, changePageSize, @@ -98,27 +102,27 @@ export const SecurityScansTab = () => { const payload: ScanListPayloadType = { offset, size: pageSize, - appName: searchType === SearchType.APPLICATION ? searchKey : '', - cveName: searchType === SearchType.VULNERABILITY ? searchKey : '', + appName: searchKey, severity: severity.map((severityFilterValue) => SeverityFilterValues[severityFilterValue]), clusterIds: cluster.map((clusterId) => +clusterId), envIds: environment.map((envId) => +envId), sortBy, sortOrder, + scanStatus, } const filterConfig = useMemo( - () => ({ offset, pageSize, searchKey, sortBy, sortOrder, severity, cluster, environment, searchType }), + () => ({ offset, pageSize, searchKey, sortBy, sortOrder, severity, cluster, environment, scanStatus }), [ offset, pageSize, searchKey, sortBy, sortOrder, + scanStatus, JSON.stringify(severity), JSON.stringify(cluster), JSON.stringify(environment), - searchType, ], ) @@ -129,11 +133,15 @@ export const SecurityScansTab = () => { getVulnerabilityFilterData(), ) - const getClusterLabelFromId = (clusterId: string) => - clusterEnvListResult?.filters?.clusters.find((clusterOption) => clusterOption.value === clusterId).label + const getClusterLabelFromId = (clusterId: string) => { + const option = clusterEnvListResult?.cluster.find((clusterOption) => clusterOption.value === clusterId) + return `${option?.label || ''}` + } - const getEnvLabelFromId = (envId: string) => - clusterEnvListResult?.filters?.environments.find((envOption) => envOption.value === envId).label + const getEnvLabelFromId = (envId: string) => { + const option = clusterEnvListResult?.environment.find((envOption) => envOption.value === envId) + return `${option?.label || ''}` + } const getLabelFromValue = (filterLabel: string, filterValue: string): string => { if (filterLabel === SecurityScansTabMultiFilterKeys.environment) { @@ -156,14 +164,24 @@ export const SecurityScansTab = () => { ) const isLoading = scanListLoading || getIsRequestAborted(scanListError) + const isNotScannedList = scanStatus === ScanTypeOptions.NOT_SCANNED const getFilterUpdateHandler = (filterKey: SecurityScansTabMultiFilterKeys) => (selectedOption: SelectPickerOptionType[]) => { updateSearchParams({ [filterKey]: selectedOption.map((option) => String(option.value)) }) } - const updateSearchType = (selectedOption: OptionType) => { - updateSearchParams({ searchType: selectedOption.value }) + const handleSegmentControlChange: SegmentedControlProps['onChange'] = (selectedSegment) => { + // Clear all filters and set only scanStatus in a single operation + // This ensures scanStatus is the only param in the URL + updateSearchParams({ + scanStatus: selectedSegment.value as ScanTypeOptions, + severity: [], + cluster: [], + environment: [], + }) + handleSearch('') + changePage(1) } const selectedSeverities = severity.map((severityId) => ({ @@ -195,6 +213,10 @@ export const SecurityScansTab = () => { }) } + const redirectToAppEnv = (appId: number, envId: number) => { + push(`${URLS.APPLICATION_MANAGEMENT_APP}/${appId}/details/${envId}`) + } + const isScanListEmpty = !isLoading && !securityScansResult?.result.securityScans.length const groupedFiltersPropsMap: GroupedFilterSelectPickerProps['filterSelectPickerPropsMap'] = useMemo( @@ -206,7 +228,7 @@ export const SecurityScansTab = () => { isLoading: clusterEnvListLoading, appliedFilterOptions: selectedClusters, handleApplyFilter: getFilterUpdateHandler(SecurityScansTabMultiFilterKeys.cluster), - options: clusterEnvListResult?.filters?.clusters ?? [], + options: clusterEnvListResult?.cluster ?? [], optionListError: clusterEnvListError, reloadOptions: reloadClusterEnvOptions, }, @@ -217,7 +239,7 @@ export const SecurityScansTab = () => { isLoading: clusterEnvListLoading, appliedFilterOptions: selectedEnvironments, handleApplyFilter: getFilterUpdateHandler(SecurityScansTabMultiFilterKeys.environment), - options: clusterEnvListResult?.filters?.environments ?? [], + options: clusterEnvListResult?.environment ?? [], optionListError: clusterEnvListError, reloadOptions: reloadClusterEnvOptions, }, @@ -228,7 +250,7 @@ export const SecurityScansTab = () => { isLoading: clusterEnvListLoading, appliedFilterOptions: selectedSeverities, handleApplyFilter: getFilterUpdateHandler(SecurityScansTabMultiFilterKeys.severity), - options: clusterEnvListResult?.filters?.severity ?? [], + options: clusterEnvListResult?.severity ?? [], optionListError: clusterEnvListError, reloadOptions: reloadClusterEnvOptions, }, @@ -237,7 +259,7 @@ export const SecurityScansTab = () => { ) const renderHeader = () => ( -
+
{ />
IMAGE VULNERABILITY SCAN
-
- -
+ {!isNotScannedList && ( + <> +
+ +
+
+ +
+ + )}
) const renderFilters = () => ( -
+
-
-
- -
- -
+ + +
id="grouped-scan-list-filters" - options={SCAN_LIST_GROUP_FILTER_OPTIONS} + options={getGroupFilterItems(scanStatus)} filterSelectPickerPropsMap={groupedFiltersPropsMap} isFilterApplied={areGroupedFiltersActive} /> @@ -312,7 +341,7 @@ export const SecurityScansTab = () => { ) const renderSavedFilters = () => ( - > + > filterConfig={{ severity, cluster, @@ -332,15 +361,17 @@ export const SecurityScansTab = () => { return (
{arrayLoading.map((value) => ( -
- - - - - +
+ + + + + {!isNotScannedList && ( + <> + + + + )}
))}
@@ -349,27 +380,45 @@ export const SecurityScansTab = () => { return ( <> - {securityScansResult.result.securityScans.map((scan) => ( -
handleOpenScanDetailsModal(event, scan)} - key={`${scan.name}-${scan.environment}`} - role="button" - tabIndex={0} - > - - - {scan.name} - - {scan.environment} -
{getSeverityWithCount(scan.severityCount)}
- - {scan.lastExecution && scan.lastExecution !== ZERO_TIME_STRING - ? dayjs(scan.lastExecution).format(DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT) - : ''} - -
- ))} +
+ {securityScansResult.result.securityScans.map((scan) => { + const totalSeverities = Object.values(scan.severityCount).reduce((acc, curr) => acc + curr, 0) + return ( +
redirectToAppEnv(scan.appId, scan.envId) + : (event) => handleOpenScanDetailsModal(event, scan) + } + key={`${scan.name}-${scan.environment}`} + role="button" + tabIndex={0} + > + + + {scan.name} + + {scan.environment} +
{isNotScannedList ? 'Not Scanned' : getSeverityWithCount(scan.severityCount)}
+ {!isNotScannedList && ( + <> + + {scan.fixableVulnerabilities} out of {totalSeverities} + + + {scan.lastExecution && scan.lastExecution !== ZERO_TIME_STRING + ? dayjs(scan.lastExecution).format( + DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT, + ) + : ''} + + + )} +
+ ) + })} +
{securityScansResult.result.totalCount > DEFAULT_BASE_PAGE_SIZE && ( { <> {renderHeader()} {renderScanList()} - {renderScanDetailsModal()} + {!isNotScannedList && renderScanDetailsModal()} ) } @@ -442,9 +491,10 @@ export const SecurityScansTab = () => { } return ( -
- +
{renderMainContent()}
) } + +export default SecurityScansTab diff --git a/src/components/security/SecurityScansTab/constants.tsx b/src/components/security/SecurityScansTab/constants.tsx index bb36d29215..46aea41154 100644 --- a/src/components/security/SecurityScansTab/constants.tsx +++ b/src/components/security/SecurityScansTab/constants.tsx @@ -14,36 +14,16 @@ * limitations under the License. */ -import { GroupedFilterSelectPickerProps } from '@devtron-labs/devtron-fe-common-lib' +import { SegmentedControlProps } from '@devtron-labs/devtron-fe-common-lib' -import { ScanDetailsType, SearchType, SearchTypeOptionType, SecurityScansTabMultiFilterKeys } from './types' +import { ScanDetailsType, ScanTypeOptions } from './types' export const INITIAL_SCAN_DETAILS: ScanDetailsType = { appId: 0, envId: 0, } -export const SEARCH_TYPE_OPTIONS: SearchTypeOptionType[] = [ - { label: 'Application', value: SearchType.APPLICATION }, - { label: 'Vulnerability', value: SearchType.VULNERABILITY }, +export const SCANNED_UNSCANNED_CONTROL_SEGMENTS: SegmentedControlProps['segments'] = [ + { label: 'Scanned', value: ScanTypeOptions.SCANNED }, + { label: 'Not scanned', value: ScanTypeOptions.NOT_SCANNED }, ] - -export const SCAN_LIST_GROUP_FILTER_OPTIONS: GroupedFilterSelectPickerProps['options'] = - [ - { - items: [ - { - id: SecurityScansTabMultiFilterKeys.cluster, - label: 'Cluster', - }, - { - id: SecurityScansTabMultiFilterKeys.environment, - label: 'Environment', - }, - { - id: SecurityScansTabMultiFilterKeys.severity, - label: 'Severity', - }, - ], - }, - ] diff --git a/src/components/security/SecurityScansTab/index.ts b/src/components/security/SecurityScansTab/index.ts new file mode 100644 index 0000000000..b75a3fa4b7 --- /dev/null +++ b/src/components/security/SecurityScansTab/index.ts @@ -0,0 +1 @@ +export { default as SecurityScansTab } from './SecurityScansTab' diff --git a/src/components/security/SecurityScansTab/types.tsx b/src/components/security/SecurityScansTab/types.tsx index 6a0052d4d5..1b13d6ca84 100644 --- a/src/components/security/SecurityScansTab/types.tsx +++ b/src/components/security/SecurityScansTab/types.tsx @@ -16,6 +16,12 @@ import { SortingOrder } from '@devtron-labs/devtron-fe-common-lib' +export enum ScanTypeOptions { + SCANNED = 'scanned', + NOT_SCANNED = 'not-scanned', + ALL = 'all', +} + export enum SecurityScansTabMultiFilterKeys { severity = 'severity', environment = 'environment', @@ -23,16 +29,11 @@ export enum SecurityScansTabMultiFilterKeys { } export enum SecurityScansTabSingleFilterKeys { - searchType = 'searchType', + scanStatus = 'scanStatus', } -export interface ScanListUrlFiltersType - extends Record, - Record {} - -export enum SearchType { - APPLICATION = 'appName', - VULNERABILITY = 'cveName', +export interface ScanListUrlFiltersType extends Record { + [SecurityScansTabSingleFilterKeys.scanStatus]: ScanTypeOptions } export interface ScanDetailsType { @@ -50,12 +51,12 @@ export interface ScanListPayloadType { offset: number size: number appName: string - cveName: string severity: number[] clusterIds: number[] envIds: number[] sortBy: SecurityListSortableKeys sortOrder: SortingOrder + scanStatus: ScanTypeOptions } export enum SeverityFilterValues { @@ -65,8 +66,3 @@ export enum SeverityFilterValues { 'low' = 0, 'unknown' = 5, } - -export interface SearchTypeOptionType { - label: string - value: SearchType -} diff --git a/src/components/security/SecurityScansTab/utils.tsx b/src/components/security/SecurityScansTab/utils.tsx index f6dfa30f8f..225affbe77 100644 --- a/src/components/security/SecurityScansTab/utils.tsx +++ b/src/components/security/SecurityScansTab/utils.tsx @@ -14,65 +14,72 @@ * limitations under the License. */ -import { Badge, ComponentSizeType, SeveritiesDTO, SeverityCount } from '@devtron-labs/devtron-fe-common-lib' +import { + Badge, + ComponentSizeType, + GroupedFilterSelectPickerProps, + SeveritiesDTO, + SeverityChip, + SeverityCount, +} from '@devtron-labs/devtron-fe-common-lib' -import { SearchType, SecurityScansTabMultiFilterKeys, SecurityScansTabSingleFilterKeys } from './types' +import { ScanTypeOptions, SecurityScansTabMultiFilterKeys, SecurityScansTabSingleFilterKeys } from './types' export const parseSearchParams = (searchParams: URLSearchParams) => ({ [SecurityScansTabMultiFilterKeys.severity]: searchParams.getAll(SecurityScansTabMultiFilterKeys.severity) || [], [SecurityScansTabMultiFilterKeys.environment]: searchParams.getAll(SecurityScansTabMultiFilterKeys.environment) || [], [SecurityScansTabMultiFilterKeys.cluster]: searchParams.getAll(SecurityScansTabMultiFilterKeys.cluster) || [], - [SecurityScansTabSingleFilterKeys.searchType]: - searchParams.get(SecurityScansTabSingleFilterKeys.searchType) || 'appName', + [SecurityScansTabSingleFilterKeys.scanStatus]: + (searchParams.get(SecurityScansTabSingleFilterKeys.scanStatus) as ScanTypeOptions) || ScanTypeOptions.SCANNED, }) -export const getSearchLabelFromValue = (searchType: string) => { - if (searchType === SearchType.VULNERABILITY) return 'Vulnerability' - return 'Application' -} - const SEVERITY_ORDER = [ - { key: SeveritiesDTO.CRITICAL, label: 'Critical', variant: 'negative' }, - { key: SeveritiesDTO.HIGH, label: 'High', variant: 'custom', fontColor: 'R500', bgColor: 'R100' }, - { key: SeveritiesDTO.MEDIUM, label: 'Medium', variant: 'custom', fontColor: 'O600', bgColor: 'O100' }, - { key: SeveritiesDTO.LOW, label: 'Low', variant: 'warning' }, - { key: SeveritiesDTO.UNKNOWN, label: 'Unknown', variant: 'neutral' }, -] as const + SeveritiesDTO.CRITICAL, + SeveritiesDTO.HIGH, + SeveritiesDTO.MEDIUM, + SeveritiesDTO.LOW, + SeveritiesDTO.UNKNOWN, +] export const getSeverityWithCount = (severityCount: SeverityCount) => { const badges = [] // eslint-disable-next-line no-restricted-syntax for (const item of SEVERITY_ORDER) { - if (severityCount[item.key]) { - if (item.variant === 'custom') { - badges.push( - , - ) - } else { - badges.push( - , - ) - } + const count = severityCount[item] + if (count) { + badges.push() } } - if (badges.length === 0) { - return + return } return
{badges}
} + +export const getGroupFilterItems: ( + scanStatus: ScanTypeOptions, +) => GroupedFilterSelectPickerProps['options'] = (scanStatus) => [ + { + items: [ + { + id: SecurityScansTabMultiFilterKeys.cluster, + label: 'Cluster', + }, + { + id: SecurityScansTabMultiFilterKeys.environment, + label: 'Environment', + }, + ...(scanStatus === ScanTypeOptions.SCANNED + ? [ + { + id: SecurityScansTabMultiFilterKeys.severity, + label: 'Severity', + }, + ] + : []), + ], + }, +] diff --git a/src/components/security/Vulnerabilities/CVEList/CVEList.tsx b/src/components/security/Vulnerabilities/CVEList/CVEList.tsx new file mode 100644 index 0000000000..2e2b1ddfec --- /dev/null +++ b/src/components/security/Vulnerabilities/CVEList/CVEList.tsx @@ -0,0 +1,289 @@ +import { + ComponentSizeType, + FilterChips, + FiltersTypeEnum, + getCVEUrlFromCVEName, + getSelectPickerOptionByValue, + getSelectPickerOptionsByValue, + GroupedFilterSelectPicker, + GroupedFilterSelectPickerProps, + PaginationEnum, + SearchBar, + SelectPickerOptionType, + Severity, + SEVERITY_LABEL_MAP, + SeverityChip, + Table, + TableCellComponentProps, + TableProps, + TableViewWrapperProps, + useQuery, +} from '@devtron-labs/devtron-fe-common-lib' + +import { DISCOVERY_AGE_FILTER_OPTIONS } from '../constants' +import { FixAvailabilityOptions } from '../types' +import VulnerabilityViewTypeSelect from '../VulnerabilityViewTypeSelect' +import { CVE_LIST_GROUP_FILTER_OPTIONS } from './constants' +import { getCVEList, getCVEListFilters } from './service' +import { CVEDetails, CVEListFilterData, CVEListFilters } from './types' +import { getFilterChipLabel, parseSearchParams } from './utils' + +const SeverityCellComponent = ({ row }: TableCellComponentProps) => { + const { data } = row + return ( +
+ +
+ ) +} + +const CVENameCellComponent = ({ row }: TableCellComponentProps) => { + const { data } = row + return ( + + {data.cveName} + + ) +} + +const CVETableWrapper = ({ + children, + updateSearchParams, + handleSearch, + ageOfDiscovery, + fixAvailability, + application, + environment, + severity, + cluster, + clearFilters, + searchKey, +}: TableViewWrapperProps>) => { + const { isFetching, data, refetch, error } = useQuery({ + queryKey: ['cveListFilters'], + queryFn: getCVEListFilters, + }) + + const { + fixAvailability: fixAvailabilityOptions, + ageOfDiscovery: ageOfDiscoveryOptions, + application: applicationOptions, + environment: environmentOptions, + severity: severityOptions, + cluster: clusterOptions, + } = data || {} + + const getUpdateHandler = (key: CVEListFilters) => (updatedOptions: SelectPickerOptionType[]) => { + updateSearchParams({ + [key]: updatedOptions.map((option) => option.value), + }) + } + + const commonGroupedFilterProps = { + isDisabled: isFetching, + isLoading: isFetching, + optionListError: error, + reloadOptionList: refetch, + } + + const groupedFilterOptionsMap: GroupedFilterSelectPickerProps['filterSelectPickerPropsMap'] = { + fixAvailability: { + ...commonGroupedFilterProps, + inputId: 'cve-fix-availability-filter', + placeholder: 'Fix Availability', + options: fixAvailabilityOptions || [], + appliedFilterOptions: getSelectPickerOptionsByValue(fixAvailabilityOptions || [], fixAvailability || []), + handleApplyFilter: getUpdateHandler('fixAvailability'), + }, + ageOfDiscovery: { + ...commonGroupedFilterProps, + inputId: 'cve-age-of-discovery-filter', + placeholder: 'Age of Discovery', + options: ageOfDiscoveryOptions || [], + appliedFilterOptions: getSelectPickerOptionsByValue(ageOfDiscoveryOptions || [], ageOfDiscovery || []), + handleApplyFilter: getUpdateHandler('ageOfDiscovery'), + }, + application: { + ...commonGroupedFilterProps, + inputId: 'cve-application-filter', + placeholder: 'Application', + options: applicationOptions || [], + appliedFilterOptions: getSelectPickerOptionsByValue(applicationOptions || [], application || []), + handleApplyFilter: getUpdateHandler('application'), + }, + environment: { + ...commonGroupedFilterProps, + inputId: 'cve-environment-filter', + placeholder: 'Environment', + options: environmentOptions || [], + appliedFilterOptions: getSelectPickerOptionsByValue(environmentOptions || [], environment || []), + handleApplyFilter: getUpdateHandler('environment'), + }, + severity: { + ...commonGroupedFilterProps, + inputId: 'cve-severity-filter', + placeholder: 'Severity', + options: severityOptions || [], + appliedFilterOptions: getSelectPickerOptionsByValue(severityOptions || [], severity || []), + handleApplyFilter: getUpdateHandler('severity'), + }, + cluster: { + ...commonGroupedFilterProps, + inputId: 'cve-cluster-filter', + placeholder: 'Cluster', + options: clusterOptions || [], + appliedFilterOptions: getSelectPickerOptionsByValue(clusterOptions || [], cluster || []), + handleApplyFilter: getUpdateHandler('cluster'), + }, + } + + const getFormattedFilterValue = (filterKey: CVEListFilters, filterValue: string): string => { + switch (filterKey) { + case 'fixAvailability': + return filterValue === FixAvailabilityOptions.FIX_AVAILABLE ? 'Fix Available' : 'Fix Not Available' + case 'ageOfDiscovery': + return ( + (getSelectPickerOptionByValue(DISCOVERY_AGE_FILTER_OPTIONS, filterValue)?.label as string) || + filterValue + ) + case 'severity': + return SEVERITY_LABEL_MAP[filterValue as Severity] + case 'application': + return ( + (getSelectPickerOptionByValue(applicationOptions || [], filterValue)?.label as string) || + filterValue + ) + case 'environment': + return ( + (getSelectPickerOptionByValue(environmentOptions || [], filterValue)?.label as string) || + filterValue + ) + case 'cluster': + return (getSelectPickerOptionByValue(clusterOptions || [], filterValue)?.label as string) || filterValue + default: + return filterValue + } + } + + return ( + <> +
+
+
+ + +
+ + id="cve-list-filters" + filterSelectPickerPropsMap={groupedFilterOptionsMap} + options={CVE_LIST_GROUP_FILTER_OPTIONS} + isFilterApplied={ + severity.length > 0 || + cluster.length > 0 || + fixAvailability.length > 0 || + application.length > 0 || + environment.length > 0 || + ageOfDiscovery.length > 0 + } + /> +
+
+ > + filterConfig={{ + severity, + cluster, + fixAvailability, + application, + environment, + ageOfDiscovery, + }} + onRemoveFilter={updateSearchParams} + clearFilters={clearFilters} + className="px-20" + getFormattedLabel={getFilterChipLabel} + getFormattedValue={getFormattedFilterValue} + /> + {children} + + ) +} + +const CVEList = () => { + const colums: TableProps['columns'] = [ + { + label: 'CVE ID', + field: 'cveName', + isSortable: true, + size: { fixed: 180 }, + CellComponent: CVENameCellComponent, + }, + { + label: 'Severity', + field: 'severity', + size: { fixed: 100 }, + CellComponent: SeverityCellComponent, + }, + { + label: 'Application', + field: 'appName', + size: { fixed: 150 }, + }, + { + label: 'Environment', + field: 'envName', + size: { fixed: 150 }, + }, + { + label: 'Discovered 1st Time', + field: 'discoveredAt', + size: { fixed: 150 }, + }, + { + label: 'Package', + field: 'package', + size: { fixed: 180 }, + }, + { + label: 'Current Version', + field: 'currentVersion', + size: { fixed: 150 }, + isSortable: true, + }, + { + label: 'Fix In Version', + field: 'fixedVersion', + size: { fixed: 150 }, + isSortable: true, + }, + ] + + return ( + + id="table__cve-listing" + columns={colums} + getRows={getCVEList} + emptyStateConfig={{ + noRowsConfig: { + title: 'No CVEs Found', + }, + }} + paginationVariant={PaginationEnum.PAGINATED} + filtersVariant={FiltersTypeEnum.URL} + filter={() => true} + additionalFilterProps={{ + initialSortKey: 'cveName', + parseSearchParams, + }} + ViewWrapper={CVETableWrapper} + /> + ) +} + +export default CVEList diff --git a/src/components/security/Vulnerabilities/CVEList/constants.ts b/src/components/security/Vulnerabilities/CVEList/constants.ts new file mode 100644 index 0000000000..b21fe12527 --- /dev/null +++ b/src/components/security/Vulnerabilities/CVEList/constants.ts @@ -0,0 +1,16 @@ +import { GroupedFilterSelectPickerProps } from '@devtron-labs/devtron-fe-common-lib' + +import { CVEListFilters } from './types' + +export const CVE_LIST_GROUP_FILTER_OPTIONS: GroupedFilterSelectPickerProps['options'] = [ + { + items: [ + { id: 'application', label: 'Application' }, + { id: 'environment', label: 'Environment' }, + { id: 'severity', label: 'Severity' }, + { id: 'cluster', label: 'Cluster' }, + { id: 'fixAvailability', label: 'Fix Availability' }, + { id: 'ageOfDiscovery', label: 'Age of Discovery' }, + ], + }, +] diff --git a/src/components/security/Vulnerabilities/CVEList/index.ts b/src/components/security/Vulnerabilities/CVEList/index.ts new file mode 100644 index 0000000000..3a463d25b4 --- /dev/null +++ b/src/components/security/Vulnerabilities/CVEList/index.ts @@ -0,0 +1 @@ +export { default as CVEList } from './CVEList' diff --git a/src/components/security/Vulnerabilities/CVEList/service.ts b/src/components/security/Vulnerabilities/CVEList/service.ts new file mode 100644 index 0000000000..c8fdb7020b --- /dev/null +++ b/src/components/security/Vulnerabilities/CVEList/service.ts @@ -0,0 +1,68 @@ +import dayjs from 'dayjs' + +import { DATE_TIME_FORMATS, post, stringComparatorBySortOrder, TableProps } from '@devtron-labs/devtron-fe-common-lib' + +import { getVulnerabilityFilterData } from '@Components/security/security.service' +import { SeverityFilterValues } from '@Components/security/SecurityScansTab/types' +import { Routes } from '@Config/constants' +import { getAppListMin } from '@Services/service' + +import { DISCOVERY_AGE_FILTER_OPTIONS, FIX_AVAILABLE_FILTER_OPTIONS } from '../constants' +import { CVEDetails, CVEListFilterData, CVEListFilters, VulnerabilityDTO } from './types' + +export const getCVEListFilters = async (): Promise => { + const [appListResponse, filtersResponse] = await Promise.all([getAppListMin(), getVulnerabilityFilterData()]) + + return { + application: (appListResponse?.result || []) + .map((app) => ({ + label: app.name, + value: `${app.id}`, + })) + .sort((a, b) => stringComparatorBySortOrder(a.label, b.label)), + ...filtersResponse, + ageOfDiscovery: DISCOVERY_AGE_FILTER_OPTIONS, + fixAvailability: FIX_AVAILABLE_FILTER_OPTIONS, + } +} + +export const getCVEList: TableProps['getRows'] = async ( + { + offset, + pageSize, + searchKey, + sortBy, + sortOrder, + ...filters + }: Parameters[0] & Record, + signal: AbortSignal, +) => { + const { application, environment, ageOfDiscovery, fixAvailability, severity, cluster } = filters + const response = await post( + Routes.SECURITY_SCAN_VULNERABILITIES, + { + offset, + size: pageSize, + sortBy, + sortOrder, + cveName: searchKey, + appIds: application.map(Number), + envIds: environment.map(Number), + severity: severity.map((severityFilterValue) => SeverityFilterValues[severityFilterValue]), + clusterIds: cluster.map(Number), + fixAvailability, + ageOfDiscovery, + }, + { signal }, + ) + return { + rows: (response.result?.list ?? []).map((cve) => ({ + data: { + ...cve, + discoveredAt: dayjs(cve.discoveredAt).format(DATE_TIME_FORMATS.WEEKDAY_WITH_DATE_MONTH_AND_YEAR), + }, + id: Object.values(cve).join('-'), + })), + totalRows: response.result?.total ?? 0, + } +} diff --git a/src/components/security/Vulnerabilities/CVEList/types.ts b/src/components/security/Vulnerabilities/CVEList/types.ts new file mode 100644 index 0000000000..d375e39ff5 --- /dev/null +++ b/src/components/security/Vulnerabilities/CVEList/types.ts @@ -0,0 +1,29 @@ +import { SelectPickerOptionType, SeveritiesDTO } from '@devtron-labs/devtron-fe-common-lib' + +export interface CVEDetails { + cveName: string + severity: SeveritiesDTO + appName: string + appId: number + envName: string + envId: number + discoveredAt: string + package: string + currentVersion: string + fixedVersion: string +} + +export interface VulnerabilityDTO { + total: number + list: CVEDetails[] +} + +export type CVEListFilters = + | 'application' + | 'environment' + | 'severity' + | 'cluster' + | 'fixAvailability' + | 'ageOfDiscovery' + +export type CVEListFilterData = Record diff --git a/src/components/security/Vulnerabilities/CVEList/utils.ts b/src/components/security/Vulnerabilities/CVEList/utils.ts new file mode 100644 index 0000000000..dfdee66ad7 --- /dev/null +++ b/src/components/security/Vulnerabilities/CVEList/utils.ts @@ -0,0 +1,21 @@ +import { CVEListFilters } from './types' + +export const parseSearchParams = (searchParams: URLSearchParams): Record => ({ + application: searchParams.getAll('application'), + environment: searchParams.getAll('environment'), + severity: searchParams.getAll('severity'), + cluster: searchParams.getAll('cluster'), + fixAvailability: searchParams.getAll('fixAvailability'), + ageOfDiscovery: searchParams.getAll('ageOfDiscovery'), +}) + +export const getFilterChipLabel = (filterKey: CVEListFilters) => { + switch (filterKey) { + case 'fixAvailability': + return 'Fix Availability' + case 'ageOfDiscovery': + return 'Age of Discovery' + default: + return filterKey + } +} diff --git a/src/components/security/Vulnerabilities/VulnerabilitiesRouter.tsx b/src/components/security/Vulnerabilities/VulnerabilitiesRouter.tsx new file mode 100644 index 0000000000..3dafbbc121 --- /dev/null +++ b/src/components/security/Vulnerabilities/VulnerabilitiesRouter.tsx @@ -0,0 +1,53 @@ +import { Redirect, Route, Switch } from 'react-router-dom' + +import { + BreadCrumb, + BreadcrumbText, + getSecurityCenterBreadcrumb, + InfoBlock, + PageHeader, + URLS, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + +import { SecurityScansTab } from '../SecurityScansTab' +import { CVEList } from './CVEList' + +const VulnerabilitiesRouter = () => { + const { breadcrumbs } = useBreadcrumb({ + alias: { + ...getSecurityCenterBreadcrumb(), + vulnerabilities: { + component: , + linked: false, + }, + deployments: null, + cves: null, + }, + }) + + const renderBreadcrumbs = () => + + return ( +
+ + + + + + + + + + + +
+ ) +} + +export default VulnerabilitiesRouter diff --git a/src/components/security/Vulnerabilities/VulnerabilityViewTypeSelect.tsx b/src/components/security/Vulnerabilities/VulnerabilityViewTypeSelect.tsx new file mode 100644 index 0000000000..1f1a18bdb7 --- /dev/null +++ b/src/components/security/Vulnerabilities/VulnerabilityViewTypeSelect.tsx @@ -0,0 +1,47 @@ +import { useHistory, useRouteMatch } from 'react-router-dom' + +import { + ComponentSizeType, + getSelectPickerOptionByValue, + Icon, + SelectPicker, + SelectPickerOptionType, + URLS, +} from '@devtron-labs/devtron-fe-common-lib' + +import { VULNERABILITY_VIEW_TYPE_SELECT_OPTIONS } from './constants' +import { VulnerabilityViewTypes } from './types' + +const VulnerabilityViewTypeSelect = () => { + const { path } = useRouteMatch() + const { push } = useHistory() + + const viewType = + path === URLS.SECURITY_CENTER_VULNERABILITY_CVES + ? VulnerabilityViewTypes.VULNERABILITIES + : VulnerabilityViewTypes.DEPLOYMENTS + + const onChange = (selectedOption: SelectPickerOptionType) => { + push( + selectedOption.value === VulnerabilityViewTypes.VULNERABILITIES + ? URLS.SECURITY_CENTER_VULNERABILITY_CVES + : URLS.SECURITY_CENTER_VULNERABILITY_DEPLOYMENTS, + ) + } + + return ( +
+ } + /> +
+ ) +} + +export default VulnerabilityViewTypeSelect diff --git a/src/components/security/Vulnerabilities/constants.ts b/src/components/security/Vulnerabilities/constants.ts new file mode 100644 index 0000000000..589a859ad4 --- /dev/null +++ b/src/components/security/Vulnerabilities/constants.ts @@ -0,0 +1,28 @@ +import { SelectPickerOptionType, Severity } from '@devtron-labs/devtron-fe-common-lib' + +import { FixAvailabilityOptions, VulnerabilityDiscoveryAgeOptions, VulnerabilityViewTypes } from './types' + +export const VULNERABILITY_VIEW_TYPE_SELECT_OPTIONS: SelectPickerOptionType[] = [ + { label: 'Deployments', value: VulnerabilityViewTypes.DEPLOYMENTS }, + { label: 'Vulnerabilities', value: VulnerabilityViewTypes.VULNERABILITIES }, +] + +export const FIX_AVAILABLE_FILTER_OPTIONS: SelectPickerOptionType[] = [ + { label: 'Fix available', value: FixAvailabilityOptions.FIX_AVAILABLE }, + { label: 'Fix not available', value: FixAvailabilityOptions.FIX_NOT_AVAILABLE }, +] + +export const DISCOVERY_AGE_FILTER_OPTIONS: SelectPickerOptionType[] = [ + { label: '< 30days', value: VulnerabilityDiscoveryAgeOptions.LESS_THAN_30_DAYS }, + { label: '30-60 days', value: VulnerabilityDiscoveryAgeOptions.BETWEEN_30_AND_60_DAYS }, + { label: '60-90 days', value: VulnerabilityDiscoveryAgeOptions.BETWEEN_60_AND_90_DAYS }, + { label: '> 90 days', value: VulnerabilityDiscoveryAgeOptions.GREATER_THAN_90_DAYS }, +] + +export const SEVERITY_FILTER_OPTIONS: SelectPickerOptionType[] = [ + { label: 'Critical', value: Severity.CRITICAL }, + { label: 'High', value: Severity.HIGH }, + { label: 'Medium', value: Severity.MEDIUM }, + { label: 'Low', value: Severity.LOW }, + { label: 'Unknown', value: Severity.UNKNOWN }, +] diff --git a/src/components/security/Vulnerabilities/index.ts b/src/components/security/Vulnerabilities/index.ts new file mode 100644 index 0000000000..787014b3ed --- /dev/null +++ b/src/components/security/Vulnerabilities/index.ts @@ -0,0 +1,2 @@ +export { default as VulnerabilitiesRouter } from './VulnerabilitiesRouter' +export { default as VulnerabilityViewTypeSelect } from './VulnerabilityViewTypeSelect' diff --git a/src/components/security/Vulnerabilities/types.ts b/src/components/security/Vulnerabilities/types.ts new file mode 100644 index 0000000000..8a9fd020a8 --- /dev/null +++ b/src/components/security/Vulnerabilities/types.ts @@ -0,0 +1,16 @@ +export enum VulnerabilityViewTypes { + DEPLOYMENTS = 'DEPLOYMENTS', + VULNERABILITIES = 'VULNERABILITIES', +} + +export enum VulnerabilityDiscoveryAgeOptions { + LESS_THAN_30_DAYS = 'lt_30d', + BETWEEN_30_AND_60_DAYS = '30_60d', + BETWEEN_60_AND_90_DAYS = '60_90d', + GREATER_THAN_90_DAYS = 'gt_90d', +} + +export enum FixAvailabilityOptions { + FIX_AVAILABLE = 'fixAvailable', + FIX_NOT_AVAILABLE = 'fixNotAvailable', +} diff --git a/src/components/security/constants.ts b/src/components/security/constants.ts deleted file mode 100644 index 5ecb4892dc..0000000000 --- a/src/components/security/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { URLS } from '@devtron-labs/devtron-fe-common-lib' - -export const SECURITY_BREADCRUMB_CONFIG = [ - { - key: 'scans', - route: URLS.SECURITY_CENTER_SCANS, - heading: 'Security Scans', - }, - { - key: 'policies', - route: URLS.SECURITY_CENTER_POLICIES, - heading: 'Security Policies', - }, -] diff --git a/src/components/security/security.scss b/src/components/security/security.scss index 1d1ed8575b..c01db3f54e 100644 --- a/src/components/security/security.scss +++ b/src/components/security/security.scss @@ -23,12 +23,8 @@ } .table__row-grid { - grid-template-columns: 24px 250px 200px 400px 1fr; - } - - .table__row { - border-bottom: solid 1px var(--N100); - cursor: pointer; + grid-template-columns: 24px 200px 200px 1fr 200px 200px; + column-gap: 16px; } .table__title:first-child, @@ -330,19 +326,6 @@ } } - .search-type__select-picker__control { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - - .security-scan-search { - .search-bar { - border-top-left-radius: 0 !important; - border-bottom-left-radius: 0 !important; - border-left: none; - } - } - .security-tab__cell-policy--whitelist, .security-tab__cell-policy--whitelisted { color: var(--G500); diff --git a/src/components/security/security.service.ts b/src/components/security/security.service.ts index dee03cfeb0..de34104129 100644 --- a/src/components/security/security.service.ts +++ b/src/components/security/security.service.ts @@ -20,66 +20,62 @@ import { getEnvironmentListMinPublic, post, ResponseType, + SelectPickerOptionType, sortCallback, } from '@devtron-labs/devtron-fe-common-lib' import { Routes } from '../../config' -import { SecurityScanListResponseType, ResourceLevel, GetVulnerabilityPolicyResponse, CVEControlList, CVEControlListPayload } from './security.types' -import { ScanListPayloadType } from './SecurityScansTab/types' +import { + SecurityScanListResponseType, + ResourceLevel, + GetVulnerabilityPolicyResponse, + CVEControlList, + CVEControlListPayload, +} from './security.types' +import { ScanListPayloadType, SecurityScansTabMultiFilterKeys } from './SecurityScansTab/types' +import { SEVERITY_FILTER_OPTIONS } from './Vulnerabilities/constants' export function getClusterListMinNoAuth() { const URL = `${Routes.CLUSTER}/autocomplete?auth=false` return get(URL) } -export function getVulnerabilityFilterData() { - return Promise.all([getEnvironmentListMinPublic(), getClusterListMin()]).then(([envResponse, clusterResponse]) => { - let environments = envResponse.result - ? envResponse.result.map((env) => { - return { - label: env.environment_name, - value: `${env.id}`, - } - }) - : [] - let clusters = clusterResponse - ? clusterResponse.result.map((cluster) => { - return { - label: cluster.cluster_name, - value: `${cluster.id}`, - } - }) - : [] - environments = environments.sort((a, b) => { - return sortCallback('label', a, b) - }) - clusters = clusters.sort((a, b) => { - return sortCallback('label', a, b) - }) - return { - filters: { - severity: [ - { label: 'Critical', value: 'critical' }, - { label: 'High', value: 'high' }, - { label: 'Medium', value: 'medium' }, - { label: 'Low', value: 'low' }, - { label: 'Unknown', value: 'unknown' }, - ], - clusters, - environments, - }, - } - }) +export const getVulnerabilityFilterData = async (): Promise> => { + const envResponse = await getEnvironmentListMinPublic() + const clusterResponse = await getClusterListMin() + + const environment = (envResponse?.result ?? []) + .map((env) => ({ + label: env.environment_name, + value: `${env.id}`, + })) + .sort((a, b) => sortCallback('label', a, b)) + + const cluster = (clusterResponse?.result ?? []) + .map((cluster) => ({ + label: cluster.cluster_name, + value: `${cluster.id}`, + })) + .sort((a, b) => sortCallback('label', a, b)) + + return { + severity: SEVERITY_FILTER_OPTIONS, + cluster: cluster, + environment: environment, + } } -export function getSecurityScanList(payload: ScanListPayloadType, abortSignal: AbortSignal): Promise { +export function getSecurityScanList( + payload: ScanListPayloadType, + abortSignal: AbortSignal, +): Promise { const URL = 'security/scan/list' - return post(URL, payload, {signal: abortSignal}).then((response) => { - const securityScans = response.result.scanList || [] + return post(URL, payload, { signal: abortSignal }).then((response) => { + const securityScans = response.result?.scanList || [] return { result: { - offset: response.result.offset, - totalCount: response.result.total, - pageSize: response.result.size || 20, + offset: response.result?.offset, + totalCount: response.result?.total, + pageSize: response.result?.size || 20, securityScans: securityScans.map((scan) => { return { appId: scan.appId, @@ -95,6 +91,7 @@ export function getSecurityScanList(payload: ScanListPayloadType, abortSignal: A unknown: scan.severityCount.unknown, }, lastExecution: scan.lastChecked || '-', + fixableVulnerabilities: scan.fixableVulnerabilities, } }), }, @@ -158,81 +155,3 @@ export function getCVEControlList(payload: CVEControlListPayload): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - code: 200, - status: 'ok', - result: { - clusters: [ - { - id: 1, - name: 'default_cluster', - policy: 'INHERIT', - isCollapsed: false, - environments: [ - { - id: 1, - name: 'prod', - policy: 'INHERIT', - isCollapsed: false, - applications: [ - { - id: 1, - name: 'dashoard', - policy: 'INHERIT', - }, - ], - }, - ], - }, - { - id: 2, - name: 'stage', - policy: 'INHERIT', - isCollapsed: true, - environments: [ - { - id: 1, - name: 'devtron-prod', - policy: 'INHERIT', - isCollapsed: false, - applications: [ - { - id: 3, - name: 'blobs', - policy: 'INHERIT', - }, - ], - }, - ], - }, - { - id: 3, - name: 'prod', - policy: 'INHERIT', - isCollapsed: true, - environments: [ - { - id: 1, - name: 'prod', - policy: 'INHERIT', - isCollapsed: false, - applications: [ - { - id: 1, - name: 'orch', - policy: 'INHERIT', - }, - ], - }, - ], - }, - ], - }, - }) - }, 500) - }) -} diff --git a/src/components/security/security.types.ts b/src/components/security/security.types.ts index eb9538fd26..063c0fc4da 100644 --- a/src/components/security/security.types.ts +++ b/src/components/security/security.types.ts @@ -43,6 +43,7 @@ export interface SecurityScanType { type: string environment: string severityCount: SeverityCount + fixableVulnerabilities: number } export interface SecurityScanListResponseType { diff --git a/src/components/security/security.util.tsx b/src/components/security/security.util.tsx index 2c41ef390e..08458459eb 100644 --- a/src/components/security/security.util.tsx +++ b/src/components/security/security.util.tsx @@ -14,48 +14,10 @@ * limitations under the License. */ -import { - BreadcrumbText, - useBreadcrumb, - getSecurityCenterBreadcrumb, -} from '@devtron-labs/devtron-fe-common-lib' - -import { SECURITY_BREADCRUMB_CONFIG } from './constants' import { VulnerabilityExposureFilterKeys, VulnerabilityExposureSearchParams } from './security.types' -import { matchPath } from 'react-router-dom' export const parseVulnerabilityExposureSearchParams = (searchParams: URLSearchParams) => ({ [VulnerabilityExposureFilterKeys.cluster]: searchParams.getAll(VulnerabilityExposureFilterKeys.cluster), [VulnerabilityExposureFilterKeys.environment]: searchParams.getAll(VulnerabilityExposureFilterKeys.environment), [VulnerabilityExposureSearchParams.cveName]: searchParams.get(VulnerabilityExposureSearchParams.cveName) ?? '', }) - -export const getTippyContent = () => ( -
- Devtron provides DevSecOps capabilities across your software development life cycle. -

- One of the key components of DevSecOps is the detection of security risks. Currently, Devtron supports the - following types of scanning: -

-
    -
  • Image Scan
  • -
  • Code Scan
  • -
  • Kubernetes Manifest Scan
  • -
-
-) - -export const getSecurityBreadcrumbAlias = (url: string): Parameters[0] => { - const cleanUrl = url.split('?')[0].split('#')[0] - - const alias = getSecurityCenterBreadcrumb() - SECURITY_BREADCRUMB_CONFIG.forEach(({ key, route, heading }) => { - const isActive = !!matchPath(cleanUrl, { path: route, exact: false }) - alias[key] = { - component: , - linked: false - } - }) - - return { alias } -} diff --git a/src/components/workflowEditor/Workflow.tsx b/src/components/workflowEditor/Workflow.tsx index d179198378..a50d73baad 100644 --- a/src/components/workflowEditor/Workflow.tsx +++ b/src/components/workflowEditor/Workflow.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +/* eslint-disable react/no-danger */ +import DOMPurify from 'dompurify' import { Component } from 'react' import { Link, generatePath } from 'react-router-dom' import Tippy from '@tippyjs/react' @@ -43,6 +44,7 @@ import { ConditionalWrap, ChangeCIPayloadType, URLS as CommonURLS, + highlightSearchText, } from '@devtron-labs/devtron-fe-common-lib' import { ReactComponent as ICInput } from '../../assets/icons/ic-input.svg' import { ReactComponent as ICMoreOption } from '../../assets/icons/ic-more-option.svg' @@ -778,7 +780,19 @@ export class Workflow extends Component { } data-testid="workflow-header" > - {this.props.name} + {!this.props.isOffendingPipelineView && !configDiffView && (
diff --git a/src/components/workflowEditor/types.ts b/src/components/workflowEditor/types.ts index 1a64b2ae4c..c373c32303 100644 --- a/src/components/workflowEditor/types.ts +++ b/src/components/workflowEditor/types.ts @@ -394,6 +394,7 @@ export interface WorkflowProps workflowPositionState?: WorkflowPositionState handleDisplayLoader?: () => void isOffendingPipelineView?: boolean + searchText?: string } export interface WorkflowState { diff --git a/src/components/workflowEditor/workflowEditor.tsx b/src/components/workflowEditor/workflowEditor.tsx index 43b8e12fd4..dfe3c3b3ed 100644 --- a/src/components/workflowEditor/workflowEditor.tsx +++ b/src/components/workflowEditor/workflowEditor.tsx @@ -44,6 +44,7 @@ import { ButtonStyleType, handleAnalyticsEvent, GenericEmptyState, + SearchBar, } from '@devtron-labs/devtron-fe-common-lib' import { PipelineContext, WorkflowEditProps, WorkflowEditState } from './types' import { URLS, AppConfigStatus, ViewType } from '../../config' @@ -770,12 +771,20 @@ class WorkflowEdit extends Component { ) } + getSearchKey = (): string => { + return new URLSearchParams(this.props.location.search).get('searchKey') || '' + } + + getWorkflowNameToMatch = (): string => { + return new URLSearchParams(this.props.location.search).get('workflowName') || '' + } + renderNewBuildPipelineButton() { return (