Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bulk delete for scans #1622

Merged
merged 2 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deepfence_frontend/apps/dashboard/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,14 @@ export function getScanResultsApiClient() {
return {
deleteScanResult: scanResultsApi.deleteScanResult.bind(scanResultsApi),
downloadScanResultsForScanID: scanResultsApi.downloadScanResults.bind(scanResultsApi),
bulkDeleteScans: scanResultsApi.bulkDeleteScans.bind(scanResultsApi),
deleteScanResultsForScanID:
scanResultsApi.deleteScanResultsForScanID.bind(scanResultsApi),
notifyScanResult: scanResultsApi.notifyScanResult.bind(scanResultsApi),
maskScanResult: scanResultsApi.maskScanResult.bind(scanResultsApi),
unmaskScanResult: scanResultsApi.unmaskScanResult.bind(scanResultsApi),
getAllNodesInScanResults:
scanResultsApi.getAllNodesInScanResults.bind(scanResultsApi),
bulkDeleteScansHistory: scanResultsApi.bulkDeleteScans.bind(scanResultsApi),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {

import { getScanResultsApiClient } from '@/api/api';
import {
ModelBulkDeleteScansRequestScanTypeEnum,
ModelScanInfo,
UtilsReportFiltersNodeTypeEnum,
UtilsReportFiltersScanTypeEnum,
Expand All @@ -47,6 +48,7 @@ import { EllipsisIcon } from '@/components/icons/common/Ellipsis';
import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLine';
import { FilterIcon } from '@/components/icons/common/Filter';
import { TimesIcon } from '@/components/icons/common/Times';
import { TrashLineIcon } from '@/components/icons/common/TrashLine';
import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm';
import { ScanStatusBadge } from '@/components/ScanStatusBadge';
import { MalwareIcon } from '@/components/sideNavigation/icons/Malware';
Expand Down Expand Up @@ -108,11 +110,22 @@ const action = async ({

if (actionType === ActionEnumType.DELETE) {
const resultApi = apiWrapper({
fn: getScanResultsApiClient().deleteScanResultsForScanID,
fn: getScanResultsApiClient().bulkDeleteScans,
});
const result = await resultApi({
scanId: scanIds[0] as string, // TODO: Add support for multi deletion
scanType: ScanTypeEnum.MalwareScan,
modelBulkDeleteScansRequest: {
filters: {
compare_filter: null,
contains_filter: {
filter_in: {
node_id: scanIds,
},
},
order_filter: { order_fields: [] },
match_filter: { filter_in: {} },
},
scan_type: ModelBulkDeleteScansRequestScanTypeEnum.Malware,
},
});
if (!result.ok) {
if (result.error.response.status === 400 || result.error.response.status === 409) {
Expand Down Expand Up @@ -1031,6 +1044,23 @@ const BulkActions = ({
>
Cancel Scan
</Button>
<Button
color="error"
variant="flat"
startIcon={<TrashLineIcon />}
size="sm"
disabled={!selectedRows.length}
onClick={() => {
setIdsToDelete(
selectedRows.map((row) => {
return row.scanId;
}),
);
setShowDeleteDialog(true);
}}
>
Delete
</Button>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
useSearchParams,
} from 'react-router-dom';
import { toast } from 'sonner';
import { cn } from 'tailwind-preset';
import {
Badge,
Breadcrumb,
Expand All @@ -34,6 +35,7 @@ import {

import { getScanResultsApiClient } from '@/api/api';
import {
ModelBulkDeleteScansRequestScanTypeEnum,
ModelCloudNodeAccountInfo,
UtilsReportFiltersNodeTypeEnum,
UtilsReportFiltersScanTypeEnum,
Expand All @@ -45,12 +47,12 @@ import {
ICloudAccountType,
SearchableCloudAccountsList,
} from '@/components/forms/SearchableCloudAccountsList';
import { BellLineIcon } from '@/components/icons/common/BellLine';
import { EllipsisIcon } from '@/components/icons/common/Ellipsis';
import { ErrorStandardLineIcon } from '@/components/icons/common/ErrorStandardLine';
import { FilterIcon } from '@/components/icons/common/Filter';
import { PlusIcon } from '@/components/icons/common/Plus';
import { TimesIcon } from '@/components/icons/common/Times';
import { TrashLineIcon } from '@/components/icons/common/TrashLine';
import { CLOUDS } from '@/components/scan-configure-forms/ComplianceScanConfigureForm';
import { StopScanForm } from '@/components/scan-configure-forms/StopScanForm';
import { ScanStatusBadge } from '@/components/ScanStatusBadge';
Expand Down Expand Up @@ -132,22 +134,33 @@ const action = async ({
}: ActionFunctionArgs): Promise<{ success?: boolean; message?: string } | null> => {
const formData = await request.formData();
const actionType = formData.get('actionType');
const scanId = formData.get('scanId');
const scanType = formData.get('scanType');
const scanIds = formData.getAll('scanId');
const scanType = formData.get('scanType') as ModelBulkDeleteScansRequestScanTypeEnum;
if (!actionType) {
throw new Error('Invalid action');
}

if (actionType === ActionEnumType.DELETE) {
if (!scanId) {
throw new Error('Invalid action');
if (scanIds.length === 0) {
throw new Error('Scan ids are required for deletion');
}
const deleteScanResultsForScanIDApi = apiWrapper({
fn: getScanResultsApiClient().deleteScanResultsForScanID,
fn: getScanResultsApiClient().bulkDeleteScans,
});
const result = await deleteScanResultsForScanIDApi({
scanId: scanId.toString(),
scanType: scanType as ScanTypeEnum,
modelBulkDeleteScansRequest: {
filters: {
compare_filter: null,
contains_filter: {
filter_in: {
node_id: scanIds,
},
},
order_filter: { order_fields: [] },
match_filter: { filter_in: {} },
},
scan_type: scanType,
},
});
if (!result.ok) {
if (result.error.response.status === 400 || result.error.response.status === 409) {
Expand Down Expand Up @@ -361,30 +374,42 @@ const Filters = () => {
};
const DeleteConfirmationModal = ({
showDialog,
scanId,
scanIds,
scanType,
setShowDialog,
onSuccess,
}: {
showDialog: boolean;
scanId: string;
scanIds: string[];
setShowDialog: React.Dispatch<React.SetStateAction<boolean>>;
scanType?: ScanTypeEnum;
scanType?: ModelBulkDeleteScansRequestScanTypeEnum;
onSuccess: () => void;
}) => {
const fetcher = useFetcher();

const onDeleteAction = useCallback(
(actionType: string) => {
const formData = new FormData();
formData.append('actionType', actionType);
formData.append('scanId', scanId);
scanIds.forEach((scanId) => formData.append('scanId', scanId));
formData.append('scanType', scanType ?? '');
fetcher.submit(formData, {
method: 'post',
});
},
[scanId, scanType, fetcher],
[scanIds, scanType, fetcher],
);

useEffect(() => {
if (
fetcher.state === 'idle' &&
fetcher.data?.success &&
fetcher.data.action === ActionEnumType.DELETE
) {
onSuccess();
}
}, [fetcher]);

return (
<Modal
size="s"
Expand Down Expand Up @@ -450,25 +475,23 @@ const ActionDropdown = ({
scanType,
nodeId,
trigger,
setShowDeleteDialog,
onTableAction,
setScanIdToDelete,
}: {
trigger: React.ReactNode;
scanId?: string;
nodeType?: string;
scanType: ScanTypeEnum;
scanStatus: string;
nodeId?: string;
setShowDeleteDialog: React.Dispatch<React.SetStateAction<boolean>>;
onTableAction: (ids: string[], actionType: ActionEnumType) => void;
setScanIdToDelete: React.Dispatch<React.SetStateAction<string>>;
}) => {
const fetcher = useFetcher();
const [open, setOpen] = useState(false);
const { downloadScan } = useDownloadScan();
const [openStopScanModal, setOpenStopScanModal] = useState(false);

const [showDeleteDialog, setShowDeleteDialog] = useState(false);

const onDownloadAction = useCallback(() => {
downloadScan({
scanId,
Expand Down Expand Up @@ -498,7 +521,21 @@ const ActionDropdown = ({
scanType={scanType}
/>
)}

{showDeleteDialog && (
<DeleteConfirmationModal
showDialog={showDeleteDialog}
scanIds={[scanId]}
scanType={
(scanType as ScanTypeEnum) === ScanTypeEnum.ComplianceScan
? ModelBulkDeleteScansRequestScanTypeEnum.Compliance
: ModelBulkDeleteScansRequestScanTypeEnum.CloudCompliance
}
setShowDialog={setShowDeleteDialog}
onSuccess={() => {
//
}}
/>
)}
<Dropdown
triggerAsChild
align="start"
Expand Down Expand Up @@ -542,11 +579,14 @@ const ActionDropdown = ({
disabled={!scanId || !nodeType}
onClick={() => {
if (!scanId || !nodeType) return;
setScanIdToDelete(scanId);
setShowDeleteDialog(true);
}}
>
<span className="flex items-center gap-x-2 text-red-700 dark:text-status-error">
<span
className={cn('flex items-center gap-x-2', {
'text-red-700 dark:text-status-error': scanId,
})}
>
Delete latest scan
</span>
</DropdownItem>
Expand All @@ -561,11 +601,13 @@ const ActionDropdown = ({

const BulkActions = ({
onClick,
onDelete,
onCancelScan,
disabled,
}: {
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined;
onCancelScan?: React.MouseEventHandler<HTMLButtonElement> | undefined;
onDelete?: React.MouseEventHandler<HTMLButtonElement> | undefined;
disabled: boolean;
}) => {
const { navigate } = usePageNavigation();
Expand Down Expand Up @@ -610,6 +652,16 @@ const BulkActions = ({
>
Cancel scan
</Button>
<Button
color="error"
variant="flat"
startIcon={<TrashLineIcon />}
size="sm"
disabled={disabled}
onClick={onDelete}
>
Delete scan
</Button>
</>
);
};
Expand All @@ -619,17 +671,13 @@ const AccountTable = ({
rowSelectionState,
onTableAction,
scanType,
setShowDeleteDialog,
setScanIdToDelete,
nodeType,
}: {
nodeType?: ComplianceScanNodeTypeEnum;
scanType: 'ComplianceScan' | 'CloudComplianceScan';
setRowSelectionState: React.Dispatch<React.SetStateAction<RowSelectionState>>;
rowSelectionState: RowSelectionState;
onTableAction: (ids: string[], actionType: ActionEnumType) => void;
setShowDeleteDialog: React.Dispatch<React.SetStateAction<boolean>>;
setScanIdToDelete: React.Dispatch<React.SetStateAction<string>>;
}) => {
const [searchParams, setSearchParams] = useSearchParams();
const { data } = usePostureAccounts();
Expand Down Expand Up @@ -712,10 +760,8 @@ const AccountTable = ({
nodeId={cell.row.original.node_id}
nodeType={nodeType}
scanType={scanType}
setScanIdToDelete={setScanIdToDelete}
scanStatus={cell.row.original.last_scan_status || ''}
onTableAction={onTableAction}
setShowDeleteDialog={setShowDeleteDialog}
trigger={
<button className="p-1 flex">
<span className="block h-4 w-4 dark:text-text-text-and-icon rotate-90 shrink-0">
Expand Down Expand Up @@ -976,7 +1022,6 @@ const Accounts = () => {

const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [showCancelScan, setShowCancelScan] = useState(false);
const [scanIdToDelete, setScanIdToDelete] = useState('');
const [nodeIdsToScan, setNodeIdsToScan] = useState<string[]>([]);

const scanType = isNonCloudProvider(routeParams.nodeType)
Expand Down Expand Up @@ -1006,13 +1051,6 @@ const Accounts = () => {
setSelectedScanType(scanType);
return;
}
const formData = new FormData();
formData.append('actionType', actionType);
formData.append('scanId', scanIdToDelete);
nodeIds.forEach((item) => formData.append('nodeIds[]', item));
fetcher.submit(formData, {
method: 'post',
});
},
[fetcher],
);
Expand Down Expand Up @@ -1041,6 +1079,9 @@ const Accounts = () => {
onCancelScan={() => {
setShowCancelScan(true);
}}
onDelete={() => {
setShowDeleteDialog(true);
}}
/>
<Button
variant="flat"
Expand Down Expand Up @@ -1086,8 +1127,6 @@ const Accounts = () => {
<Suspense fallback={<TableSkeleton columns={6} rows={10} />}>
<AccountTable
setRowSelectionState={setRowSelectionState}
setScanIdToDelete={setScanIdToDelete}
setShowDeleteDialog={setShowDeleteDialog}
rowSelectionState={rowSelectionState}
onTableAction={onTableAction}
scanType={scanType}
Expand All @@ -1098,9 +1137,16 @@ const Accounts = () => {
{showDeleteDialog && (
<DeleteConfirmationModal
showDialog={showDeleteDialog}
scanId={scanIdToDelete}
scanType={scanType}
scanIds={selectedRows.map((row) => row.scanId)}
scanType={
isNonCloudProvider(routeParams.nodeType)
? ModelBulkDeleteScansRequestScanTypeEnum.Compliance
: ModelBulkDeleteScansRequestScanTypeEnum.CloudCompliance
}
setShowDialog={setShowDeleteDialog}
onSuccess={() => {
setRowSelectionState({});
}}
/>
)}
</div>
Expand Down
Loading