From 6ef0e95a22b6cda49051057a77bb5f7053554bc8 Mon Sep 17 00:00:00 2001 From: milan-deepfence Date: Wed, 25 Oct 2023 22:21:07 +0530 Subject: [PATCH 1/4] wip: masking/unmsking --- .../apps/dashboard/api-spec.json | 4 +- .../models/ModelScanResultsMaskRequest.ts | 26 +- .../pages/VulnerabilityScanResults.tsx | 279 ++++++++++++++++-- .../dashboard/src/queries/vulnerability.tsx | 2 + 4 files changed, 273 insertions(+), 38 deletions(-) diff --git a/deepfence_frontend/apps/dashboard/api-spec.json b/deepfence_frontend/apps/dashboard/api-spec.json index 6a96bf88ee..3bdd3be856 100644 --- a/deepfence_frontend/apps/dashboard/api-spec.json +++ b/deepfence_frontend/apps/dashboard/api-spec.json @@ -12787,8 +12787,10 @@ "required": ["scan_id", "result_ids", "scan_type"], "type": "object", "properties": { + "entity_id": { "type": "string" }, + "image_name": { "type": "string" }, + "image_tag": { "type": "string" }, "mask_across_hosts_and_images": { "type": "boolean" }, - "mask_in_this_host_or_image_tags": { "type": "boolean" }, "result_ids": { "type": "array", "items": { "type": "string" }, diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsMaskRequest.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsMaskRequest.ts index 2d2e4c1c94..e286ddc913 100644 --- a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsMaskRequest.ts +++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsMaskRequest.ts @@ -21,16 +21,28 @@ import { exists, mapValues } from '../runtime'; export interface ModelScanResultsMaskRequest { /** * - * @type {boolean} + * @type {string} * @memberof ModelScanResultsMaskRequest */ - mask_across_hosts_and_images?: boolean; + entity_id?: string; + /** + * + * @type {string} + * @memberof ModelScanResultsMaskRequest + */ + image_name?: string; + /** + * + * @type {string} + * @memberof ModelScanResultsMaskRequest + */ + image_tag?: string; /** * * @type {boolean} * @memberof ModelScanResultsMaskRequest */ - mask_in_this_host_or_image_tags?: boolean; + mask_across_hosts_and_images?: boolean; /** * * @type {Array} @@ -87,8 +99,10 @@ export function ModelScanResultsMaskRequestFromJSONTyped(json: any, ignoreDiscri } return { + 'entity_id': !exists(json, 'entity_id') ? undefined : json['entity_id'], + 'image_name': !exists(json, 'image_name') ? undefined : json['image_name'], + 'image_tag': !exists(json, 'image_tag') ? undefined : json['image_tag'], 'mask_across_hosts_and_images': !exists(json, 'mask_across_hosts_and_images') ? undefined : json['mask_across_hosts_and_images'], - 'mask_in_this_host_or_image_tags': !exists(json, 'mask_in_this_host_or_image_tags') ? undefined : json['mask_in_this_host_or_image_tags'], 'result_ids': json['result_ids'], 'scan_id': json['scan_id'], 'scan_type': json['scan_type'], @@ -104,8 +118,10 @@ export function ModelScanResultsMaskRequestToJSON(value?: ModelScanResultsMaskRe } return { + 'entity_id': value.entity_id, + 'image_name': value.image_name, + 'image_tag': value.image_tag, 'mask_across_hosts_and_images': value.mask_across_hosts_and_images, - 'mask_in_this_host_or_image_tags': value.mask_in_this_host_or_image_tags, 'result_ids': value.result_ids, 'scan_id': value.scan_id, 'scan_type': value.scan_type, diff --git a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx index 64a94d6901..d28c2821c2 100644 --- a/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/vulnerabilities/pages/VulnerabilityScanResults.tsx @@ -121,7 +121,7 @@ const action = async ({ const nodeIds = (formData.getAll('nodeIds[]') ?? []) as string[]; const actionType = formData.get('actionType'); const _scanId = scanId; - const mask = formData.get('maskHostAndImages'); + const maskHostAndImages = formData.get('maskHostAndImages'); if (!_scanId) { throw new Error('Scan ID is required'); } @@ -184,13 +184,30 @@ const action = async ({ const apiFunctionApi = apiWrapper({ fn: apiFunction, }); + const maskInfo: { + image_name?: string; + image_tag?: string; + entity_id?: string; + } = {}; + const hostId = formData.get('hostId'); + const imageName = formData.get('imageName'); + const imageTag = formData.get('imageTag'); + if (hostId) { + maskInfo.entity_id = hostId.toString(); + } + if (imageName) { + maskInfo.image_name = imageName.toString(); + } + if (imageTag) { + maskInfo.image_tag = imageTag.toString(); + } const result = await apiFunctionApi({ modelScanResultsMaskRequest: { - mask_across_hosts_and_images: mask === 'maskHostAndImages', - mask_in_this_host_or_image_tags: mask !== 'maskHostAndImages', + mask_across_hosts_and_images: maskHostAndImages === 'true', result_ids: [...nodeIds], scan_id: _scanId, scan_type: ScanTypeEnum.VulnerabilityScan, + ...maskInfo, }, }); if (!result.ok) { @@ -774,13 +791,30 @@ const ActionDropdown = ({ setShowDeleteDialog, onTableAction, nodeType, + maskInfo, }: { ids: string[]; trigger: React.ReactNode; setIdsToDelete: React.Dispatch>; setShowDeleteDialog: React.Dispatch>; - onTableAction: (ids: string[], actionType: string, maskHostAndImages?: string) => void; + onTableAction: ( + ids: string[], + actionType: string, + maskInfo?: { + hostId?: string; + imageName?: string; + imageTag?: string; + maskHostAndImages?: boolean; + }, + ) => void; nodeType: string; + maskInfo: + | { + imageName?: string; + imageTag?: string; + hostId?: string; + } + | undefined; }) => { const isHost = nodeType === 'host'; return ( @@ -789,27 +823,93 @@ const ActionDropdown = ({ align={'start'} content={ <> - onTableAction(ids, ActionEnumType.MASK, '')}> - {isHost - ? 'Mask vulnerability for this host' - : 'Mask vulnerability for this image(all tags)'} - + {isHost ? ( + + onTableAction(ids, ActionEnumType.MASK, { + hostId: maskInfo?.hostId, + }) + } + > + Mask vulnerability for this host + + ) : ( + <> + + onTableAction(ids, ActionEnumType.MASK, { + imageName: maskInfo?.imageName, + imageTag: maskInfo?.imageTag, + }) + } + > + Mask vulnerability for this image tag + + + onTableAction(ids, ActionEnumType.MASK, { + imageName: maskInfo?.imageName, + }) + } + > + Mask vulnerability for this image(all tags) + + + )} onTableAction(ids, ActionEnumType.MASK, 'maskHostAndImages')} + onClick={() => + onTableAction(ids, ActionEnumType.MASK, { + maskHostAndImages: true, + }) + } > Mask vulnerability across hosts and images + - onTableAction(ids, ActionEnumType.UNMASK, '')}> - {isHost - ? 'Un-mask vulnerability for this host' - : 'Un-mask vulnerability for this image(all tags)'} - + {isHost ? ( + + onTableAction(ids, ActionEnumType.UNMASK, { + hostId: maskInfo?.hostId, + }) + } + > + Un-mask vulnerability for this host + + ) : ( + <> + + onTableAction(ids, ActionEnumType.UNMASK, { + imageName: maskInfo?.imageName, + imageTag: maskInfo?.imageTag, + }) + } + > + Un-mask vulnerability for this image tag + + + onTableAction(ids, ActionEnumType.UNMASK, { + imageTag: maskInfo?.imageTag, + }) + } + > + Un-mask vulnerability for this image(all tags) + + + )} onTableAction(ids, ActionEnumType.UNMASK, 'maskHostAndImages')} + onClick={() => + onTableAction(ids, ActionEnumType.UNMASK, { + maskHostAndImages: true, + }) + } > Un-mask vulnerability across hosts and images + { @@ -841,11 +941,27 @@ const BulkActions = ({ setIdsToDelete, setShowDeleteDialog, onTableAction, + maskInfo, }: { ids: string[]; setIdsToDelete: React.Dispatch>; setShowDeleteDialog: React.Dispatch>; - onTableAction: (ids: string[], actionType: string, maskHostAndImages?: string) => void; + onTableAction: ( + ids: string[], + actionType: string, + maskInfo?: { + hostId?: string; + imageName?: string; + imageTag?: string; + maskHostAndImages?: boolean; + }, + ) => void; + maskInfo?: { + hostId?: string; + imageName?: string; + imageTag?: string; + maskHostAndImages?: boolean; + }; }) => { const [openNotifyModal, setOpenNotifyModal] = useState(false); const { @@ -865,13 +981,45 @@ const BulkActions = ({ disabled={!ids.length} content={ <> - onTableAction(ids, ActionEnumType.MASK, '')}> - {isHost - ? 'Mask vulnerabilities for this host' - : 'Mask vulnerabilities for this image(all tags)'} - + {isHost ? ( + + onTableAction(ids, ActionEnumType.MASK, { + hostId: maskInfo?.hostId, + }) + } + > + Mask vulnerabilities for this host + + ) : ( + <> + + onTableAction(ids, ActionEnumType.MASK, { + imageName: maskInfo?.imageName, + imageTag: maskInfo?.imageTag, + }) + } + > + Mask vulnerabilities for this image tag + + + onTableAction(ids, ActionEnumType.MASK, { + imageName: maskInfo?.imageName, + }) + } + > + Mask vulnerabilities for this image(all tags) + + + )} onTableAction(ids, ActionEnumType.MASK, 'maskHostAndImages')} + onClick={() => + onTableAction(ids, ActionEnumType.MASK, { + maskHostAndImages: true, + }) + } > Mask vulnerabilities across hosts and images @@ -895,14 +1043,44 @@ const BulkActions = ({ disabled={!ids.length} content={ <> - onTableAction(ids, ActionEnumType.UNMASK, '')}> - {isHost - ? 'Un-mask vulnerabilities for this host' - : 'Un-mask vulnerabilities for this image(all tags)'} - + {isHost ? ( + + onTableAction(ids, ActionEnumType.UNMASK, { + hostId: maskInfo?.hostId, + }) + } + > + Un-mask vulnerabilities for this host + + ) : ( + <> + + onTableAction(ids, ActionEnumType.UNMASK, { + imageName: maskInfo?.imageName, + imageTag: maskInfo?.imageTag, + }) + } + > + Un-mask vulnerabilities for this image tag + + + onTableAction(ids, ActionEnumType.UNMASK, { + imageTag: maskInfo?.imageTag, + }) + } + > + Un-mask vulnerabilities for this image(all tags) + + + )} - onTableAction(ids, ActionEnumType.UNMASK, 'maskHostAndImages') + onTableAction(ids, ActionEnumType.UNMASK, { + maskHostAndImages: true, + }) } > Un-mask vulnerabilities across hosts and images @@ -1311,12 +1489,35 @@ const CVEResults = () => { const fetcher = useFetcher(); const onTableAction = useCallback( - (ids: string[], actionType: string, maskHostAndImages?: string) => { + ( + ids: string[], + actionType: string, + maskInfo?: { + hostId?: string; + imageName?: string; + imageTag?: string; + maskHostAndImages?: boolean; + }, + ) => { const formData = new FormData(); formData.append('actionType', actionType); if (actionType === ActionEnumType.MASK || actionType === ActionEnumType.UNMASK) { - formData.append('maskHostAndImages', maskHostAndImages ?? ''); + if (maskInfo?.maskHostAndImages) { + formData.append( + 'maskHostAndImages', + maskInfo?.maskHostAndImages?.toString() ?? '', + ); + } + if (maskInfo?.hostId) { + formData.append('hostId', maskInfo.hostId?.toString() ?? ''); + } + if (maskInfo?.imageName) { + formData.append('imageName', maskInfo.imageName?.toString() ?? ''); + } + if (maskInfo?.imageTag) { + formData.append('imageTag', maskInfo.imageTag?.toString() ?? ''); + } } ids.forEach((item) => formData.append('nodeIds[]', item)); @@ -1432,7 +1633,16 @@ const CVETable = ({ rowSelectionState, setRowSelectionState, }: { - onTableAction: (ids: string[], actionType: string, maskHostAndImages?: string) => void; + onTableAction: ( + ids: string[], + actionType: string, + maskInfo?: { + hostId?: string; + imageName?: string; + imageTag?: string; + maskHostAndImages?: boolean; + }, + ) => void; setIdsToDelete: React.Dispatch>; setShowDeleteDialog: React.Dispatch>; rowSelectionState: RowSelectionState; @@ -1462,6 +1672,11 @@ const CVETable = ({ setShowDeleteDialog={setShowDeleteDialog} onTableAction={onTableAction} nodeType={nodeType} + maskInfo={{ + imageName: data.data?.dockerImageName, + imageTag: data.data?.imageTag, + hostId: data.data?.hostId, + }} trigger={