diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index c0daa11..edc650d 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -4,6 +4,7 @@
import IocManagement from './components/IocManagement.svelte';
import PrecursorAlerts from './components/PrecursorAlerts.svelte';
import DevicePosture from './components/DevicePosture.svelte';
+ import IocFeeds from './components/IocFeeds.svelte';
import Toast from './components/common/Toast.svelte';
import { onMount } from 'svelte';
import { authApi } from './lib/api';
@@ -17,7 +18,7 @@
localStorage.setItem('ariaSidebarCollapsed', String(sidebarCollapsed));
}
- type View = 'dashboard' | 'events' | 'ioc' | 'sequences' | 'posture';
+ type View = 'dashboard' | 'events' | 'ioc' | 'sequences' | 'posture' | 'feeds';
let currentView: View = 'dashboard';
let eventsInitialSeverity: ThreatSeverity | '' = '';
@@ -97,6 +98,14 @@
{#if !sidebarCollapsed}Device Posture{/if}
+
+
+
+
+
+
+
+
+ {#if feedsLoading && feeds.length === 0}
+
Loading feed statusβ¦
+ {:else}
+
+ {#each orderedFeeds as feed (feed.feedName)}
+ {@const meta = FEED_META[feed.feedName] ?? { label: feed.feedName, icon: 'π‘', requiresKey: false }}
+
+
+
{meta.icon}
+
+
{meta.label}
+
{feed.feedName}
+
+
+ {statusLabel(feed.lastRunStatus)}
+
+
+
+
+ {#if feed.configured}
+ Configured
+ {:else if meta.requiresKey}
+ Not configured β API key required
+ {:else}
+ No key required
+ {/if}
+
+
+
+
+ Last run
+ {relativeTime(feed.lastRunAt)}
+
+
+ IOCs added
+ {fmtCount(feed.lastIocsAdded)}
+
+
+ Total from feed
+ {fmtCount(feed.totalIocsFromFeed)}
+
+
+
+
+
+ {/each}
+
+ {/if}
+
+
+
+
+
+
+
+
{ runsPage = e.detail.page; loadRuns(runsPage); }}
+ />
+
+
+ {#if runsLoading}
+
Loading run historyβ¦
+ {:else if runs.length === 0}
+
No feed runs recorded yet. Trigger a refresh to see results here.
+ {:else}
+
+
+
+ | Feed |
+ Started |
+ Duration |
+ Status |
+ IOCs Added |
+ IOCs Updated |
+ Error |
+
+
+
+ {#each runs as run (run.id)}
+
+ |
+
+ {FEED_META[run.feedName]?.icon ?? 'π‘'}
+ {FEED_META[run.feedName]?.label ?? run.feedName}
+
+ |
+ {formatTs(run.startedAt)} |
+ {duration(run.startedAt, run.completedAt)} |
+
+
+ {statusLabel(run.status)}
+
+ |
+ {run.iocsAdded.toLocaleString()} |
+ {run.iocsUpdated.toLocaleString()} |
+
+ {#if run.errorMessage}
+
+ {run.errorMessage.length > 60
+ ? run.errorMessage.substring(0, 60) + 'β¦'
+ : run.errorMessage}
+
+ {:else}
+ β
+ {/if}
+ |
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index a3b6ad1..697cc37 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -29,7 +29,7 @@ export type ThreatSeverity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO';
export type EventType = 'PHYSICAL' | 'OS_EVENT' | 'FILE_SYSTEM' | 'COMPOSITE';
export type AttackPhase = 'PHYSICAL_ACCESS' | 'MALWARE_STAGING' | 'MALWARE_EXECUTION' | 'PERSISTENCE' | 'CLEANUP';
export type IocType = 'FILENAME' | 'MD5' | 'REGISTRY_KEY' | 'DIRECTORY' | 'SERVICE_NAME' | 'IP' | 'FILE_PATH';
-export type IocSource = 'FBI_FLASH' | 'FS_ISAC' | 'MANUAL';
+export type IocSource = 'FBI_FLASH' | 'FS_ISAC' | 'MANUAL' | 'OTX' | 'MALWARE_BAZAAR' | 'THREAT_FOX';
export type ConfidenceLevel = 'HIGH' | 'MEDIUM' | 'LOW';
// ββ Entities βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -115,6 +115,27 @@ export interface PageResponse