Skip to content

Commit 22fcf1b

Browse files
committed
Merge branch 'refactor/vulnerability-exposure' of https://github.com/devtron-labs/dashboard into fix/force-delete-helm
2 parents 41b5885 + 05a650d commit 22fcf1b

12 files changed

+444
-665
lines changed

.eslintignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,6 @@ src/components/security/SecurityPolicyEdit.tsx
390390
src/components/security/SecurityPolicyEnvironment.tsx
391391
src/components/security/SecurityPolicyGlobal.tsx
392392
src/components/security/UpdateSeverityModal.tsx
393-
src/components/security/VulnerabilityExposure.tsx
394393
src/components/security/security.service.ts
395394
src/components/security/security.util.tsx
396395
src/components/terminal/TerminalWrapper.tsx
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
DEFAULT_BASE_PAGE_SIZE,
3+
GenericFilterEmptyState,
4+
noop,
5+
Pagination,
6+
SortableTableHeaderCell,
7+
SortingOrder,
8+
} from '@devtron-labs/devtron-fe-common-lib'
9+
import { ExposureListProps } from '../security.types'
10+
11+
const ExposureList = ({
12+
appListResponse,
13+
areFiltersApplied,
14+
clearExposureListFilters,
15+
offset,
16+
pageSize,
17+
changePage,
18+
changePageSize,
19+
}: ExposureListProps) => {
20+
const appListLength = appListResponse.result.scanList.length
21+
if (!appListLength && areFiltersApplied) {
22+
return (
23+
<div className="dc__position-rel" style={{ height: 'calc(100vh - 200px)' }}>
24+
<GenericFilterEmptyState handleClearFilters={clearExposureListFilters} />
25+
</div>
26+
)
27+
}
28+
return appListLength ? (
29+
<>
30+
<div className="w-100">
31+
<div className="fs-12 fw-6 lh-20 cn-7 pl-20 pr-20 dc__border-bottom px-20 vulnerability-exp-table-row h-36">
32+
<SortableTableHeaderCell
33+
title="NAME"
34+
isSortable={false}
35+
isSorted
36+
triggerSorting={noop}
37+
disabled={false}
38+
sortOrder={SortingOrder.ASC}
39+
/>
40+
<SortableTableHeaderCell
41+
title="ENVIRONMENT"
42+
isSortable={false}
43+
isSorted
44+
triggerSorting={noop}
45+
disabled
46+
sortOrder={SortingOrder.ASC}
47+
/>
48+
<SortableTableHeaderCell
49+
title="POLICY"
50+
isSortable={false}
51+
isSorted
52+
triggerSorting={noop}
53+
disabled
54+
sortOrder={SortingOrder.ASC}
55+
/>
56+
</div>
57+
{appListResponse.result.scanList.map((cve) => (
58+
<div
59+
key={`${cve.appName}-${cve.envName}`}
60+
className="dc__border-bottom-n1 dc__hover-n50 vulnerability-exp-table-row px-20 h-44 dc__align-items-center fs-13 lh-20 cn-9"
61+
>
62+
<span>{cve.appName}</span>
63+
<span>{cve.envName}</span>
64+
<span>
65+
<span className={`security-tab__cell-policy--${cve.policy}`}>
66+
{cve.policy.toUpperCase()}
67+
</span>
68+
</span>
69+
</div>
70+
))}
71+
</div>
72+
{appListLength > DEFAULT_BASE_PAGE_SIZE && (
73+
<Pagination
74+
rootClassName="flex dc__content-space px-20"
75+
size={appListLength}
76+
pageSize={pageSize}
77+
offset={offset}
78+
changePage={changePage}
79+
changePageSize={changePageSize}
80+
/>
81+
)}
82+
</>
83+
) : null
84+
}
85+
86+
export default ExposureList
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
/*
2+
* Copyright (c) 2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { useMemo, useState } from 'react'
18+
import {
19+
Progressing,
20+
SearchBar,
21+
FilterSelectPicker,
22+
useAsync,
23+
useUrlFilters,
24+
SelectPickerOptionType,
25+
ErrorScreenManager,
26+
FilterChips,
27+
FeatureTitleWithInfo,
28+
getCVEUrlFromCVEName,
29+
Button,
30+
ComponentSizeType,
31+
ButtonComponentType,
32+
} from '@devtron-labs/devtron-fe-common-lib'
33+
import { getCVEControlList, getVulnerabilityFilterData } from '../security.service'
34+
import {
35+
CVEControlListPayload,
36+
ExposureListContainerProps,
37+
VulnerabilityExposureFilterKeys,
38+
VulnerabilityExposureUrlFiltersType,
39+
} from '../security.types'
40+
import { parseVulnerabilityExposureSearchParams } from '../security.util'
41+
import ExposureList from './ExposureList'
42+
43+
const ExposureListContainer = ({ urlFilters }: ExposureListContainerProps) => {
44+
const [filtersLoading, filtersResponse, filtersError, reloadFilters] = useAsync(getVulnerabilityFilterData)
45+
46+
const {
47+
offset,
48+
pageSize,
49+
searchKey: appName,
50+
handleSearch,
51+
changePage,
52+
changePageSize,
53+
updateSearchParams,
54+
cluster,
55+
environment,
56+
cveName,
57+
} = urlFilters
58+
59+
const payload: CVEControlListPayload = useMemo(
60+
() => ({
61+
offset,
62+
size: pageSize,
63+
cveName: cveName.toUpperCase(),
64+
appName,
65+
clusterIds: cluster.map((clusterId) => +clusterId),
66+
envIds: environment.map((clusterId) => +clusterId),
67+
}),
68+
[offset, pageSize, appName, JSON.stringify(cluster), JSON.stringify(environment), cveName],
69+
)
70+
71+
const [appListLoading, appListResponse, appListError, reloadAppList] = useAsync(
72+
() => getCVEControlList(payload),
73+
[JSON.stringify(payload)],
74+
!!cveName,
75+
)
76+
77+
if (appListLoading) {
78+
return (
79+
<div style={{ height: 'calc(100vh - 280px)' }}>
80+
<Progressing pageLoader />
81+
</div>
82+
)
83+
}
84+
85+
if (appListError) {
86+
return (
87+
<div style={{ height: 'calc(100vh - 280px)' }}>
88+
<ErrorScreenManager code={appListError.code} reload={reloadAppList} />
89+
</div>
90+
)
91+
}
92+
93+
const clusterListOptions: SelectPickerOptionType[] = filtersResponse?.filters.clusters || []
94+
const environmentListOptions: SelectPickerOptionType[] = filtersResponse?.filters.environments || []
95+
96+
const getFilterLabelFromValue = (filterKey: string, filterValue: string) => {
97+
if (filterKey === VulnerabilityExposureFilterKeys.cluster) {
98+
return (
99+
filtersResponse?.filters.clusters.find((clusterOption) => clusterOption.value === filterValue)?.label ??
100+
filterValue
101+
)
102+
}
103+
return (
104+
filtersResponse?.filters.environments.find((envOption) => envOption.value === filterValue)?.label ??
105+
filterValue
106+
)
107+
}
108+
109+
const selectedClusters: SelectPickerOptionType[] = cluster.map((clusterId) => ({
110+
label: getFilterLabelFromValue(VulnerabilityExposureFilterKeys.cluster, clusterId),
111+
value: clusterId,
112+
}))
113+
114+
const selectedEnvironments: SelectPickerOptionType[] = environment.map((envId) => ({
115+
label: getFilterLabelFromValue(VulnerabilityExposureFilterKeys.cluster, envId),
116+
value: envId,
117+
}))
118+
119+
const handleUpdateClusterFilter = (filterValue: SelectPickerOptionType[]) => {
120+
updateSearchParams({ cluster: filterValue.map((clusterOption) => String(clusterOption.value)) })
121+
}
122+
123+
const handleUpdateEnvironmentFilter = (filterValue: SelectPickerOptionType[]) => {
124+
updateSearchParams({ environment: filterValue.map((env) => String(env.value)) })
125+
}
126+
127+
const clearExposureListFilters = () => {
128+
updateSearchParams({ cluster: [], environment: [] })
129+
handleSearch('')
130+
}
131+
132+
const renderFilters = () => (
133+
<div className="security-scan__filters px-20 py-12">
134+
<SearchBar
135+
initialSearchText={appName}
136+
containerClassName="w-250"
137+
handleEnter={handleSearch}
138+
inputProps={{
139+
placeholder: 'Search applications',
140+
autoFocus: false,
141+
}}
142+
dataTestId="security-vulnerability-search-application"
143+
/>
144+
<div className="flexbox dc__gap-8">
145+
<FilterSelectPicker
146+
inputId="vulnerability-exposure-cluster-filter"
147+
placeholder="Clusters"
148+
options={clusterListOptions}
149+
appliedFilterOptions={selectedClusters}
150+
isDisabled={filtersLoading}
151+
isLoading={filtersLoading}
152+
optionListError={filtersError}
153+
reloadOptionList={reloadFilters}
154+
handleApplyFilter={handleUpdateClusterFilter}
155+
/>
156+
<FilterSelectPicker
157+
inputId="vulnerability-exposure-environment-filter"
158+
placeholder="Environments"
159+
options={environmentListOptions}
160+
appliedFilterOptions={selectedEnvironments}
161+
isDisabled={filtersLoading}
162+
isLoading={filtersLoading}
163+
optionListError={filtersError}
164+
reloadOptionList={reloadFilters}
165+
handleApplyFilter={handleUpdateEnvironmentFilter}
166+
shouldMenuAlignRight
167+
/>
168+
</div>
169+
</div>
170+
)
171+
172+
const renderAppliedFilters = () => (
173+
<FilterChips<Omit<VulnerabilityExposureUrlFiltersType, 'cveName'>>
174+
filterConfig={{
175+
environment,
176+
cluster,
177+
}}
178+
className="px-20"
179+
clearFilters={clearExposureListFilters}
180+
onRemoveFilter={updateSearchParams}
181+
getFormattedValue={getFilterLabelFromValue}
182+
/>
183+
)
184+
185+
const appListLength: number = appListResponse.result.totalCount
186+
const areFiltersApplied: boolean = !!appName || !!cluster.length || !!environment.length
187+
188+
return (
189+
<>
190+
{!!appListLength && renderFilters()}
191+
{!!appListLength && renderAppliedFilters()}
192+
<ExposureList
193+
appListResponse={appListResponse}
194+
areFiltersApplied={areFiltersApplied}
195+
clearExposureListFilters={clearExposureListFilters}
196+
offset={offset}
197+
pageSize={pageSize}
198+
changePage={changePage}
199+
changePageSize={changePageSize}
200+
/>
201+
</>
202+
)
203+
}
204+
205+
const VulnerabilityExposure = () => {
206+
const urlFilters = useUrlFilters<never, VulnerabilityExposureUrlFiltersType>({
207+
parseSearchParams: parseVulnerabilityExposureSearchParams,
208+
})
209+
210+
const { updateSearchParams, cveName } = urlFilters
211+
212+
const [cveNameSearchKey, setCveNameSearchKey] = useState(cveName)
213+
214+
const handleSearchCVE = (cveNameSearchString: string) => {
215+
if (cveNameSearchString) {
216+
updateSearchParams({ cveName: cveNameSearchString })
217+
return
218+
}
219+
updateSearchParams({ cluster: [], environment: [], cveName: cveNameSearchString })
220+
}
221+
222+
const handleSearchCVEButtonClick = () => {
223+
updateSearchParams({ cveName: cveNameSearchKey })
224+
}
225+
226+
return (
227+
<div className="vulnerability-exp">
228+
<div className="px-20 pt-16">
229+
<FeatureTitleWithInfo
230+
title="CVE Policy"
231+
renderDescriptionContent={() =>
232+
'Check the exposure of your system for a CVE-ID for future deployments.'
233+
}
234+
showInfoIconTippy
235+
dataTestId="cve-exposure-heading"
236+
/>
237+
</div>
238+
<div className="px-20 py-16 flexbox-col dc__gap-16">
239+
<div className="flexbox dc__gap-8">
240+
<SearchBar
241+
initialSearchText={cveName}
242+
containerClassName="w-250 br-4"
243+
handleSearchChange={setCveNameSearchKey}
244+
handleEnter={handleSearchCVE}
245+
inputProps={{
246+
placeholder: 'Enter CVE ID',
247+
autoFocus: true,
248+
}}
249+
dataTestId="security-vulnerability-cve-list"
250+
/>
251+
<Button
252+
dataTestId="search-vulnerability-button"
253+
text="Search"
254+
size={ComponentSizeType.medium}
255+
onClick={handleSearchCVEButtonClick}
256+
component={ButtonComponentType.button}
257+
/>
258+
</div>
259+
{!!cveName && (
260+
<span className="fs-13">
261+
Showing results for&nbsp;
262+
<a href={getCVEUrlFromCVEName(cveName)} rel="noopener noreferrer" target="_blank">
263+
{cveName}
264+
</a>
265+
</span>
266+
)}
267+
</div>
268+
<div className="dc__border-bottom-n1" />
269+
{!!cveName && <ExposureListContainer urlFilters={urlFilters} />}
270+
</div>
271+
)
272+
}
273+
274+
export default VulnerabilityExposure
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as VulnerabilityExposure } from './VulnerabilityExposure'

src/components/security/SecurityPoliciesTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React, { Component } from 'react'
17+
import { Component } from 'react'
1818
import { Switch, Route, Redirect, NavLink, RouteComponentProps } from 'react-router-dom'
1919
import { SecurityPolicyGlobal } from './SecurityPolicyGlobal'
2020
import { SecurityPolicyCluster } from './SecurityPolicyCluster'
2121
import { SecurityPolicyApp } from './SecurityPolicyApp'
2222
import { SecurityPolicyEnvironment } from './SecurityPolicyEnvironment'
23-
import { VulnerabilityExposure } from './VulnerabilityExposure'
23+
import { VulnerabilityExposure } from './AddCVEPolicy'
2424

2525
export class SecurityPoliciesTab extends Component<RouteComponentProps<{}>> {
2626
renderRouter() {

0 commit comments

Comments
 (0)