From f306374dab6e051d15c56278f5304440a4de7029 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 08:03:33 -0700 Subject: [PATCH 01/12] initial working concept --- api/api.js | 35 +++++++++++++-- swa/package-lock.json | 62 ++++++++++++++++++++++++-- swa/package.json | 17 +++---- swa/src/App.js | 49 +++++++++++--------- swa/src/ResultPage.js | 101 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 228 insertions(+), 36 deletions(-) create mode 100644 swa/src/ResultPage.js diff --git a/api/api.js b/api/api.js index 0fb0370..922fbbf 100755 --- a/api/api.js +++ b/api/api.js @@ -42,6 +42,12 @@ if (!fs.existsSync(uploadsDir)) { fs.mkdirSync(uploadsDir); } +// Esure the results directory exists +const resultsDir = path.join(__dirname, 'results'); +if (!fs.existsSync(resultsDir)) { + fs.mkdirSync(resultsDir); +} + // Configure multer for file uploads const storage = multer.diskStorage({ destination: (req, file, cb) => { @@ -111,14 +117,21 @@ const checkFileHeader = (checkPath) => { return true; }; -// Function to execute analysis commands on local files +// Function to execute analysis commands on local files and save result as JSON const analyzeFile = async (filePath, res) => { logger.info(`Sending target: ${filePath} for analysis`); try { const analysisResult = await Analyze(filePath); - logger.info('Analysis output sent to client'); - res.json(JSON.parse(analysisResult)); + const resultObj = JSON.parse(analysisResult); + // Generate UUID for result + const resultId = uuidv4(); + const resultPath = path.join(resultsDir, `${resultId}.json`); + // Save result to file + await fs.promises.writeFile(resultPath, JSON.stringify(resultObj, null, 2), 'utf8'); + logger.info(`Analysis result saved: ${resultPath}`); + // Return only the UUID to the client + res.json({ uuid: resultId }); } catch (error) { logger.error(`Failed to analyze target: ${error.message}`); res.status(500).send("An error occurred while analyzing the file"); @@ -127,6 +140,22 @@ const analyzeFile = async (filePath, res) => { } }; +// GET endpoint to fetch analysis result by UUID +app.get('/:uuid', async (req, res) => { + const { uuid } = req.params; + const resultPath = path.join(resultsDir, `${uuid}.json`); + try { + if (!fs.existsSync(resultPath)) { + return res.status(404).send('Result not found'); + } + const data = await fs.promises.readFile(resultPath, 'utf8'); + res.type('application/json').send(data); + } catch (err) { + logger.error(`Failed to fetch result for UUID ${uuid}: ${err.message}`); + res.status(500).send('An error occured while retrieving result'); + } +}); + // PUT and POST endpoint to receive .dmp file or URL and analyze it const handleAnalyzeDmp = async (req, res) => { const uploadName = req.uploadName || uuidv4().split('-')[0]; // Retrieve the upload name from the request object diff --git a/swa/package-lock.json b/swa/package-lock.json index 4d447d4..5525942 100644 --- a/swa/package-lock.json +++ b/swa/package-lock.json @@ -14,6 +14,7 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-helmet": "^6.1.0", + "react-router-dom": "^7.9.4", "react-scripts": "5.0.1", "web-vitals": "^5.1.0" } @@ -13907,6 +13908,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz", + "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14801,6 +14849,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -16382,9 +16436,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", "peer": true, "bin": { @@ -16392,7 +16446,7 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { diff --git a/swa/package.json b/swa/package.json index 4b05527..ec99b09 100644 --- a/swa/package.json +++ b/swa/package.json @@ -3,14 +3,15 @@ "version": "0.1.0", "private": true, "dependencies": { - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-helmet": "^6.1.0", - "react-scripts": "5.0.1", - "web-vitals": "^5.1.0" + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-helmet": "^6.1.0", + "react-router-dom": "^7.9.4", + "react-scripts": "5.0.1", + "web-vitals": "^5.1.0" }, "scripts": { "start": "react-scripts start", diff --git a/swa/src/App.js b/swa/src/App.js index d58c29b..eff8dda 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -1,4 +1,6 @@ import React, { useState } from 'react'; +import { BrowserRouter as Router, Route, Routes, useNavigate } from 'react-router-dom'; +import ResultPage from './ResultPage'; import { Helmet } from 'react-helmet'; import Footer from './footer'; @@ -12,6 +14,7 @@ const FileUpload = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [responseData, setResponseData] = useState(''); + const navigate = useNavigate(); const handleFileChange = (e) => { setFile(e.target.files[0]); @@ -23,14 +26,11 @@ const FileUpload = () => { const handleFileUpload = async () => { if (!file) return; - const formData = new FormData(); formData.append('dmpFile', file); - setLoading(true); setError(''); setResponseData(''); - try { const response = await fetch(API_URL, { method: 'PUT', @@ -40,11 +40,15 @@ const FileUpload = () => { setError('Error: File too large. The maximum allowed size is 10MB.'); return; } - const responseText = await response.text(); + const responseJson = await response.json(); if (!response.ok) { - throw new Error(`${responseText}`); + throw new Error(responseJson?.error || 'Upload failed'); + } + if (responseJson.uuid) { + navigate(`/${responseJson.uuid}`); + } else { + setError('No UUID returned from API'); } - setResponseData(responseText); } catch (error) { console.error(error); setError(`Error: ${error.message}`); @@ -55,11 +59,9 @@ const FileUpload = () => { const handleUrlSubmit = async () => { if (!url) return; - setLoading(true); setError(''); setResponseData(''); - try { const response = await fetch(`${API_URL}?url=${encodeURIComponent(url)}`, { method: 'PUT', @@ -68,11 +70,15 @@ const FileUpload = () => { setError('Error: File too large. The maximum allowed size is 10MB.'); return; } - const responseText = await response.text(); + const responseJson = await response.json(); if (!response.ok) { - throw new Error(`${responseText}`); + throw new Error(responseJson?.error || 'Upload failed'); + } + if (responseJson.uuid) { + navigate(`/${responseJson.uuid}`); + } else { + setError('No UUID returned from API'); } - setResponseData(responseText); } catch (error) { console.error(error); setError(`Error: ${error.message}`); @@ -81,15 +87,7 @@ const FileUpload = () => { } }; - // Function to validate JSON Response - const isValidJson = (data) => { - try { - JSON.parse(data); - return true; - } catch (e) { - return false; - } - }; + // Removed unused isValidJson // Function to sort JSON keys const sortJson = (data, order) => { @@ -199,4 +197,13 @@ const FileUpload = () => { ); }; -export default FileUpload; +const App = () => ( + + + } /> + } /> + + +); + +export default App; diff --git a/swa/src/ResultPage.js b/swa/src/ResultPage.js new file mode 100644 index 0000000..222a57a --- /dev/null +++ b/swa/src/ResultPage.js @@ -0,0 +1,101 @@ +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; + +const API_URL = `${process.env.REACT_APP_API_URL}`; + +const isValidJson = (data) => { + try { + JSON.parse(data); + return true; + } catch (e) { + return false; + } +}; + +const sortJson = (data, order) => { + return order.reduce((acc, key) => { + if (data.hasOwnProperty(key)) { + acc[key] = data[key]; + } + return acc; + }, {}); +}; + +const renderJsonToHtml = (data) => { + if (Array.isArray(data)) { + return data.map((item, index) => ( +
+ {renderJsonToHtml(item)} +
+ )); + } + const order = ["dmpName", "dmpInfo", "analysis", "post", "rawContent"]; + const specialKeys = ["rawContent"]; + const sortedData = sortJson(data, order); + const keyValueArray = Object.entries(sortedData).map(([key, value]) => ({ key, value })); + const specialItems = keyValueArray.filter(item => specialKeys.includes(item.key)); + const regularItems = keyValueArray.filter(item => !specialKeys.includes(item.key)); + const regularRender = regularItems.map((item, index) => ( + +

{item.key}

+
{item.value}
+
+ )); + const specialRender = specialItems.map((item, index) => ( +
+
+ Raw results +
{item.value}
+
+
+ )); + return ( + <> + {regularRender} + {specialRender} + + ); +}; + +const ResultPage = () => { + const { uuid } = useParams(); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [responseData, setResponseData] = useState(''); + + useEffect(() => { + const fetchResult = async () => { + setLoading(true); + setError(''); + setResponseData(''); + try { + const res = await fetch(`${API_URL}/${uuid}`); + if (!res.ok) { + throw new Error(`Fetch failed: ${res.statusText}`); + } + const data = await res.text(); + setResponseData(data); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + fetchResult(); + }, [uuid]); + + return ( +
+

Result for UUID: {uuid}

+ {loading &&

Loading...

} + {error &&

{error}

} + {responseData && ( + isValidJson(responseData) + ? <>{renderJsonToHtml(JSON.parse(responseData))} + :

Error: Invalid JSON received from backend.

+ )} +
+ ); +}; + +export default ResultPage; From 096118322c42f432262dd99075585285a618a6ad Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 08:11:17 -0700 Subject: [PATCH 02/12] update class to classname --- swa/src/App.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/swa/src/App.js b/swa/src/App.js index eff8dda..d2e263d 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -106,7 +106,7 @@ const FileUpload = () => { // No clue what this does, but everything is rendered inside it if (Array.isArray(data)) { return data.map((item, index) => ( -
+
{renderJsonToHtml(item)}
)); @@ -133,10 +133,10 @@ const FileUpload = () => { // Render the regular items const regularRender = regularItems.map((item, index) => ( <> -

+

{item.key}

-
+
{item.value}
@@ -147,7 +147,7 @@ const FileUpload = () => {
Raw results -
{item.value}
+
{item.value}
)); @@ -170,14 +170,14 @@ const FileUpload = () => { -
-
+
+
-
+
- {!error && !responseData &&

{loading ? 'Processing...' : 'Upload your .dmp file or a .zip file containing multiple .dmp files directly or via a direct link.'}

} - {error &&

{error}

} + {!error && !responseData &&

{loading ? 'Processing...' : 'Upload your .dmp file or a .zip file containing multiple .dmp files directly or via a direct link.'}

} + {error &&

{error}

} {responseData && ( <>{renderJsonToHtml(JSON.parse(responseData))} )} From 1b7b67a598292bb7d552c76a747cc31413cf2550 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 08:19:13 -0700 Subject: [PATCH 03/12] reframe site and duplicate items from main page --- swa/src/ResultPage.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/swa/src/ResultPage.js b/swa/src/ResultPage.js index 222a57a..b1b7704 100644 --- a/swa/src/ResultPage.js +++ b/swa/src/ResultPage.js @@ -1,6 +1,9 @@ import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; +import { Helmet } from 'react-helmet'; +// Retrieve the site name and API URL from environment variables +const SITE_NAME = process.env.REACT_APP_SITE_NAME; const API_URL = `${process.env.REACT_APP_API_URL}`; const isValidJson = (data) => { @@ -85,8 +88,14 @@ const ResultPage = () => { }, [uuid]); return ( -
-

Result for UUID: {uuid}

+
+ + {SITE_NAME} + +
+ {loading &&

Loading...

} {error &&

{error}

} {responseData && ( @@ -94,6 +103,7 @@ const ResultPage = () => { ? <>{renderJsonToHtml(JSON.parse(responseData))} :

Error: Invalid JSON received from backend.

)} +
); }; From 2c7949e583e43f7b6603f52118b0269252d65bf5 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 08:25:34 -0700 Subject: [PATCH 04/12] handle errors properly, it assumed all responses were JSON results --- swa/src/App.js | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/swa/src/App.js b/swa/src/App.js index d2e263d..6f06f68 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -40,11 +40,21 @@ const FileUpload = () => { setError('Error: File too large. The maximum allowed size is 10MB.'); return; } - const responseJson = await response.json(); + const text = await response.text(); + let responseJson = null; + try { + responseJson = JSON.parse(text); + } catch (e) { + if (!response.ok) { + setError(text); + return; + } + } if (!response.ok) { - throw new Error(responseJson?.error || 'Upload failed'); + setError(responseJson?.error || 'Upload failed'); + return; } - if (responseJson.uuid) { + if (responseJson && responseJson.uuid) { navigate(`/${responseJson.uuid}`); } else { setError('No UUID returned from API'); @@ -70,11 +80,21 @@ const FileUpload = () => { setError('Error: File too large. The maximum allowed size is 10MB.'); return; } - const responseJson = await response.json(); + const text = await response.text(); + let responseJson = null; + try { + responseJson = JSON.parse(text); + } catch (e) { + if (!response.ok) { + setError(text); + return; + } + } if (!response.ok) { - throw new Error(responseJson?.error || 'Upload failed'); + setError(responseJson?.error || 'Upload failed'); + return; } - if (responseJson.uuid) { + if (responseJson && responseJson.uuid) { navigate(`/${responseJson.uuid}`); } else { setError('No UUID returned from API'); From 3fe19283e94f5ede70b6fe07a51794cfff429697 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 08:56:01 -0700 Subject: [PATCH 05/12] adding href to get back to main page --- swa/src/App.js | 4 +++- swa/src/ResultPage.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/swa/src/App.js b/swa/src/App.js index 6f06f68..e3372c1 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -188,7 +188,9 @@ const FileUpload = () => {
diff --git a/swa/src/ResultPage.js b/swa/src/ResultPage.js index b1b7704..fd3265e 100644 --- a/swa/src/ResultPage.js +++ b/swa/src/ResultPage.js @@ -94,7 +94,9 @@ const ResultPage = () => {
{loading &&

Loading...

} {error &&

{error}

} From bfb2a621efb987ec3b4ce69cacba56625a27c24e Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 09:11:36 -0700 Subject: [PATCH 06/12] fixes for DS --- swa/src/ResultPage.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/swa/src/ResultPage.js b/swa/src/ResultPage.js index fd3265e..074812a 100644 --- a/swa/src/ResultPage.js +++ b/swa/src/ResultPage.js @@ -17,7 +17,7 @@ const isValidJson = (data) => { const sortJson = (data, order) => { return order.reduce((acc, key) => { - if (data.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(data, key)) { acc[key] = data[key]; } return acc; @@ -38,14 +38,14 @@ const renderJsonToHtml = (data) => { const keyValueArray = Object.entries(sortedData).map(([key, value]) => ({ key, value })); const specialItems = keyValueArray.filter(item => specialKeys.includes(item.key)); const regularItems = keyValueArray.filter(item => !specialKeys.includes(item.key)); - const regularRender = regularItems.map((item, index) => ( + const regularRender = regularItems.map((item) => (

{item.key}

{item.value}
)); - const specialRender = specialItems.map((item, index) => ( -
+ const specialRender = specialItems.map((item) => ( +
Raw results
{item.value}
From 3bf3e3df0d0ad3d6e5aad6c9fd819b1606d4027f Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sat, 18 Oct 2025 09:47:50 -0700 Subject: [PATCH 07/12] Add cleanup task for old results and a volume to retain results --- README.md | 6 ++++++ api/Dockerfile | 3 +++ api/api.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/README.md b/README.md index 0337750..c8cf517 100755 --- a/README.md +++ b/README.md @@ -58,6 +58,12 @@ Once launched use `REACT_APP_API_URL=http://localhost:3000` in your `.env` and l ### Deployment Using an nginx reverse proxy to apply CORS (don't run ENABLE_CORS=true in prod) and SSL is the best method, the nginx default max client upload size of 10MB is fine for this appliction. +You want to declare a volume for `C:\app\results`, an example command for deployment is below. + +```bash +docker run -d --restart unless-stopped --name webdbg-api -v webdbg-results:C:\app\results -p 3000:3000 ghcr.io/r-techsupport/webdbg-api:latest +``` + ### PUT endpoint usage With a file ```bash diff --git a/api/Dockerfile b/api/Dockerfile index f2cc3a6..0415284 100755 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -16,6 +16,9 @@ COPY . . # Install application dependencies RUN npm install +# Declare a volume for results so they persist +VOLUME C:\\app\\results + # Expose the application port EXPOSE 3000 diff --git a/api/api.js b/api/api.js index 922fbbf..c637dfe 100755 --- a/api/api.js +++ b/api/api.js @@ -335,6 +335,38 @@ app.get('/', (req, res) => { }); }); +// Cleanup function to delete result files older than 7 days +const cleanupOldResults = async () => { + const now = Date.now(); + const weekMs = 7 * 24 * 60 * 60 * 1000; + try { + const files = await fs.promises.readdir(resultsDir); + let deletedCount = 0; + for (const file of files) { + if (file.endsWith('.json')) { + const filePath = path.join(resultsDir, file); + const stat = await fs.promises.stat(filePath); + if (now - stat.mtimeMs > weekMs) { + await fs.promises.unlink(filePath); + logger.info(`Deleted old result file: ${filePath}`); + deletedCount++; + } + } + } + if (deletedCount === 0) { + logger.info('Cleanup ran: no old result files deleted.'); + } + } catch (err) { + logger.error(`Cleanup error: ${err.message}`); + } +}; + +// Cleanup middleware: run after every request +app.use(async (req, res, next) => { + await cleanupOldResults(); + next(); +}); + // Centralized error handling middleware app.use((err, req, res, next) => { if (err instanceof multer.MulterError) { From 3bbc6b5b765dec86665a014590e7da186f5b5a72 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sun, 19 Oct 2025 04:52:50 -0700 Subject: [PATCH 08/12] shorten UUID and add logging for result requests --- api/api.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/api.js b/api/api.js index c637dfe..c03f4db 100755 --- a/api/api.js +++ b/api/api.js @@ -124,8 +124,8 @@ const analyzeFile = async (filePath, res) => { try { const analysisResult = await Analyze(filePath); const resultObj = JSON.parse(analysisResult); - // Generate UUID for result - const resultId = uuidv4(); + // Generate short result ID (first octet of UUID) + const resultId = uuidv4().split('-')[0]; const resultPath = path.join(resultsDir, `${resultId}.json`); // Save result to file await fs.promises.writeFile(resultPath, JSON.stringify(resultObj, null, 2), 'utf8'); @@ -144,11 +144,14 @@ const analyzeFile = async (filePath, res) => { app.get('/:uuid', async (req, res) => { const { uuid } = req.params; const resultPath = path.join(resultsDir, `${uuid}.json`); + logger.info(`Result requested: ${resultPath}`); try { if (!fs.existsSync(resultPath)) { + logger.info(`Result not found: ${resultPath}`); return res.status(404).send('Result not found'); } const data = await fs.promises.readFile(resultPath, 'utf8'); + logger.info(`Result served: ${resultPath}`); res.type('application/json').send(data); } catch (err) { logger.error(`Failed to fetch result for UUID ${uuid}: ${err.message}`); From 43d98a992f7704f7960611274378a4cf8ecea898 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sun, 19 Oct 2025 05:25:01 -0700 Subject: [PATCH 09/12] replace index with a stable key --- swa/src/App.js | 40 ++++++++++++++++++++++------------------ swa/src/ResultPage.js | 14 +++++++++----- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/swa/src/App.js b/swa/src/App.js index e3372c1..9deb98c 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -125,11 +125,15 @@ const FileUpload = () => { // No clue what this does, but everything is rendered inside it if (Array.isArray(data)) { - return data.map((item, index) => ( -
- {renderJsonToHtml(item)} -
- )); + return data.map((item) => { + // Use a stable key: stringified item or item.key if available + let key = (item && typeof item === 'object' && item.key) ? item.key : JSON.stringify(item); + return ( +
+ {renderJsonToHtml(item)} +
+ ); + }); } // Define the key order to display in @@ -152,23 +156,23 @@ const FileUpload = () => { // Render the regular items const regularRender = regularItems.map((item, index) => ( - <> -

- {item.key} -

-
- {item.value} -
- + +

+ {item.key} +

+
+ {item.value} +
+
)); // Render the special items with their own method const specialRender = specialItems.map((item, index) => ( -
-
- Raw results -
{item.value}
-
+
+
+ Raw results +
{item.value}
+
)); diff --git a/swa/src/ResultPage.js b/swa/src/ResultPage.js index 074812a..81ab8fb 100644 --- a/swa/src/ResultPage.js +++ b/swa/src/ResultPage.js @@ -26,11 +26,15 @@ const sortJson = (data, order) => { const renderJsonToHtml = (data) => { if (Array.isArray(data)) { - return data.map((item, index) => ( -
- {renderJsonToHtml(item)} -
- )); + return data.map((item) => { + // Use a stable key: item.key if available, otherwise stringified item + let key = (item && typeof item === 'object' && item.key) ? item.key : JSON.stringify(item); + return ( +
+ {renderJsonToHtml(item)} +
+ ); + }); } const order = ["dmpName", "dmpInfo", "analysis", "post", "rawContent"]; const specialKeys = ["rawContent"]; From 3f7b1706d495ebbc9a2224b4c806b76c6531c4bd Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sun, 19 Oct 2025 05:31:53 -0700 Subject: [PATCH 10/12] update usage to have retrieve --- api/USAGE.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/USAGE.md b/api/USAGE.md index b0d98da..454bea6 100755 --- a/api/USAGE.md +++ b/api/USAGE.md @@ -1,6 +1,6 @@ # BSOD-API -A basic API to injest `.dmp` files and return analyzed text. +A basic API to injest `.dmp` files and return a UUID in JSON associated with the results. Fetching the UUID will return the associated result in JSON. ## Usage With a file @@ -11,4 +11,9 @@ curl.exe -X PUT http://localhost:3000/analyze-dmp -F "dmpFile=@path/to/test.dmp" With a URL ```bash curl -X PUT http://localhost:3000/analyze-dmp -F "url=http://example.com/file.dmp" +``` + +Retrieve result JSON +```bash +curl -X GET http://chakotay.dev0.sh:3001/ ``` \ No newline at end of file From efb2a37ecf08aeb95fd341b5f807671e0da932a5 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sun, 19 Oct 2025 05:36:27 -0700 Subject: [PATCH 11/12] let to const for DS --- swa/src/App.js | 2 +- swa/src/ResultPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swa/src/App.js b/swa/src/App.js index 9deb98c..d730ec0 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -127,7 +127,7 @@ const FileUpload = () => { if (Array.isArray(data)) { return data.map((item) => { // Use a stable key: stringified item or item.key if available - let key = (item && typeof item === 'object' && item.key) ? item.key : JSON.stringify(item); + const key = (item && typeof item === 'object' && item.key) ? item.key : JSON.stringify(item); return (
{renderJsonToHtml(item)} diff --git a/swa/src/ResultPage.js b/swa/src/ResultPage.js index 81ab8fb..57f044a 100644 --- a/swa/src/ResultPage.js +++ b/swa/src/ResultPage.js @@ -28,7 +28,7 @@ const renderJsonToHtml = (data) => { if (Array.isArray(data)) { return data.map((item) => { // Use a stable key: item.key if available, otherwise stringified item - let key = (item && typeof item === 'object' && item.key) ? item.key : JSON.stringify(item); + const key = (item && typeof item === 'object' && item.key) ? item.key : JSON.stringify(item); return (
{renderJsonToHtml(item)} From 7a857860fda71cc22900dcfe5d0c10f816bac924 Mon Sep 17 00:00:00 2001 From: PipeItToDevNull Date: Sun, 19 Oct 2025 05:40:35 -0700 Subject: [PATCH 12/12] remove unused vars --- swa/src/App.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swa/src/App.js b/swa/src/App.js index d730ec0..040a4fe 100644 --- a/swa/src/App.js +++ b/swa/src/App.js @@ -155,7 +155,7 @@ const FileUpload = () => { const regularItems = keyValueArray.filter(item => !specialKeys.includes(item.key)); // Render the regular items - const regularRender = regularItems.map((item, index) => ( + const regularRender = regularItems.map((item) => (

{item.key} @@ -167,7 +167,7 @@ const FileUpload = () => { )); // Render the special items with their own method - const specialRender = specialItems.map((item, index) => ( + const specialRender = specialItems.map((item) => (
Raw results