From 79d15a9304faa77cf09f93d8d704ad1e4cb2dd3d Mon Sep 17 00:00:00 2001 From: aaniya22 Date: Sun, 31 May 2026 08:55:07 +0530 Subject: [PATCH 1/4] feat: add scan history sidebar to Findings page --- frontend/src/components/ScanHistory.tsx | 63 +++++++++++++++++++++++++ frontend/src/pages/Findings.tsx | 23 +++++++-- 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/ScanHistory.tsx diff --git a/frontend/src/components/ScanHistory.tsx b/frontend/src/components/ScanHistory.tsx new file mode 100644 index 00000000..74946f68 --- /dev/null +++ b/frontend/src/components/ScanHistory.tsx @@ -0,0 +1,63 @@ +import { useEffect, useState } from "react"; +import { getTasks } from "../api"; + +interface ScanMeta { + task_id: string; + tool_name: string; + target: string; + status: string; + created_at: string; + duration_seconds?: number; +} + +interface Props { + onSelect: (taskId: string) => void; + activeTaskId?: string; +} + +export function ScanHistory({ onSelect, activeTaskId }: Props) { + const [history, setHistory] = useState([]); + + useEffect(() => { + const params = new URLSearchParams({ per_page: "20", page: "1" }); + getTasks(params) + .then((data: any) => setHistory(data.tasks || [])) + .catch(console.error); + }, []); + + if (history.length === 0) { + return ( +
+ No past scans found. +
+ ); + } + + return ( +
+

+ Scan History +

+ {history.map((scan) => ( + + ))} +
+ ); +} diff --git a/frontend/src/pages/Findings.tsx b/frontend/src/pages/Findings.tsx index fb9f849b..2f20bdf9 100644 --- a/frontend/src/pages/Findings.tsx +++ b/frontend/src/pages/Findings.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react' import { motion } from 'framer-motion' -import { getFindings } from '../api' +import { getFindings, getTaskResult } from '../api' +import { ScanHistory } from '../components/ScanHistory' import { formatLocaleDate, parseDateSafe, getCurrentTimeZone } from '../utils/date' type RiskFactor = { factor: string @@ -118,6 +119,7 @@ export default function Findings() { const [selectedFindingId, setSelectedFindingId] = useState(null) const [reviewState, setReviewState] = useState({}) const [copiedFindingId, setCopiedFindingId] = useState(null) + const [activeTaskId, setActiveTaskId] = useState() useEffect(() => { setLoading(true) @@ -130,6 +132,16 @@ export default function Findings() { .finally(() => setLoading(false)) }, []) + useEffect(() => { + if (!activeTaskId) return + getTaskResult(activeTaskId) + .then((data: any) => { + const nextFindings = data.findings || [] + setFindings(nextFindings) + }) + .catch(console.error) + }, [activeTaskId]) + useEffect(() => { try { const saved = localStorage.getItem('secuscan-finding-review-state') @@ -397,7 +409,11 @@ export default function Findings() { return (
-
+
+ +
Triage Workspace v5.1 @@ -788,7 +804,8 @@ export default function Findings() {
+
) -} +} \ No newline at end of file From b241579b810d73bea087760a5e54e1e1f0491dad Mon Sep 17 00:00:00 2001 From: aaniya22 Date: Sun, 31 May 2026 09:12:38 +0530 Subject: [PATCH 2/4] fix: add getTasks mock to test files for ScanHistory component --- frontend/testing/unit/AppRoutes.test.tsx | 2 ++ frontend/testing/unit/pages/Findings.test.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frontend/testing/unit/AppRoutes.test.tsx b/frontend/testing/unit/AppRoutes.test.tsx index 031ac013..fa0f3be4 100644 --- a/frontend/testing/unit/AppRoutes.test.tsx +++ b/frontend/testing/unit/AppRoutes.test.tsx @@ -3,6 +3,8 @@ import { MemoryRouter, useLocation } from 'react-router-dom' import { AppRoutes } from '../../src/App' vi.mock('../../src/api', () => ({ + getTasks: vi.fn(() => Promise.resolve({ tasks: [] })), + getTaskResult: vi.fn(() => Promise.resolve({ findings: [] })), getHealth: vi.fn().mockResolvedValue({ status: 'operational' }), getDashboardSummary: vi.fn().mockResolvedValue({ total_findings: 0, diff --git a/frontend/testing/unit/pages/Findings.test.tsx b/frontend/testing/unit/pages/Findings.test.tsx index 561ae086..fc4672c5 100644 --- a/frontend/testing/unit/pages/Findings.test.tsx +++ b/frontend/testing/unit/pages/Findings.test.tsx @@ -6,6 +6,8 @@ import { getFindings } from '../../../src/api' import * as dateUtils from '../../../src/utils/date' vi.mock('../../../src/api', () => ({ + getTasks: vi.fn(() => Promise.resolve({ tasks: [] })), + getTaskResult: vi.fn(() => Promise.resolve({ findings: [] })), getFindings: vi.fn(), API_BASE: 'http://127.0.0.1:8000', })) From 8ded186b21f846e7f038cf12df7053e4b4934603 Mon Sep 17 00:00:00 2001 From: aaniya22 Date: Sun, 31 May 2026 19:01:15 +0530 Subject: [PATCH 3/4] fix: address review feedback on scan history sidebar (#276) - Fix design tokens in ScanHistory to match SecuScan styling - Replace replacement-character separator with middle dot - Preserve selectedFindingId reset when switching scans - Add focused tests for ScanHistory scan selection --- frontend/src/components/ScanHistory.tsx | 18 ++-- frontend/src/pages/Findings.tsx | 17 ++-- frontend/testing/unit/pages/Findings.test.tsx | 90 ++++++++++++++++++- 3 files changed, 106 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/ScanHistory.tsx b/frontend/src/components/ScanHistory.tsx index 74946f68..6da4e8cd 100644 --- a/frontend/src/components/ScanHistory.tsx +++ b/frontend/src/components/ScanHistory.tsx @@ -27,7 +27,7 @@ export function ScanHistory({ onSelect, activeTaskId }: Props) { if (history.length === 0) { return ( -
+
No past scans found.
); @@ -35,23 +35,23 @@ export function ScanHistory({ onSelect, activeTaskId }: Props) { return (
-

- Scan History -

+

+ Load Past Scan +

{history.map((scan) => ( + +
+
+ + {/* Stat strip */} +
+ {[ + { label: "Total hosts", val: assets.length, sub: "across 3 scans" }, + { label: "Unique ports", val: totalPorts, sub: "open services" }, + { label: "Findings", val: totalFindings, sub: `${criticalCount} critical`, subColor: "#A32D2D" }, + { label: "Last scanned", val: "2h ago", sub: "scan #2024-07-31", num: false }, + ].map((s, i) => ( +
+
{s.label}
+
{s.val}
+
{s.sub}
+
+ ))} +
+ + {/* Filters */} +
+
+ βŒ• + setQuery(e.target.value)} + placeholder="Filter by host, IP, port, tag…" + style={{ ...inputStyle, width: "100%", paddingLeft: 28 }} + /> +
+ {[ + { id: "sev", val: sevFilter, set: setSevFilter, opts: ["critical","high","medium","low","none"], placeholder: "All severities" }, + { id: "scn", val: scannerFilter, set: setScannerFilter, opts: ["nmap","nuclei","subfinder","httpx"], placeholder: "All scanners" }, + ].map(f => ( + + ))} + +
+ + {/* Table */} +
+ + + + {[ + ["Host / IP", "24%"], + ["Open ports", "20%"], + ["Scanner", "12%"], + ["Findings", "10%"], + ["Severity", "11%"], + ["First seen", "11%"], + ["Last seen", "12%"], + ].map(([h, w]) => ( + + ))} + + + + {filtered.length === 0 ? ( + + ) : filtered.map((a, i) => ( + setSelected(selected === a.host ? null : a.host)} + style={{ + borderBottom: i < filtered.length - 1 ? "0.5px solid #e8e8e4" : "none", + background: selected === a.host ? "#f5f5f0" : "#fff", + cursor: "pointer", + transition: "background 0.1s", + }} + onMouseEnter={e => { if (selected !== a.host) e.currentTarget.style.background = "#f9f9f6"; }} + onMouseLeave={e => { if (selected !== a.host) e.currentTarget.style.background = "#fff"; }} + > + + + + + + + + + ))} + +
+ {h} +
No assets match your filters.
+
{a.host}
+
{a.ip}
+
{a.tags.map(t => )}
+
+
+ {a.ports.slice(0, 4).map(p => )} + {a.ports.length > 4 && } +
+
{a.scanner} 0 ? "#111" : "#ccc" }}> + {a.findings > 0 ? a.findings : "β€”"} + {relTime(a.first)}{relTime(a.last)}
+
+ + {/* Detail panel */} + {selected && (() => { + const a = assets.find(x => x.host === selected); + if (!a) return null; + const s = SEV_STYLES[a.severity]; + return ( +
+
+
+
{a.host}
+
{a.ip}
+
+ +
+
+
+
All ports
+
{a.ports.map(p => )}
+
+
+
Tags
+
{a.tags.map(t => )}
+
+
+
Highest severity
+ +
+
+
Scanner / task
+ {a.scanner} +
+
+
Findings
+ {a.findings} linked +
+
+
Timeline
+ First {relTime(a.first)} Β· Last {relTime(a.last)} +
+
+
+ ); + })()} + + + ); +} \ No newline at end of file diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 38caa98f..653c6710 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -6,7 +6,7 @@ export const routes = { scans: '/scans', reports: '/reports', workflows: '/workflows', - settings: '/settings', + inventory: '/inventory', task: '/task/:taskId', } as const