feat(postgres): data migration + per-BU closed counts in frontend
- Create backend/scripts/migrate-to-postgres.js (one-time SQLite→Postgres copy) - Successfully migrated: 6 users, 21 CVEs, 6307 findings, 20965 compliance items, 138 archives, 67 atlas plans, all notes/overrides merged - All 22 tables verified with matching row counts - Frontend StatusDonut now uses server-provided per-BU counts (no more N/A) - Counts endpoint called with teams param on scope change - Re-fetch counts when admin scope toggle changes
This commit is contained in:
@@ -5052,8 +5052,12 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
const fetchCounts = async () => {
|
||||
setCountsLoading(true);
|
||||
try {
|
||||
// Fetch global counts — open count is overridden by client-side scoped findings
|
||||
const res = await fetch(`${API_BASE}/ivanti/findings/counts`, { credentials: 'include' });
|
||||
// Fetch counts from server — Postgres provides per-BU open+closed counts
|
||||
const teamsParam = getActiveTeamsParam();
|
||||
const url = teamsParam
|
||||
? `${API_BASE}/ivanti/findings/counts?teams=${encodeURIComponent(teamsParam)}`
|
||||
: `${API_BASE}/ivanti/findings/counts`;
|
||||
const res = await fetch(url, { credentials: 'include' });
|
||||
const data = await res.json();
|
||||
if (res.ok) setStatusCounts({ open: data.open ?? 0, closed: data.closed ?? 0 });
|
||||
} catch (e) {
|
||||
@@ -5182,6 +5186,11 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
fetchCardStatus();
|
||||
}, []); // eslint-disable-line
|
||||
|
||||
// Re-fetch counts when admin scope changes (per-BU counts from Postgres)
|
||||
useEffect(() => {
|
||||
fetchCounts();
|
||||
}, [adminScope]); // eslint-disable-line
|
||||
|
||||
// Set/clear a single column filter
|
||||
const setColFilter = useCallback((colKey, vals) => {
|
||||
setColumnFilters((prev) => {
|
||||
@@ -5662,21 +5671,16 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) {
|
||||
<div role="tabpanel">
|
||||
{metricsTab === 'ivanti' && (
|
||||
<div style={{ display: 'flex', gap: '3rem', flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
||||
{/* Open vs Closed donut — open from scoped findings; closed only meaningful when unscoped */}
|
||||
{/* Open vs Closed donut — per-BU counts from Postgres */}
|
||||
<div style={{ flex: '0 0 auto' }}>
|
||||
<div style={{ fontFamily: 'monospace', fontSize: '0.68rem', fontWeight: '600', color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '0.75rem' }}>
|
||||
Open vs Closed
|
||||
</div>
|
||||
<StatusDonut
|
||||
open={scopedFindings.length}
|
||||
closed={getActiveTeamsParam() ? 0 : statusCounts.closed}
|
||||
open={statusCounts.open}
|
||||
closed={statusCounts.closed}
|
||||
loading={countsLoading}
|
||||
/>
|
||||
{getActiveTeamsParam() && (
|
||||
<div style={{ fontFamily: 'monospace', fontSize: '0.6rem', color: '#475569', marginTop: '0.5rem' }}>
|
||||
Closed count unavailable when filtered by BU
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
|
||||
Reference in New Issue
Block a user