diff --git a/deepfence_frontend/apps/dashboard/api-spec.json b/deepfence_frontend/apps/dashboard/api-spec.json index 778235e388..a40ede3e80 100644 --- a/deepfence_frontend/apps/dashboard/api-spec.json +++ b/deepfence_frontend/apps/dashboard/api-spec.json @@ -12358,6 +12358,7 @@ "required": ["scan_id", "result_ids", "scan_type"], "type": "object", "properties": { + "notify_individual": { "type": "boolean" }, "result_ids": { "type": "array", "items": { "type": "string" }, diff --git a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsActionRequest.ts b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsActionRequest.ts index c80202feee..04980db0ef 100644 --- a/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsActionRequest.ts +++ b/deepfence_frontend/apps/dashboard/src/api/generated/models/ModelScanResultsActionRequest.ts @@ -19,6 +19,12 @@ import { exists, mapValues } from '../runtime'; * @interface ModelScanResultsActionRequest */ export interface ModelScanResultsActionRequest { + /** + * + * @type {boolean} + * @memberof ModelScanResultsActionRequest + */ + notify_individual?: boolean; /** * * @type {Array} @@ -75,6 +81,7 @@ export function ModelScanResultsActionRequestFromJSONTyped(json: any, ignoreDisc } return { + 'notify_individual': !exists(json, 'notify_individual') ? undefined : json['notify_individual'], 'result_ids': json['result_ids'], 'scan_id': json['scan_id'], 'scan_type': json['scan_type'], @@ -90,6 +97,7 @@ export function ModelScanResultsActionRequestToJSON(value?: ModelScanResultsActi } return { + 'notify_individual': value.notify_individual, 'result_ids': value.result_ids, 'scan_id': value.scan_id, 'scan_type': value.scan_type, diff --git a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx index 4cc896cbf7..3cfa61f6b4 100644 --- a/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/malwares/pages/MalwareScanResults.tsx @@ -16,6 +16,7 @@ import { BreadcrumbLink, Button, Card, + Checkbox, CircleSpinner, Combobox, ComboboxOption, @@ -110,6 +111,7 @@ const action = async ({ } if (actionType === ActionEnumType.DELETE || actionType === ActionEnumType.NOTIFY) { + const notifyIndividual = formData.get('notifyIndividual')?.toString(); const apiFunction = actionType === ActionEnumType.DELETE ? getScanResultsApiClient().deleteScanResult @@ -122,6 +124,7 @@ const action = async ({ result_ids: [...ids], scan_id: _scanId, scan_type: ScanTypeEnum.MalwareScan, + notify_individual: notifyIndividual === 'on', }, }); if (!result.ok) { @@ -461,6 +464,85 @@ const DeleteScanConfirmationModal = ({ ); }; +const NotifyModal = ({ + open, + ids, + closeModal, +}: { + open: boolean; + ids: string[]; + closeModal: React.Dispatch>; +}) => { + const fetcher = useFetcher(); + + return ( + closeModal(false)} + title={ + !fetcher.data?.success ? ( +
+ + + + Notify malwares +
+ ) : undefined + } + > + {!fetcher.data?.success ? ( + + + {ids.map((id) => ( + + ))} + +
+ The selected malwares will be notified. +
+ Do you want to notify each malware separately? +
+ +
+ {fetcher.data?.message && ( +

+ {fetcher.data?.message} +

+ )} +
+
+ + +
+
+ ) : ( + + )} +
+ ); +}; + const ScanHistory = () => { return (
@@ -642,6 +724,14 @@ const ActionDropdown = ({ Un-mask malware across hosts and images + { + onTableAction(ids, ActionEnumType.NOTIFY); + }} + > + Notify + + { setIdsToDelete(ids); @@ -670,8 +760,12 @@ const BulkActions = ({ setShowDeleteDialog: React.Dispatch>; onTableAction: (ids: string[], actionType: string, maskHostAndImages?: string) => void; }) => { + const [openNotifyModal, setOpenNotifyModal] = useState(false); return ( <> + {openNotifyModal && ( + + )} + + +
+ + ) : ( + + )} + + ); +}; + const ScanHistory = () => { return (
@@ -616,6 +699,13 @@ const ActionDropdown = ({ onTableAction(ids, ActionEnumType.UNMASK)}> Un-mask + { + onTableAction(ids, ActionEnumType.NOTIFY); + }} + > + Notify + { setIdsToDelete(ids); @@ -643,8 +733,12 @@ const BulkActions = ({ setShowDeleteDialog: React.Dispatch>; onTableAction: (ids: string[], actionType: string) => void; }) => { + const [openNotifyModal, setOpenNotifyModal] = useState(false); return ( <> + {openNotifyModal && ( + + )} + + +
+ + ) : ( + + )} + + ); +}; + const ScanHistory = () => { return (
@@ -613,6 +696,13 @@ const ActionDropdown = ({ onTableAction(ids, ActionEnumType.UNMASK)}> Un-mask + { + onTableAction(ids, ActionEnumType.NOTIFY); + }} + > + Notify + { setIdsToDelete(ids); @@ -641,8 +731,12 @@ const BulkActions = ({ setShowDeleteDialog: React.Dispatch>; onTableAction: (ids: string[], actionType: string) => void; }) => { + const [openNotifyModal, setOpenNotifyModal] = useState(false); return ( <> + {openNotifyModal && ( + + )} + + +
+ + ) : ( + + )} + + ); +}; + const ScanHistory = () => { return (
@@ -642,6 +724,14 @@ const ActionDropdown = ({ Un-mask secret across hosts and images + { + onTableAction(ids, ActionEnumType.NOTIFY); + }} + > + Notify + + { setIdsToDelete(ids); @@ -670,8 +760,12 @@ const BulkActions = ({ setShowDeleteDialog: React.Dispatch>; onTableAction: (ids: string[], actionType: string, maskHostAndImages?: string) => void; }) => { + const [openNotifyModal, setOpenNotifyModal] = useState(false); return ( <> + {openNotifyModal && ( + + )} + + +
+ + ) : ( + + )} + + ); +}; + const ScanHistory = () => { return (
@@ -603,6 +685,14 @@ const ActionDropdown = ({ Un-mask vulnerability across hosts and images + { + onTableAction(ids, ActionEnumType.NOTIFY); + }} + > + Notify + + { setIdsToDelete(ids); @@ -631,8 +721,12 @@ const BulkActions = ({ setShowDeleteDialog: React.Dispatch>; onTableAction: (ids: string[], actionType: string, maskHostAndImages?: string) => void; }) => { + const [openNotifyModal, setOpenNotifyModal] = useState(false); return ( <> + {openNotifyModal && ( + + )} +