feat: per-BU trend lines in counts history chart
- Create ivanti_counts_history_by_bu table (bu_ownership, state, count per sync) - Sync writes per-BU snapshot alongside global history on each sync - Seed table with current counts for immediate first data point - GET /counts/history accepts ?teams param — queries per-BU table when filtered - IvantiCountsChart accepts teamsParam prop, re-fetches on scope change - ReportingPage passes getActiveTeamsParam() to the chart - Historical per-BU data accumulates from this point forward - Global history (no filter) still uses the original aggregate table
This commit is contained in:
@@ -188,7 +188,7 @@ function extractDate(ts) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main component
|
||||
// ---------------------------------------------------------------------------
|
||||
export default function IvantiCountsChart() {
|
||||
export default function IvantiCountsChart({ teamsParam }) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [history, setHistory] = useState([]);
|
||||
@@ -199,8 +199,11 @@ export default function IvantiCountsChart() {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const historyUrl = teamsParam
|
||||
? `${API_BASE}/ivanti/findings/counts/history?teams=${encodeURIComponent(teamsParam)}`
|
||||
: `${API_BASE}/ivanti/findings/counts/history`;
|
||||
const [countsRes, anomalyRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/ivanti/findings/counts/history`, { credentials: 'include' }),
|
||||
fetch(historyUrl, { credentials: 'include' }),
|
||||
fetch(`${API_BASE}/ivanti/findings/anomaly/history`, { credentials: 'include' }),
|
||||
]);
|
||||
if (!cancelled) {
|
||||
@@ -218,7 +221,7 @@ export default function IvantiCountsChart() {
|
||||
};
|
||||
load();
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
}, [teamsParam]);
|
||||
|
||||
const chartData = useMemo(
|
||||
() => history.map(r => ({ ...r, date: fmtDate(r.date) })),
|
||||
|
||||
@@ -5187,8 +5187,16 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
}, []); // eslint-disable-line
|
||||
|
||||
// Re-fetch counts when admin scope changes (per-BU counts from Postgres)
|
||||
// Silent fetch — no loading spinner, just update the numbers
|
||||
useEffect(() => {
|
||||
fetchCounts();
|
||||
const teamsParam = getActiveTeamsParam();
|
||||
const url = teamsParam
|
||||
? `${API_BASE}/ivanti/findings/counts?teams=${encodeURIComponent(teamsParam)}`
|
||||
: `${API_BASE}/ivanti/findings/counts`;
|
||||
fetch(url, { credentials: 'include' })
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(data => { if (data) setStatusCounts({ open: data.open ?? 0, closed: data.closed ?? 0 }); })
|
||||
.catch(() => {});
|
||||
}, [adminScope]); // eslint-disable-line
|
||||
|
||||
// Set/clear a single column filter
|
||||
@@ -5786,7 +5794,7 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
Panel 1.5 — Open vs Closed trend over time
|
||||
---------------------------------------------------------------- */}
|
||||
{metricsTab === 'ivanti' && <AnomalyBanner />}
|
||||
{metricsTab === 'ivanti' && <IvantiCountsChart />}
|
||||
{metricsTab === 'ivanti' && <IvantiCountsChart teamsParam={getActiveTeamsParam()} />}
|
||||
|
||||
{/* ----------------------------------------------------------------
|
||||
Panel 2 — Findings table
|
||||
|
||||
Reference in New Issue
Block a user