mirror of
https://github.com/CERT-Polska/drakvuf-sandbox.git
synced 2026-10-11 22:00:00 +00:00
Feature: Download raw analysis files from web (#1148)
This commit is contained in:
@@ -14,5 +14,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ./dev/frontend.Dockerfile
|
||||
volumes:
|
||||
- "../drakrun/web/frontend/src:/app/src"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
@@ -28,6 +28,7 @@ from drakrun.lib.config import load_config
|
||||
from drakrun.lib.paths import UPLOADS_DIR
|
||||
from drakrun.lib.s3_storage import get_s3_client, is_s3_enabled, upload_sample_to_s3
|
||||
from drakrun.web.schema import (
|
||||
AnalysisFileRequestQuery,
|
||||
AnalysisListResponse,
|
||||
AnalysisRequestPath,
|
||||
AnalysisResponse,
|
||||
@@ -40,6 +41,7 @@ from drakrun.web.schema import (
|
||||
UploadFileForm,
|
||||
)
|
||||
from drakrun.web.storage import (
|
||||
list_analysis_files,
|
||||
list_analysis_logs,
|
||||
open_seekable_stream,
|
||||
read_analysis_json,
|
||||
@@ -307,6 +309,25 @@ def list_logs(path: AnalysisRequestPath):
|
||||
return jsonify(analysis_logs)
|
||||
|
||||
|
||||
@api.get("/files/<task_uid>")
|
||||
def list_files(path: AnalysisRequestPath):
|
||||
task_uid = path.task_uid
|
||||
analysis_files = list_analysis_files(task_uid, s3_config=config.s3)
|
||||
return jsonify(analysis_files)
|
||||
|
||||
|
||||
@api.get("/files/<task_uid>/download")
|
||||
def download_analysis_file(path: AnalysisRequestPath, query: AnalysisFileRequestQuery):
|
||||
task_uid = path.task_uid
|
||||
return send_analysis_file(
|
||||
task_uid,
|
||||
query.filename,
|
||||
s3_config=config.s3,
|
||||
mimetype="application/octet-stream",
|
||||
download_name=query.filename.split("/")[-1],
|
||||
)
|
||||
|
||||
|
||||
@api.get("/graph/<task_uid>")
|
||||
def graph(path: AnalysisRequestPath):
|
||||
task_uid = path.task_uid
|
||||
|
||||
Generated
+128
-1
@@ -6,7 +6,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@drakvuf-sandbox/web",
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||
@@ -16,10 +16,13 @@
|
||||
"@novnc/novnc": "^1.5.0",
|
||||
"axios": "^1.12.0",
|
||||
"bootstrap": "^5.3.5",
|
||||
"jszip": "^3.10.1",
|
||||
"jszip-utils": "^0.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-medium-image-zoom": "^5.2.14",
|
||||
"react-modal": "^3.16.3",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"react-select": "^5.10.1",
|
||||
"startbootstrap-sb-admin": "^7.0.7"
|
||||
@@ -1803,6 +1806,11 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
@@ -2180,6 +2188,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/exenv": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
|
||||
"integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw=="
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -2471,6 +2484,11 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
@@ -2495,6 +2513,11 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
@@ -2535,6 +2558,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -2605,6 +2633,22 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz",
|
||||
"integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg=="
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -2627,6 +2671,14 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
@@ -2812,6 +2864,11 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -2940,6 +2997,11 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -2988,6 +3050,11 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"node_modules/react-medium-image-zoom": {
|
||||
"version": "5.2.14",
|
||||
"resolved": "https://registry.npmjs.org/react-medium-image-zoom/-/react-medium-image-zoom-5.2.14.tgz",
|
||||
@@ -3003,6 +3070,21 @@
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-modal": {
|
||||
"version": "3.16.3",
|
||||
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz",
|
||||
"integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==",
|
||||
"dependencies": {
|
||||
"exenv": "^1.2.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-lifecycles-compat": "^3.0.0",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19",
|
||||
"react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
@@ -3085,6 +3167,20 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
@@ -3156,6 +3252,11 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
@@ -3170,6 +3271,11 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -3234,6 +3340,14 @@
|
||||
"@popperjs/core": "^2.11.6"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
@@ -3355,6 +3469,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/virtua": {
|
||||
"version": "0.40.3",
|
||||
"resolved": "https://registry.npmjs.org/virtua/-/virtua-0.40.3.tgz",
|
||||
@@ -3459,6 +3578,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
"@novnc/novnc": "^1.5.0",
|
||||
"axios": "^1.12.0",
|
||||
"bootstrap": "^5.3.5",
|
||||
"jszip": "^3.10.1",
|
||||
"jszip-utils": "^0.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-medium-image-zoom": "^5.2.14",
|
||||
"react-modal": "^3.16.3",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"react-select": "^5.10.1",
|
||||
"startbootstrap-sb-admin": "^7.0.7"
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import { use, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getAnalysisFileList } from "./api.js";
|
||||
import { CanceledError } from "axios";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faDownload, faFile } from "@fortawesome/free-solid-svg-icons";
|
||||
import Modal from "react-modal";
|
||||
import JSZip from "jszip";
|
||||
import JSZipUtils from "jszip-utils";
|
||||
|
||||
let BASE_URL = "";
|
||||
if (import.meta.env.VITE_API_SERVER) {
|
||||
BASE_URL = import.meta.env.VITE_API_SERVER;
|
||||
} else {
|
||||
BASE_URL = "/api";
|
||||
}
|
||||
|
||||
Modal.setAppElement("#root");
|
||||
|
||||
function urlToPromise(url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
JSZipUtils.getBinaryContent(url, function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function FileTree({ fileList, analysisId }) {
|
||||
return (
|
||||
<ul
|
||||
style={{ overflow: "auto", marginLeft: "8pt" }}
|
||||
className="list-unstyled"
|
||||
>
|
||||
{fileList.map((file, idx) => {
|
||||
return (
|
||||
<li key={`f-${idx}`}>
|
||||
<span className="text-nowrap font-monospace">
|
||||
<FontAwesomeIcon
|
||||
icon={faFile}
|
||||
className="me-3 text-primary"
|
||||
/>
|
||||
<a
|
||||
href={`${BASE_URL}/files/${analysisId}/download?filename=${encodeURIComponent(file)}`}
|
||||
>
|
||||
{file}
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export function AnalysisZipDownload({ files, analysisId }) {
|
||||
const [modalOpened, setModalOpened] = useState(false);
|
||||
const [modalMessage, setModalMessage] = useState("");
|
||||
const [downloadFinished, setDownloadFinished] = useState(false);
|
||||
const [blobURL, setBlobURL] = useState(null);
|
||||
const modalStyle = {
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
marginRight: "-50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
maxHeight: "100%",
|
||||
},
|
||||
};
|
||||
|
||||
const makeZip = useCallback(() => {
|
||||
const zip = new JSZip();
|
||||
for (let file of files) {
|
||||
zip.file(
|
||||
file,
|
||||
urlToPromise(
|
||||
`${BASE_URL}/files/${analysisId}/download?filename=${encodeURIComponent(file)}`,
|
||||
),
|
||||
{ binary: true },
|
||||
);
|
||||
}
|
||||
zip.generateAsync({ type: "blob" }, (metadata) => {
|
||||
let msg = "progression : " + metadata.percent.toFixed(2) + " %";
|
||||
if (metadata.currentFile) {
|
||||
msg += ", current file = " + metadata.currentFile;
|
||||
}
|
||||
setModalMessage(msg);
|
||||
}).then(
|
||||
(blob) => {
|
||||
setBlobURL(URL.createObjectURL(blob));
|
||||
setModalMessage("");
|
||||
setDownloadFinished(true);
|
||||
},
|
||||
(err) => {
|
||||
setModalMessage(err);
|
||||
setDownloadFinished(true);
|
||||
},
|
||||
);
|
||||
}, [files, analysisId]);
|
||||
|
||||
const startDownload = useCallback(() => {
|
||||
setDownloadFinished(false);
|
||||
setModalOpened(true);
|
||||
makeZip();
|
||||
}, [makeZip]);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
if (blobURL) {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
setBlobURL(null);
|
||||
}
|
||||
setModalOpened(false);
|
||||
}, [blobURL]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={modalOpened}
|
||||
onRequestClose={downloadFinished ? closeModal : undefined}
|
||||
contentLabel="Downloading analysis..."
|
||||
style={modalStyle}
|
||||
>
|
||||
<div className="text-center">
|
||||
{downloadFinished ? (
|
||||
blobURL ? (
|
||||
<div>
|
||||
<strong>
|
||||
Analysis downloaded successfully.
|
||||
</strong>
|
||||
<br />
|
||||
Click on the link below to store it:
|
||||
</div>
|
||||
) : (
|
||||
<div>Download failed</div>
|
||||
)
|
||||
) : (
|
||||
<div>Downloading analysis files...</div>
|
||||
)}
|
||||
|
||||
<span className="text-muted">{modalMessage}</span>
|
||||
{downloadFinished ? (
|
||||
<>
|
||||
{blobURL ? (
|
||||
<div>
|
||||
<a
|
||||
href={blobURL}
|
||||
download={`${analysisId}.zip`}
|
||||
>{`${analysisId}.zip`}</a>
|
||||
</div>
|
||||
) : (
|
||||
[]
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
[]
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary mb-2"
|
||||
onClick={() => startDownload()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faDownload} className="me-2" />
|
||||
Download all
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function AnalysisFilesViewer({ analysisId }) {
|
||||
const [files, setFiles] = useState();
|
||||
const [error, setError] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const abortController = new AbortController();
|
||||
setFiles(undefined);
|
||||
getAnalysisFileList({ analysisId, abortController })
|
||||
.then((response) => {
|
||||
setFiles(response.slice().sort());
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!(error instanceof CanceledError)) {
|
||||
setError(error);
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [analysisId]);
|
||||
|
||||
if (typeof error !== "undefined") {
|
||||
return <div className="text-danger">Error: {error.toString()}</div>;
|
||||
}
|
||||
|
||||
if (typeof files === "undefined") {
|
||||
return <div>Loading analysis files list...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<AnalysisZipDownload files={files} analysisId={analysisId} />
|
||||
<FileTree fileList={files} analysisId={analysisId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||
import axios, { CanceledError } from "axios";
|
||||
import { AnalysisSummary } from "./AnalysisSummary.jsx";
|
||||
import { ProcessBadge } from "./ProcessBadge.jsx";
|
||||
import { AnalysisFilesViewer } from "./AnalysisFiles.jsx";
|
||||
|
||||
export function AnalysisLogViewerTab({ analysisId }) {
|
||||
const logType = useTabContext()?.activeTab;
|
||||
@@ -269,6 +270,9 @@ function AnalysisReportTabs({
|
||||
<AnalysisScreenshotViewer analysis={analysis} />
|
||||
</PaddedTab>
|
||||
) : null}
|
||||
<PaddedTab tab="Analysis files">
|
||||
<AnalysisFilesViewer analysisId={analysis.id} />
|
||||
</PaddedTab>
|
||||
</TabSwitcher>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,3 +69,8 @@ li.selected {
|
||||
color: #6c757d;
|
||||
}
|
||||
/* Ends overriding */
|
||||
|
||||
.ReactModalPortal {
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@@ -103,6 +103,21 @@ export async function getProcessLog({
|
||||
return logRequest.data;
|
||||
}
|
||||
|
||||
export async function getAnalysisFileList({
|
||||
analysisId,
|
||||
abortController = undefined,
|
||||
}) {
|
||||
const listRequest = await axios.get(
|
||||
`/files/${analysisId}`,
|
||||
abortController
|
||||
? {
|
||||
signal: abortController.signal,
|
||||
}
|
||||
: {},
|
||||
);
|
||||
return listRequest.data;
|
||||
}
|
||||
|
||||
export async function uploadSample({
|
||||
file,
|
||||
timeout,
|
||||
|
||||
@@ -18,6 +18,10 @@ export default defineConfig({
|
||||
target: process.env.PROXY_BACKEND_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/openapi": {
|
||||
target: process.env.PROXY_BACKEND_URL,
|
||||
changeOrigin: true,
|
||||
}
|
||||
} : {}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ class AnalysisRequestPath(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class AnalysisFileRequestQuery(BaseModel):
|
||||
filename: str
|
||||
|
||||
|
||||
class LogsRequestPath(AnalysisRequestPath):
|
||||
log_type: str
|
||||
|
||||
|
||||
@@ -232,6 +232,31 @@ def open_seekable_stream(
|
||||
file.close()
|
||||
|
||||
|
||||
def list_analysis_files(
|
||||
analysis_id: str, s3_config: S3StorageConfigSection
|
||||
) -> list[str]:
|
||||
if not is_s3_enabled(s3_config):
|
||||
base_path = ANALYSES_DIR / analysis_id
|
||||
if not base_path.exists():
|
||||
raise FileNotFoundError
|
||||
return [
|
||||
path.relative_to(base_path).as_posix()
|
||||
for path in base_path.rglob("*")
|
||||
if path.is_file()
|
||||
]
|
||||
# S3 handling
|
||||
s3_client = get_s3_client(s3_config)
|
||||
analysis_key = get_s3_prefix(analysis_id) + "/"
|
||||
response = s3_client.list_objects_v2(Bucket=s3_config.bucket, Prefix=analysis_key)[
|
||||
"Contents"
|
||||
]
|
||||
keys = []
|
||||
for obj in response:
|
||||
object_name = obj["Key"][len(analysis_key) :]
|
||||
keys.append(object_name)
|
||||
return keys
|
||||
|
||||
|
||||
def list_analysis_logs(analysis_id: str, s3_config: S3StorageConfigSection):
|
||||
if not is_s3_enabled(s3_config):
|
||||
base_path = ANALYSES_DIR / analysis_id
|
||||
|
||||
Reference in New Issue
Block a user