diff --git a/apps/dashboard/src/main/java/com/akto/action/ReportAction.java b/apps/dashboard/src/main/java/com/akto/action/ReportAction.java index 87617004fb..609ed8a46a 100644 --- a/apps/dashboard/src/main/java/com/akto/action/ReportAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/ReportAction.java @@ -1,5 +1,6 @@ package com.akto.action; +import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; @@ -10,6 +11,11 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import com.akto.dao.testing.sources.TestReportsDao; +import com.akto.dto.testing.sources.TestReports; +import com.mongodb.MongoCommandException; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; import org.apache.struts2.ServletActionContext; import org.bson.types.ObjectId; import org.json.JSONObject; @@ -32,10 +38,45 @@ public class ReportAction extends UserAction { private String reportUrl; private String pdf; private String status; + private boolean firstPollRequest; private static final LoggerMaker loggerMaker = new LoggerMaker(ReportAction.class); - + public String downloadReportPDF() { + if(reportUrl == null || reportUrl.isEmpty()) { + status = "ERROR"; + addActionError("Report URL cannot be empty"); + return ERROR.toUpperCase(); + } + + String reportUrlId; + try { + String path = new URL(reportUrl).getPath(); + String[] segments = path.split("/"); + reportUrlId = segments[segments.length - 1]; + } catch (Exception e) { + status = "ERROR"; + addActionError("Report URL cannot be empty"); + return ERROR.toUpperCase(); + } + + if(!ObjectId.isValid(reportUrlId)) { + status = "ERROR"; + addActionError("Report URL is invalid"); + return ERROR.toUpperCase(); + } + + ObjectId reportUrlIdObj = new ObjectId(reportUrlId); + + if(firstPollRequest) { + TestReports testReport = TestReportsDao.instance.findOne(Filters.eq("_id", reportUrlIdObj)); + if(testReport != null && (testReport.getPdfReportString() != null && !testReport.getPdfReportString().isEmpty())) { + status = "COMPLETED"; + pdf = testReport.getPdfReportString(); + return SUCCESS.toUpperCase(); + } + } + if (reportId == null) { // Initiate PDF generation @@ -89,6 +130,22 @@ public String downloadReportPDF() { if (status.equals("COMPLETED")) { loggerMaker.infoAndAddToDb("Pdf download status for report id - " + reportId + " completed. Attaching pdf in response ", LogDb.DASHBOARD); pdf = node.get("base64PDF").textValue(); + try { + TestReportsDao.instance.updateOne(Filters.eq("_id", reportUrlIdObj), Updates.set(TestReports.PDF_REPORT_STRING, pdf)); + } catch(Exception e) { + loggerMaker.errorAndAddToDb("Error: " + e.getMessage() + ", while updating report binary for reportId: " + reportId, LogDb.DASHBOARD); + if (e instanceof MongoCommandException) { + MongoCommandException mongoException = (MongoCommandException) e; + if (mongoException.getCode() == 17420) { + addActionError("The report is too large to save. Please reduce its size and try again."); + } else { + addActionError("A database error occurred while saving the report. Try again later."); + } + } else { + addActionError("An error occurred while updating the report in DB. Please try again."); + } + status = "ERROR"; + } } } catch (Exception e) { loggerMaker.errorAndAddToDb(e, "Error while polling pdf download for report id - " + reportId, LogDb.DASHBOARD); @@ -146,4 +203,8 @@ public String getStatus() { public void setStatus(String status) { this.status = status; } + + public void setFirstPollRequest(boolean firstPollRequest) { + this.firstPollRequest = firstPollRequest; + } } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/api.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/api.js index 5d9c8b82f4..4e9053a3a4 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/api.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/api.js @@ -425,11 +425,11 @@ export default { data: {} }) }, - downloadReportPDF(reportId, organizationName, reportDate, reportUrl) { + downloadReportPDF(reportId, organizationName, reportDate, reportUrl, firstPollRequest) { return request({ url: '/api/downloadReportPDF', method: 'post', - data: {reportId, organizationName, reportDate, reportUrl} + data: {reportId, organizationName, reportDate, reportUrl, firstPollRequest} }) }, fetchScript() { diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx index 13b04d2dfc..fb68a80bfe 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx @@ -1,5 +1,5 @@ /* eslint-disable no-loop-func */ -import { Box, Divider, Frame, HorizontalStack, Text, TopBar, VerticalStack } from '@shopify/polaris' +import { Box, Button, Divider, Frame, HorizontalStack, Text, TopBar, VerticalStack } from '@shopify/polaris' import React, { useEffect, useRef } from 'react' import api from '../api' import issuesApi from '@/apps/dashboard/pages/issues/api' @@ -233,7 +233,7 @@ const VulnerabilityReport = () => { const handleDownloadPF = async () => { - const WAIT_DURATION = 2500, MAX_RETRIES = 15 + const WAIT_DURATION = 5000, MAX_RETRIES = 60 const reportUrl = window.location.href let pdfError = "" @@ -242,20 +242,32 @@ const VulnerabilityReport = () => { setPdfDownloadEnabled(false) - const progressToastInterval = setInterval(() => { - func.setToast(true, false, "Report PDF generation in progress. Please wait...") + let reportToastInterval = setInterval(() => { + func.setToast(true, false, "Preparing your report. This might take a moment...") }, 1000) + let generationStarted = false + setTimeout(() => { + clearInterval(reportToastInterval) + generationStarted = true + if(status === "IN_PROGRESS") { + reportToastInterval = setInterval(() => { + func.setToast(true, false, "Report PDF generation in progress. Please wait...") + }, 1000) + } + }, 6000) + try { // Trigger pdf download - const startDownloadReponse = await api.downloadReportPDF(null, organizationName, currentDate, reportUrl) + const startDownloadReponse = await api.downloadReportPDF(null, organizationName, currentDate, reportUrl, true) const reportId = startDownloadReponse?.reportId status = startDownloadReponse?.status + pdf = startDownloadReponse?.pdf if (reportId !== null && status === "IN_PROGRESS") { // Poll for PDF completion for(let i = 0; i < MAX_RETRIES; i++) { - const pdfPollResponse = await api.downloadReportPDF(reportId, organizationName, currentDate, reportUrl) + const pdfPollResponse = await api.downloadReportPDF(reportId, organizationName, currentDate, reportUrl, false) status = pdfPollResponse?.status if (status === "COMPLETED") { @@ -268,16 +280,22 @@ const VulnerabilityReport = () => { await func.sleep(WAIT_DURATION) - func.setToast(true, false, "Report PDF generation in progress. Please wait...") + func.setToast(generationStarted, false, "Report PDF generation in progress. Please wait...") + + if(i === MAX_RETRIES - 1) { + pdfError = "Failed to download PDF. The size might be too large. Filter out unnecessary issues and try again." + } } } else { - pdfError = "Failed to start PDF download" + if(status !== "COMPLETED") { + pdfError = "Failed to start PDF download" + } } } catch (err) { - pdfError = err.message + pdfError = err?.response?.data?.actionErrors?.[0] || err.message } - clearInterval(progressToastInterval) + clearInterval(reportToastInterval) if (status === "COMPLETED") { if (pdf === undefined) { @@ -300,13 +318,13 @@ const VulnerabilityReport = () => { link.click(); func.setToast(true, false, "Report PDF downloaded.") } catch (err) { - pdfError = err.message + pdfError = err?.response?.data?.actionErrors?.[0] || err.message } } } if (pdfError !== "") { - func.setToast(true, true, `Error while downloading PDF. Please try again. \nError: ${pdfError}`) + func.setToast(true, true, `Error: ${pdfError}`) } setPdfDownloadEnabled(true) @@ -321,7 +339,9 @@ const VulnerabilityReport = () => { {currentDate}
- {/* */} + { + (window.USER_NAME?.toLowerCase()?.includes("@akto.io") || window.ACCOUNT_NAME?.toLowerCase()?.includes("advanced bank")) ? : <> + } Logo