Repository: google/turbinia (language: python)

Issue:
Web UI: Show task report data 
### What would this feature improve or what problem would it solve?

The report data contains useful information about the Turbinia run so it would be good to see that in the UI.

### What is the feature you are proposing?

Show the report data for the task in the UI in the detailed view.  It could even have its own "Report" section at the same level as "Processing Details".

### What alternatives have you considered?

_No response_


Discussion / hints:
Just to differentiate this FR from the other report related one: this FR is to show report data at the task level/view and the other one is to show the entire report.

Fix patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..d229c98be
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+  "python.analysis.extraPaths": [
+    "./turbinia/api/client",
+    "./turbinia/api/cli"
+  ]
+}
\ No newline at end of file
diff --git a/turbinia/api/routes/request.py b/turbinia/api/routes/request.py
index 3a67de618..39933c89c 100644
--- a/turbinia/api/routes/request.py
+++ b/turbinia/api/routes/request.py
@@ -60,7 +60,7 @@ async def get_requests_summary(request: Request):
         detail='Error retrieving requests summary') from exception
 
 
-@router.get('/report')
+@router.get('/report/{request_id}')
 async def get_request_report(request: Request, request_id: str):
   """Retrieves the MarkDown report of a Turbinia request.
 
diff --git a/turbinia/api/routes/task.py b/turbinia/api/routes/task.py
index c6bbbd2e5..ce9657267 100644
--- a/turbinia/api/routes/task.py
+++ b/turbinia/api/routes/task.py
@@ -20,7 +20,7 @@
 from collections import OrderedDict
 
 from fastapi import HTTPException, APIRouter
-from fastapi.responses import JSONResponse
+from fastapi.responses import JSONResponse, PlainTextResponse
 from fastapi.requests import Request
 from fastapi.encoders import jsonable_encoder
 
@@ -29,6 +29,8 @@
 from turbinia import state_manager
 from turbinia.api.models import workers_status
 from turbinia.api.models import tasks_statistics
+from turbinia.api.models import request_status
+from turbinia.api.cli.turbinia_client.helpers.formatter import TaskMarkdownReport
 
 log = logging.getLogger(__name__)
 
@@ -100,3 +102,26 @@ async def get_task_status(request: Request, task_id: str):
     raise HTTPException(
         status_code=500,
         detail='Error retrieving task information') from exception
+
+
+@router.get('/report/{task_id}')
+async def get_task_report(request: Request, task_id: str):
+  """Retrieves the MarkDown report of a Turbinia task.
+
+  Raises:
+    HTTPException: if another exception is caught.
+  """
+  try:
+    task_data = state_manager.get_state_manager().get_task(task_id=task_id)
+    if not task_data:
+      raise HTTPException(status_code=404, detail='Task not found.')
+    markdownreport = TaskMarkdownReport(
+        request_data=task_data).generate_markdown()
+
+    return PlainTextResponse(content=markdownreport, status_code=200)
+  except (json.JSONDecodeError, TypeError, ValueError, AttributeError,
+          ValidationError) as exception:
+    log.error(f'Error retrieving markdown report: {exception!s}', exc_info=True)
+    raise HTTPException(
+        status_code=500,
+        detail='Error retrieving markdown report') from exception
diff --git a/web/package-lock.json b/web/package-lock.json
index 7eed94b4c..67928d672 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -23,10 +23,29 @@
         "vite-plugin-vuetify": "^2.0.3"
       }
     },
-    "node_modules/@babel/parser": {
+    "node_modules/@babel/helper-string-parser": {
       "version": "7.24.8",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz",
-      "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
+      "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.24.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+      "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.25.3",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
+      "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
+      "dependencies": {
+        "@babel/types": "^7.25.2"
+      },
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -34,6 +53,19 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@babel/types": {
+      "version": "7.25.2",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
+      "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.24.8",
+        "@babel/helper-validator-identifier": "^7.24.7",
+        "to-fast-properties": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
     "node_modules/@esbuild/aix-ppc64": {
       "version": "0.21.5",
       "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -425,9 +457,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",
-      "integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.2.tgz",
+      "integrity": "sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==",
       "cpu": [
         "arm"
       ],
@@ -437,9 +469,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz",
-      "integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.2.tgz",
+      "integrity": "sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==",
       "cpu": [
         "arm64"
       ],
@@ -449,9 +481,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz",
-      "integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.2.tgz",
+      "integrity": "sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==",
       "cpu": [
         "arm64"
       ],
@@ -461,9 +493,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz",
-      "integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.2.tgz",
+      "integrity": "sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==",
       "cpu": [
         "x64"
       ],
@@ -473,9 +505,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz",
-      "integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.2.tgz",
+      "integrity": "sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==",
       "cpu": [
         "arm"
       ],
@@ -485,9 +517,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz",
-      "integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.2.tgz",
+      "integrity": "sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==",
       "cpu": [
         "arm"
       ],
@@ -497,9 +529,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz",
-      "integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.2.tgz",
+      "integrity": "sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==",
       "cpu": [
         "arm64"
       ],
@@ -509,9 +541,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz",
-      "integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.2.tgz",
+      "integrity": "sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==",
       "cpu": [
         "arm64"
       ],
@@ -521,9 +553,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz",
-      "integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.2.tgz",
+      "integrity": "sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==",
       "cpu": [
         "ppc64"
       ],
@@ -533,9 +565,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz",
-      "integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.2.tgz",
+      "integrity": "sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==",
       "cpu": [
         "riscv64"
       ],
@@ -545,9 +577,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz",
-      "integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.2.tgz",
+      "integrity": "sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==",
       "cpu": [
         "s390x"
       ],
@@ -557,9 +589,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz",
-      "integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.2.tgz",
+      "integrity": "sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==",
       "cpu": [
         "x64"
       ],
@@ -569,9 +601,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz",
-      "integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.2.tgz",
+      "integrity": "sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==",
       "cpu": [
         "x64"
       ],
@@ -581,9 +613,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz",
-      "integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.2.tgz",
+      "integrity": "sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==",
       "cpu": [
         "arm64"
       ],
@@ -593,9 +625,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz",
-      "integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.2.tgz",
+      "integrity": "sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==",
       "cpu": [
         "ia32"
       ],
@@ -605,9 +637,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz",
-      "integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.2.tgz",
+      "integrity": "sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==",
       "cpu": [
         "x64"
       ],
@@ -623,9 +655,9 @@
       "devOptional": true
     },
     "node_modules/@vitejs/plugin-vue": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.0.tgz",
-      "integrity": "sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz",
+      "integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==",
       "dev": true,
       "engines": {
         "node": "^18.0.0 || >=20.0.0"
@@ -636,95 +668,95 @@
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.34.tgz",
-      "integrity": "sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.35.tgz",
+      "integrity": "sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==",
       "dependencies": {
         "@babel/parser": "^7.24.7",
-        "@vue/shared": "3.4.34",
+        "@vue/shared": "3.4.35",
         "entities": "^4.5.0",
         "estree-walker": "^2.0.2",
         "source-map-js": "^1.2.0"
       }
     },
     "node_modules/@vue/compiler-dom": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.34.tgz",
-      "integrity": "sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.35.tgz",
+      "integrity": "sha512-pWIZRL76/oE/VMhdv/ovZfmuooEni6JPG1BFe7oLk5DZRo/ImydXijoZl/4kh2406boRQ7lxTYzbZEEXEhj9NQ==",
       "dependencies": {
-        "@vue/compiler-core": "3.4.34",
-        "@vue/shared": "3.4.34"
+        "@vue/compiler-core": "3.4.35",
+        "@vue/shared": "3.4.35"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.34.tgz",
-      "integrity": "sha512-x6lm0UrM03jjDXTPZgD9Ad8bIVD1ifWNit2EaWQIZB5CULr46+FbLQ5RpK7AXtDHGjx9rmvC7QRCTjsiGkAwRw==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.35.tgz",
+      "integrity": "sha512-xacnRS/h/FCsjsMfxBkzjoNxyxEyKyZfBch/P4vkLRvYJwe5ChXmZZrj8Dsed/752H2Q3JE8kYu9Uyha9J6PgA==",
       "dependencies": {
         "@babel/parser": "^7.24.7",
-        "@vue/compiler-core": "3.4.34",
-        "@vue/compiler-dom": "3.4.34",
-        "@vue/compiler-ssr": "3.4.34",
-        "@vue/shared": "3.4.34",
+        "@vue/compiler-core": "3.4.35",
+        "@vue/compiler-dom": "3.4.35",
+        "@vue/compiler-ssr": "3.4.35",
+        "@vue/shared": "3.4.35",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.30.10",
-        "postcss": "^8.4.39",
+        "postcss": "^8.4.40",
         "source-map-js": "^1.2.0"
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.34.tgz",
-      "integrity": "sha512-8TDBcLaTrFm5rnF+Qm4BlliaopJgqJ28Nsrc80qazynm5aJO+Emu7y0RWw34L8dNnTRdcVBpWzJxhGYzsoVu4g==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.35.tgz",
+      "integrity": "sha512-7iynB+0KB1AAJKk/biENTV5cRGHRdbdaD7Mx3nWcm1W8bVD6QmnH3B4AHhQQ1qZHhqFwzEzMwiytXm3PX1e60A==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.34",
-        "@vue/shared": "3.4.34"
+        "@vue/compiler-dom": "3.4.35",
+        "@vue/shared": "3.4.35"
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.34.tgz",
-      "integrity": "sha512-ua+Lo+wBRlBEX9TtgPOShE2JwIO7p6BTZ7t1KZVPoaBRfqbC7N3c8Mpzicx173fXxx5VXeU6ykiHo7WgLzJQDA==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.35.tgz",
+      "integrity": "sha512-Ggtz7ZZHakriKioveJtPlStYardwQH6VCs9V13/4qjHSQb/teE30LVJNrbBVs4+aoYGtTQKJbTe4CWGxVZrvEw==",
       "dependencies": {
-        "@vue/shared": "3.4.34"
+        "@vue/shared": "3.4.35"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.34.tgz",
-      "integrity": "sha512-PXhkiRPwcPGJ1BnyBZFI96GfInCVskd0HPNIAZn7i3YOmLbtbTZpB7/kDTwC1W7IqdGPkTVC63IS7J2nZs4Ebg==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.35.tgz",
+      "integrity": "sha512-D+BAjFoWwT5wtITpSxwqfWZiBClhBbR+bm0VQlWYFOadUUXFo+5wbe9ErXhLvwguPiLZdEF13QAWi2vP3ZD5tA==",
       "dependencies": {
-        "@vue/reactivity": "3.4.34",
-        "@vue/shared": "3.4.34"
+        "@vue/reactivity": "3.4.35",
+        "@vue/shared": "3.4.35"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.34.tgz",
-      "integrity": "sha512-dXqIe+RqFAK2Euak4UsvbIupalrhc67OuQKpD7HJ3W2fv8jlqvI7szfBCsAEcE8o/wyNpkloxB6J8viuF/E3gw==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.35.tgz",
+      "integrity": "sha512-yGOlbos+MVhlS5NWBF2HDNgblG8e2MY3+GigHEyR/dREAluvI5tuUUgie3/9XeqhPE4LF0i2wjlduh5thnfOqw==",
       "dependencies": {
-        "@vue/reactivity": "3.4.34",
-        "@vue/runtime-core": "3.4.34",
-        "@vue/shared": "3.4.34",
+        "@vue/reactivity": "3.4.35",
+        "@vue/runtime-core": "3.4.35",
+        "@vue/shared": "3.4.35",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.34.tgz",
-      "integrity": "sha512-GeyEUfMVRZMD/mZcNONEqg7MiU10QQ1DB3O/Qr6+8uXpbwdlmVgQ5Qs1/ZUAFX1X2UUtqMoGrDRbxdWfOJFT7Q==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.35.tgz",
+      "integrity": "sha512-iZ0e/u9mRE4T8tNhlo0tbA+gzVkgv8r5BX6s1kRbOZqfpq14qoIvCZ5gIgraOmYkMYrSEZgkkojFPr+Nyq/Mnw==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.34",
-        "@vue/shared": "3.4.34"
+        "@vue/compiler-ssr": "3.4.35",
+        "@vue/shared": "3.4.35"
       },
       "peerDependencies": {
-        "vue": "3.4.34"
+        "vue": "3.4.35"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.34.tgz",
-      "integrity": "sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A=="
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.35.tgz",
+      "integrity": "sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ=="
     },
     "node_modules/@vuetify/loader-shared": {
       "version": "2.0.3",
@@ -770,9 +802,9 @@
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "node_modules/axios": {
-      "version": "1.7.2",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
-      "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
+      "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
       "dependencies": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
@@ -844,9 +876,9 @@
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
     },
     "node_modules/debug": {
-      "version": "4.3.5",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
-      "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
       "devOptional": true,
       "dependencies": {
         "ms": "2.1.2"
@@ -1064,11 +1096,11 @@
       }
     },
     "node_modules/magic-string": {
-      "version": "0.30.10",
-      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
-      "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
+      "version": "0.30.11",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+      "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
       "dependencies": {
-        "@jridgewell/sourcemap-codec": "^1.4.15"
+        "@jridgewell/sourcemap-codec": "^1.5.0"
       }
     },
     "node_modules/marked": {
@@ -1173,9 +1205,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.39",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
-      "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
+      "version": "8.4.40",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
+      "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
       "funding": [
         {
           "type": "opencollective",
@@ -1247,9 +1279,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "4.19.0",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.0.tgz",
-      "integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==",
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.2.tgz",
+      "integrity": "sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==",
       "devOptional": true,
       "dependencies": {
         "@types/estree": "1.0.5"
@@ -1262,22 +1294,22 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.19.0",
-        "@rollup/rollup-android-arm64": "4.19.0",
-        "@rollup/rollup-darwin-arm64": "4.19.0",
-        "@rollup/rollup-darwin-x64": "4.19.0",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.19.0",
-        "@rollup/rollup-linux-arm-musleabihf": "4.19.0",
-        "@rollup/rollup-linux-arm64-gnu": "4.19.0",
-        "@rollup/rollup-linux-arm64-musl": "4.19.0",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.19.0",
-        "@rollup/rollup-linux-riscv64-gnu": "4.19.0",
-        "@rollup/rollup-linux-s390x-gnu": "4.19.0",
-        "@rollup/rollup-linux-x64-gnu": "4.19.0",
-        "@rollup/rollup-linux-x64-musl": "4.19.0",
-        "@rollup/rollup-win32-arm64-msvc": "4.19.0",
-        "@rollup/rollup-win32-ia32-msvc": "4.19.0",
-        "@rollup/rollup-win32-x64-msvc": "4.19.0",
+        "@rollup/rollup-android-arm-eabi": "4.19.2",
+        "@rollup/rollup-android-arm64": "4.19.2",
+        "@rollup/rollup-darwin-arm64": "4.19.2",
+        "@rollup/rollup-darwin-x64": "4.19.2",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.19.2",
+        "@rollup/rollup-linux-arm-musleabihf": "4.19.2",
+        "@rollup/rollup-linux-arm64-gnu": "4.19.2",
+        "@rollup/rollup-linux-arm64-musl": "4.19.2",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.19.2",
+        "@rollup/rollup-linux-riscv64-gnu": "4.19.2",
+        "@rollup/rollup-linux-s390x-gnu": "4.19.2",
+        "@rollup/rollup-linux-x64-gnu": "4.19.2",
+        "@rollup/rollup-linux-x64-musl": "4.19.2",
+        "@rollup/rollup-win32-arm64-msvc": "4.19.2",
+        "@rollup/rollup-win32-ia32-msvc": "4.19.2",
+        "@rollup/rollup-win32-x64-msvc": "4.19.2",
         "fsevents": "~2.3.2"
       }
     },
@@ -1323,6 +1355,14 @@
         }
       ]
     },
+    "node_modules/to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1380,9 +1420,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.3.4",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz",
-      "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==",
+      "version": "5.3.5",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
+      "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
       "devOptional": true,
       "dependencies": {
         "esbuild": "^0.21.3",
@@ -1454,15 +1494,15 @@
       }
     },
     "node_modules/vue": {
-      "version": "3.4.34",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.34.tgz",
-      "integrity": "sha512-VZze05HWlA3ItreQ/ka7Sx7PoD0/3St8FEiSlSTVgb6l4hL+RjtP2/8g5WQBzZgyf8WG2f+g1bXzC7zggLhAJA==",
+      "version": "3.4.35",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.35.tgz",
+      "integrity": "sha512-+fl/GLmI4GPileHftVlCdB7fUL4aziPcqTudpTGXCT8s+iZWuOCeNEB5haX6Uz2IpRrbEXOgIFbe+XciCuGbNQ==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.34",
-        "@vue/compiler-sfc": "3.4.34",
-        "@vue/runtime-dom": "3.4.34",
-        "@vue/server-renderer": "3.4.34",
-        "@vue/shared": "3.4.34"
+        "@vue/compiler-dom": "3.4.35",
+        "@vue/compiler-sfc": "3.4.35",
+        "@vue/runtime-dom": "3.4.35",
+        "@vue/server-renderer": "3.4.35",
+        "@vue/shared": "3.4.35"
       },
       "peerDependencies": {
         "typescript": "*"
@@ -1474,9 +1514,9 @@
       }
     },
     "node_modules/vuetify": {
-      "version": "3.6.13",
-      "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.6.13.tgz",
-      "integrity": "sha512-Gz7jxXAkmff2m6CM0EUWOo/72TM322/3I6aDna++k1nPOW1/hNx4td1MZG4u75fzdn3r+uIe0dbF7SWuhu6DWA==",
+      "version": "3.6.14",
+      "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.6.14.tgz",
+      "integrity": "sha512-iSa3CgdTEt/7B0aGDmkBARe8rxDDycEYHu1zNtOf1Xpvs/Tv7Ql5yHGqM2XCY0h7SL8Dme39pJIovzg3q4JLbQ==",
       "engines": {
         "node": "^12.20 || >=14.13"
       },
diff --git a/web/src/components/RequestDetails.vue b/web/src/components/RequestDetails.vue
index d6a004044..b5daf6a42 100644
--- a/web/src/components/RequestDetails.vue
+++ b/web/src/components/RequestDetails.vue
@@ -12,155 +12,157 @@ limitations under the License.
 -->
 
 <template>
-    <div>
-      <v-card-title>
-        Request Details
-        <v-snackbar timeout="5000" color="primary" location="top" height="55">
-          Request output is <strong>downloading in the background</strong>, please wait
-          <v-progress-circular color="white" indeterminate></v-progress-circular>
-          <template v-slot:activator="{ props: snackbar }">
-            <v-tooltip location="top" text="Download request output">
-              <template v-slot:activator="{ props: tooltip }">
-                <v-btn icon="mdi-folder-arrow-down-outline" variant="text" v-bind="mergeProps(snackbar, tooltip)"
-                  @click="getRequestOutput(requestDetails.request_id)">
-                </v-btn>
-              </template>
-            </v-tooltip>
+  <div>
+    <v-card-title>
+      Request Details
+      <v-snackbar timeout="5000" color="primary" location="top" height="55">
+        Request output is <strong>downloading in the background</strong>, please wait
+        <v-progress-circular color="white" indeterminate></v-progress-circular>
+        <template v-slot:activator="{ props: snackbar }">
+          <v-tooltip location="top" text="Download request output">
+            <template v-slot:activator="{ props: tooltip }">
+              <v-btn icon="mdi-folder-arrow-down-outline" variant="text" v-bind="mergeProps(snackbar, tooltip)"
+                @click="getRequestOutput(requestDetails.request_id)">
+              </v-btn>
+            </template>
+          </v-tooltip>
+        </template>
+      </v-snackbar>
+      <v-tooltip location="top" text="View request report">
+        <template v-slot:activator="{ props: tooltip }">
+          <v-btn icon="mdi-file-document-outline" variant="text" v-bind="tooltip"
+            @click="getMarkdownReport(requestDetails.request_id)">
+          </v-btn>
+        </template>
+      </v-tooltip>
+    </v-card-title>
+    <v-alert v-if="requestDetails.status === 'successful'" type="success" prominent>
+      Request <strong>{{ requestDetails.request_id }}</strong> completed successfully.
+    </v-alert>
+    <v-alert v-else-if="requestDetails.status === 'running'" type="info" prominent>
+      Request <strong>{{ requestDetails.request_id }}</strong> has <strong>{{ requestDetails.task_count -
+          requestDetails.successful_tasks - requestDetails.failed_tasks }}</strong> Tasks remaining.
+    </v-alert>
+    <v-alert v-else-if="requestDetails.status === 'completed_with_errors'" type="warning" prominent>
+      Request <strong>{{ requestDetails.request_id }}</strong> completed with <strong>{{ requestDetails.failed_tasks
+        }}</strong> failed Tasks.
+    </v-alert>
+    <v-alert v-else type="error" prominent>
+      Request <strong>{{ requestDetails.request_id }}</strong> was not successful.
+    </v-alert>
+    <v-card>
+      <v-list density="compact" v-model:opened="openGroups">
+        <v-list-group value="ids">
+          <template v-slot:activator="{ props }">
+            <v-list-item v-bind="props" title="Associated IDs"></v-list-item>
           </template>
-        </v-snackbar>
-        <v-tooltip location="top" text="View request report">
-          <template v-slot:activator="{ props: tooltip }">
-            <v-btn icon="mdi-file-document-outline" variant="text" v-bind="tooltip"
-              @click="getMarkdownReport(requestDetails.request_id)">
-            </v-btn>
+          <v-list-item title="Request ID:">
+            {{ requestDetails.request_id }}
+          </v-list-item>
+          <v-list-item title="Evidence ID:">
+            {{ requestDetails.evidence_id }}
+          </v-list-item>
+          <v-list-item title="Reason:">
+            {{ requestDetails.reason }}
+          </v-list-item>
+        </v-list-group>
+        <v-list-group value="details">
+          <template v-slot:activator="{ props }">
+            <v-list-item v-bind="props" title="Processing Details"></v-list-item>
           </template>
-        </v-tooltip>
-      </v-card-title>
-      <v-alert v-if="requestDetails.status === 'successful'" type="success" prominent>
-          Request <strong>{{ requestDetails.request_id }}</strong> completed successfully.
-      </v-alert>
-      <v-alert v-else-if="requestDetails.status === 'running'" type="info" prominent>
-          Request <strong>{{ requestDetails.request_id }}</strong> has <strong>{{ requestDetails.task_count - requestDetails.successful_tasks - requestDetails.failed_tasks }}</strong> Tasks remaining.
-      </v-alert>
-      <v-alert v-else-if="requestDetails.status === 'completed_with_errors'" type="warning" prominent>
-          Request <strong>{{ requestDetails.request_id }}</strong> completed with <strong>{{ requestDetails.failed_tasks }}</strong> failed Tasks.
-      </v-alert>
-      <v-alert v-else type="error" prominent>
-          Request <strong>{{ requestDetails.request_id }}</strong> was not successful.
-      </v-alert>
-      <v-card>
-          <v-list density="compact" v-model:opened="openGroups">
-            <v-list-group value="ids">
-              <template v-slot:activator="{ props }">
-                <v-list-item v-bind="props" title="Associated IDs"></v-list-item>
-              </template>
-              <v-list-item title="Request ID:">
-                  {{ requestDetails.request_id }}
-              </v-list-item>
-              <v-list-item title="Evidence ID:">
-                  {{ requestDetails.evidence_id }}
-              </v-list-item>
-              <v-list-item title="Reason:">
-                  {{ requestDetails.reason }}
-              </v-list-item>
-            </v-list-group>
-            <v-list-group value="details">
-              <template v-slot:activator="{ props }">
-                <v-list-item v-bind="props" title="Processing Details"></v-list-item>
-              </template>
-              <v-list-item title="Evidence Name:">
-                  {{ requestDetails.evidence_name }}
-              </v-list-item>
-              <v-list-item title="Last Update:">
-                  {{ requestDetails.last_task_update_time }}
-              </v-list-item>
-              <v-list-item title="Requester:">
-                  {{ requestDetails.requester }}
-              </v-list-item>
-              <v-list-item title="Status:">
-                  {{ requestDetails.status }}
-              </v-list-item>
-              <v-list-item title="Total:">
-                  {{ requestDetails.task_count }} Tasks
-              </v-list-item>
-              <v-list-item title="Running:">
-                  {{ requestDetails.running_tasks }} Tasks
-              </v-list-item>
-              <v-list-item title="Pending:">
-                  {{ requestDetails.queued_tasks }} Tasks
-              </v-list-item>
-              <v-list-item title="Successful:">
-                  {{ requestDetails.successful_tasks }} Tasks
-              </v-list-item>
-              <v-list-item title="Failed:">
-                  {{ requestDetails.failed_tasks }} Tasks
-              </v-list-item>
-            </v-list-group>
-          </v-list>
-      </v-card>
-     <request-report v-if="markdownReport !== ''" :markdownReport="this.markdownReport" :key="this.currentRequestID"> </request-report>
-    </div>
-  </template>
-  
-  <script>
-  import ApiClient from '../utils/RestApiClient.js'
-  import { mergeProps } from 'vue'
-  import RequestReport from './RequestReport.vue';
+          <v-list-item title="Evidence Name:">
+            {{ requestDetails.evidence_name }}
+          </v-list-item>
+          <v-list-item title="Last Update:">
+            {{ requestDetails.last_task_update_time }}
+          </v-list-item>
+          <v-list-item title="Requester:">
+            {{ requestDetails.requester }}
+          </v-list-item>
+          <v-list-item title="Status:">
+            {{ requestDetails.status }}
+          </v-list-item>
+          <v-list-item title="Total:">
+            {{ requestDetails.task_count }} Tasks
+          </v-list-item>
+          <v-list-item title="Running:">
+            {{ requestDetails.running_tasks }} Tasks
+          </v-list-item>
+          <v-list-item title="Pending:">
+            {{ requestDetails.queued_tasks }} Tasks
+          </v-list-item>
+          <v-list-item title="Successful:">
+            {{ requestDetails.successful_tasks }} Tasks
+          </v-list-item>
+          <v-list-item title="Failed:">
+            {{ requestDetails.failed_tasks }} Tasks
+          </v-list-item>
+        </v-list-group>
+      </v-list>
+    </v-card>
+    <request-report v-if="markdownReport !== ''" :markdownReport="this.markdownReport" :key="this.currentRequestID">
+    </request-report>
+  </div>
+</template>
 
-  export default {
-    components: { RequestReport },
-    props: ['requestDetails'],
-    data() {
-      return {
-        openGroups: ['ids', 'details'],
-        markdownReport: '',
-        currentRequestID: ''
-      }
-    },
-    methods: {
-        mergeProps,
-        getRequestOutput: function (request_id) {
-          ApiClient.getRequestOutput(request_id)
-            .then(({ data }) => {
-              const downloadObj = window.URL.createObjectURL(new Blob([data]))
-              const link = document.createElement('a')
-              link.href = downloadObj
-              link.setAttribute('download', request_id + '.tgz')
-              document.body.appendChild(link)
-              link.click()
-              link.remove()
-            })
-            .catch((e) => {
-              console.error(e)
-            })
-        },
-      getMarkdownReport: function (request_id) {
-        ApiClient.getRequestReport(request_id)
-          .then(({ data }) => { 
-            this.markdownReport = data
-            // To allow for re-click if user requests same report
-            if (this.currentRequestID === request_id) {
-              this.currentRequestID = request_id + Math.random()
-            } else {
-              this.currentRequestID = request_id
-            }
-          })
-          .catch((e) => {
-            console.error(e)
-          })
-      },
+<script>
+import ApiClient from '../utils/RestApiClient.js'
+import { mergeProps } from 'vue'
+import RequestReport from './RequestReport.vue';
+
+export default {
+  components: { RequestReport },
+  props: ['requestDetails'],
+  data() {
+    return {
+      openGroups: ['ids', 'details'],
+      markdownReport: '',
+      currentRequestID: ''
     }
+  },
+  methods: {
+    mergeProps,
+    getRequestOutput: function (request_id) {
+      ApiClient.getRequestOutput(request_id)
+        .then(({ data }) => {
+          const downloadObj = window.URL.createObjectURL(new Blob([data]))
+          const link = document.createElement('a')
+          link.href = downloadObj
+          link.setAttribute('download', request_id + '.tgz')
+          document.body.appendChild(link)
+          link.click()
+          link.remove()
+        })
+        .catch((e) => {
+          console.error(e)
+        })
+    },
+    getMarkdownReport: function (request_id) {
+      ApiClient.getRequestReport(request_id)
+        .then(({ data }) => {
+          this.markdownReport = data
+          // To allow for re-click if user requests same report
+          if (this.currentRequestID === request_id) {
+            this.currentRequestID = request_id + Math.random()
+          } else {
+            this.currentRequestID = request_id
+          }
+        })
+        .catch((e) => {
+          console.error(e)
+        })
+    },
   }
-  </script>
-  
-  <style scoped>
-  .v-list-item {
-    font-size: 12px;
-  }
-  
-  .v-list-item__action {
-    margin-top: 0;
-    margin-bottom: 0;
-  }
-  </style>
-  
\ No newline at end of file
+}
+</script>
+
+<style scoped>
+.v-list-item {
+  font-size: 12px;
+}
+
+.v-list-item__action {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+</style>
diff --git a/web/src/components/RequestReport.vue b/web/src/components/RequestReport.vue
index ff1b9c668..9a0b31f12 100644
--- a/web/src/components/RequestReport.vue
+++ b/web/src/components/RequestReport.vue
@@ -44,9 +44,7 @@ export default {
 </script>
 
 <style scoped>
-
 .markdown-body {
   max-width: 125ch;
 }
-
 </style>
\ No newline at end of file
diff --git a/web/src/components/TaskDetails.vue b/web/src/components/TaskDetails.vue
index f5af86519..b00ceb71b 100644
--- a/web/src/components/TaskDetails.vue
+++ b/web/src/components/TaskDetails.vue
@@ -28,6 +28,13 @@ limitations under the License.
           </v-tooltip>
         </template>
       </v-snackbar>
+      <v-tooltip location="top" text="View task report">
+        <template v-slot:activator="{ props: tooltip }">
+          <v-btn icon="mdi-file-document-outline" variant="text" v-bind="tooltip"
+            @click="getMarkdownReport(taskDetails.id)">
+          </v-btn>
+        </template>
+      </v-tooltip>
     </v-card-title>
     <v-alert v-if="taskDetails.successful === true" type="success" prominent>
       {{ taskDetails.status }}
@@ -146,18 +153,24 @@ limitations under the License.
         </div>
       </v-list>
     </v-card>
+    <request-report v-if="markdownReport !== ''" :markdownReport="this.markdownReport" :key="this.currentTaskID">
+    </request-report>
   </div>
 </template>
 
 <script>
 import ApiClient from '../utils/RestApiClient.js'
 import { mergeProps } from 'vue'
+import RequestReport from './RequestReport.vue';
 
 export default {
+  components: { RequestReport },
   props: ['taskDetails'],
   data() {
     return {
       openGroups: ['ids', 'details'],
+      markdownReport: '',
+      currentTaskID: ''
     }
   },
   methods: {
@@ -177,6 +190,21 @@ export default {
           console.error(e)
         })
     },
+    getMarkdownReport: function (task_id) {
+      ApiClient.getTaskReport(task_id)
+        .then(({ data }) => {
+          this.markdownReport = data
+          // To allow for re-click if user requests same report
+          if (this.currentTaskID === task_id) {
+            this.currentTaskID = task_id + Math.random()
+          } else {
+            this.currentTaskID = task_id
+          }
+        })
+        .catch((e) => {
+          console.error(e)
+        })
+    },
   },
 }
 </script>
diff --git a/web/src/utils/RestApiClient.js b/web/src/utils/RestApiClient.js
index 67c6bc5de..a12e2b228 100644
--- a/web/src/utils/RestApiClient.js
+++ b/web/src/utils/RestApiClient.js
@@ -44,7 +44,7 @@ export default {
 
   // Request Report
   getRequestReport(request_id) {
-    return RestApiClient.get('/api/request/report?request_id=' + request_id)
+    return RestApiClient.get('/api/request/report/' + request_id)
   },
 
   // Task List
@@ -57,6 +57,11 @@ export default {
     return RestApiClient.get('/api/task/' + task_id)
   },
 
+  // Task Report
+  getTaskReport(task_id) {
+    return RestApiClient.get('/api/task/report/' + task_id)
+  },
+
   // Task Output
   getTaskOutput(task_id) {
     return RestApiClient.get('/api/result/task/' + task_id, { responseType: 'blob' })


Test patch:
diff --git a/turbinia/api/api_server_test.py b/turbinia/api/api_server_test.py
index fd37d5c40..ff6b3c6be 100644
--- a/turbinia/api/api_server_test.py
+++ b/turbinia/api/api_server_test.py
@@ -41,25 +41,44 @@ class testTurbiniaAPIServer(unittest.TestCase):
   """ Test Turbinia API server."""
 
   _TASK_TEST_DATA = {
-      'id': 'c8f73a5bc5084086896023c12c7cc026',
-      'evidence_name': 'test_data/artifact_disk.dd',
-      'evidence_id': '084d5904f3d2412b99dc29ed34853a16',
-      'job_id': '1db0dc47d8f244f5b4fa7e15b8a87861',
-      'start_time': '2022-04-01T19:15:14.791074Z',
-      'last_update': '2022-04-01T19:17:14.791074Z',
-      'name': 'YaraAnalysisTask',
-      'request_id': '41483253079448e59685d88f37ab91f7',
-      'requester': 'root',
-      'group_id': '1234',
-      'worker_name': '95153920ab11',
-      'report_data': 'No issues found in crontabs',
-      'report_priority': 80,
-      'run_time': 46.003234,
-      'status': 'No issues found in crontabs',
-      'saved_paths': '/tmp/worker-log.txt',
-      'successful': True,
-      'output_manager': '',
-      'instance': 'turbinia-jleaniz-test'
+      'id':
+          'c8f73a5bc5084086896023c12c7cc026',
+      'evidence_name':
+          'test_data/artifact_disk.dd',
+      'evidence_id':
+          '084d5904f3d2412b99dc29ed34853a16',
+      'job_id':
+          '1db0dc47d8f244f5b4fa7e15b8a87861',
+      'start_time':
+          '2022-04-01T19:15:14.791074Z',
+      'last_update':
+          '2022-04-01T19:17:14.791074Z',
+      'name':
+          'YaraAnalysisTask',
+      'request_id':
+          '41483253079448e59685d88f37ab91f7',
+      'requester':
+          'root',
+      'group_id':
+          '1234',
+      'worker_name':
+          '95153920ab11',
+      'report_data':
+          '### YaraAnalysisTask (LOW PRIORITY): No issues found in crontabs',
+      'report_priority':
+          80,
+      'run_time':
+          46.003234,
+      'status':
+          'No issues found in crontabs',
+      'saved_paths':
+          '/tmp/worker-log.txt',
+      'successful':
+          True,
+      'output_manager':
+          '',
+      'instance':
+          'turbinia-jleaniz-test'
   }
 
   _REQUEST_TEST_DATA = {
@@ -181,8 +200,7 @@ class testTurbiniaAPIServer(unittest.TestCase):
       ]
   }
 
-  _REQUEST_REPORT = b'## Request ID: 41483253079448e59685d88f37ab91f7\n* Last Update: 2022-04-01T19:17:14.791074Z\n* Requester: root\n* Reason:\n* Status: successful\n* Failed tasks: 0\n* Running tasks: 0\n* Successful tasks: 1\n* Task Count: 1\n* Queued tasks: 0\n* Evidence Name: test_data/artifact_disk.dd\n* Evidence ID: 084d5904f3d2412b99dc29ed34853a16\n\n### YaraAnalysisTask (LOW PRIORITY): No issues found in crontabs'
-
+  _REQUEST_REPORT = '## Request ID: 41483253079448e59685d88f37ab91f7\n* Last Update: 2022-04-01T19:17:14.791074Z\n* Requester: root\n* Reason:\n* Status: successful\n* Failed tasks: 0\n* Running tasks: 0\n* Successful tasks: 1\n* Task Count: 1\n* Queued tasks: 0\n* Evidence Name: test_data/artifact_disk.dd\n* Evidence ID: 084d5904f3d2412b99dc29ed34853a16\n\n### YaraAnalysisTask (LOW PRIORITY): No issues found in crontabs'
   _COUNT_SUMMARY = 3
 
   @mock.patch('redis.StrictRedis')
@@ -252,11 +270,11 @@ def testGetTaskStatus(self, testTaskData):
     expected_result_str = json.dumps(expected_result_dict)
 
     redis_client.set(
-        'TurbiniaTask:41483253079448e59685d88f37ab91f7', expected_result_str)
+        'TurbiniaTask:c8f73a5bc5084086896023c12c7cc026', expected_result_str)
 
     testTaskData.return_value = [
         json.loads(
-            redis_client.get('TurbiniaTask:41483253079448e59685d88f37ab91f7'))
+            redis_client.get('TurbiniaTask:c8f73a5bc5084086896023c12c7cc026'))
     ]
 
     result = self.client.get(f"/api/task/{self._TASK_TEST_DATA.get('id')}")
@@ -420,10 +438,29 @@ def testGetRequestReport(self, testKeyExists, testTaskData, testRequestData):
     ]
 
     result = self.client.get(
-        f"/api/request/report?request_id={self._REQUEST_STATUS_TEST_DATA.get('request_id')}"
+        f"/api/request/report/{self._REQUEST_STATUS_TEST_DATA.get('request_id')}"
     )
     result = result.content
-    self.assertEqual(expected_result, result)
+    self.assertEqual(expected_result, result.decode())
+
+  @mock.patch('turbinia.state_manager.RedisStateManager.get_task')
+  def testGetTaskReport(self, testTaskData):
+    """Test getting task report."""
+    redis_client = fakeredis.FakeStrictRedis()
+    input_task = TurbiniaTask().deserialize(self._TASK_TEST_DATA)
+    task_data_dict = input_task.serialize()
+    task_data_str = json.dumps(task_data_dict)
+    expected_result = task_data_dict.get('report_data')
+
+    redis_client.set(
+        'TurbiniaTask:c8f73a5bc5084086896023c12c7cc026', task_data_str)
+
+    testTaskData.return_value = json.loads(
+        redis_client.get('TurbiniaTask:c8f73a5bc5084086896023c12c7cc026'))
+
+    result = self.client.get(
+        f"/api/task/report/{self._TASK_TEST_DATA.get('id')}")
+    self.assertEqual(expected_result, result.content.decode())
 
   @mock.patch('turbinia.state_manager.RedisStateManager.get_task_data')
   def testTaskNotFound(self, testTaskData):