- Add reported_at field to threat_ioc (V7 migration) — tracks when the source
originally published the IOC, distinct from addedAt (ingestion time); wired
into OTX (created field), MalwareBazaar and ThreatFox (first_seen field),
and the manual IOC creation endpoint
- Fix threat event stats to use a 24h time window for critical/high counts
(countBySeverityAndDetectedAtAfter) instead of all-time totals
- Rebuild IOC Management with canonical filter sidebar: collapsible left sidebar
with per-type toggle pills, active filter chips with individual removal
- DevicePosture: collapse institutions and filter sidebar by default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: IocFeeds.svelte called feeds.find() on a Map (backend returned
Map<String,Object> not List) → reactive statement crash → entire Svelte tree
broken, all sidebar nav dead.
Backend:
- /feeds/status now returns List<Map> with feedName/configured/enabled/
lastRunAt/lastRunStatus/lastIocsAdded/lastIocsUpdated fields
- /feeds/runs now returns full Page<IocFeedRun> (not stripped .getContent())
- IocFeedService.isConfigured(feedName) added for per-feed key check
- Feed order fixed to [OTX, MALWARE_BAZAAR, THREAT_FOX] (deterministic)
Frontend:
- loadFeeds: Array.isArray guard prevents crash if shape is ever wrong
- loadRuns: safe fallback for both Page and plain-array responses
- api.ts refreshFeed URL fixed: /feeds/refresh/{name} not /feeds/{name}/refresh
- IocFeedStatus type: added lastIocsUpdated
- IocFeeds.svelte: Refresh All moved from header to toolbar; all buttons
use canonical btn/btn-primary/btn-secondary/btn-sm classes; pollInterval
now calls pollStatus (status-only poll) not full refresh trigger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>