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 (
}
size={ComponentSizeType.medium}
@@ -788,7 +797,9 @@ class WorkflowEdit extends Component {
}
openCreateModal = () => {
- this.props.history.push(`${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${this.props.match.params.appId}/edit/workflow/empty-workflow`)
+ this.props.history.push(
+ `${URLS.AUTOMATION_AND_ENABLEMENT_JOB}/${this.props.match.params.appId}/edit/workflow/empty-workflow`,
+ )
}
renderNewJobPipelineButton = () => {
@@ -803,6 +814,15 @@ class WorkflowEdit extends Component {
)
}
+ handleUpdateSearch = (updatedSearchKey: string) => {
+ const updatedParams = new URLSearchParams({
+ searchKey: updatedSearchKey,
+ })
+ this.props.history.push({
+ search: updatedParams.toString(),
+ })
+ }
+
renderWorkflowControlButton = (): JSX.Element => {
if (this.props.isJobView) {
return this.renderNewJobPipelineButton()
@@ -828,7 +848,19 @@ class WorkflowEdit extends Component {
)
}
- return this.renderNewBuildPipelineButton()
+ return (
+
+
+ {this.renderNewBuildPipelineButton()}
+
+ )
}
renderEmptyState() {
@@ -874,6 +906,12 @@ class WorkflowEdit extends Component {
this.setState({ workflows: _wf })
}
+ filterWorkflow = (wf) => {
+ // If wfNameToMatch match full name, else match substring
+ const wfNameToMatch = this.getWorkflowNameToMatch()
+ return wfNameToMatch ? wf.name === wfNameToMatch : wf.name.includes(this.getSearchKey())
+ }
+
renderWorkflows() {
const handleModalClose = () => {
this.props.history.push(this.props.match.url)
@@ -881,7 +919,7 @@ class WorkflowEdit extends Component {
return (
<>
- {this.state.workflows.map((wf) => {
+ {this.state.workflows.filter(this.filterWorkflow).map((wf) => {
return (
{
workflowPositionState={this.state.workflowPositionState}
handleDisplayLoader={this.handleDisplayLoader}
isTemplateView={this.props.isTemplateView}
+ searchText={this.getWorkflowNameToMatch() || this.getSearchKey() || ''}
/>
)
})}
diff --git a/src/config/constants.ts b/src/config/constants.ts
index bde137a779..ed7f18f110 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -258,6 +258,7 @@ export const Routes = {
USER: 'user',
ENV_CONFIG: 'config/autocomplete',
SECURITY_SCAN_CVE_EXPOSURE: 'security/scan/cve/exposure',
+ SECURITY_SCAN_VULNERABILITIES: 'security/scan/vulnerabilities',
CONFIG_MANIFEST: 'config/manifest',
USER_RESOURCE_OPTIONS: 'user/resource/options',
HEALTH: 'health',