| 1 | "use client"; |
| 2 | import { useEffect, useState } from "react"; |
| 3 | import { api } from "@/lib/api"; |
| 4 | |
| 5 | export default function Enrichment() { |
| 6 | const [enrichers, setEnrichers] = useState<{ name: string; kind: string }[]>([]); |
| 7 | const [enricher, setEnricher] = useState("dns"); |
| 8 | const [target, setTarget] = useState("example.com"); |
| 9 | const [result, setResult] = useState<any | null>(null); |
| 10 | const [err, setErr] = useState<string | null>(null); |
| 11 | const [loading, setLoading] = useState(false); |
| 12 | |
| 13 | useEffect(() => { |
| 14 | api.enrichers().then((e) => { setEnrichers(e); if (e[0]) setEnricher(e[0].name); }) |
| 15 | .catch((e) => setErr(e.status === 401 ? "Sign in to run enrichment." : String(e))); |
| 16 | }, []); |
| 17 | |
| 18 | async function run() { |
| 19 | setErr(null); setResult(null); setLoading(true); |
| 20 | try { setResult((await api.runEnrichment(enricher, target)).result); } |
| 21 | catch (e: any) { setErr(e.status === 401 ? "Sign in to run enrichment." : "Enrichment failed."); } |
| 22 | finally { setLoading(false); } |
| 23 | } |
| 24 | |
| 25 | return ( |
| 26 | <div> |
| 27 | <h1>OSINT Enrichment</h1> |
| 28 | <p className="muted">Passive, public-source enrichment for investigations that cross into the digital domain. Secondary to the ALPR core.</p> |
| 29 | <div className="row" style={{ margin: "12px 0" }}> |
| 30 | <select value={enricher} onChange={(e) => setEnricher(e.target.value)}> |
| 31 | {enrichers.map((e) => <option key={e.name} value={e.name}>{e.name} ({e.kind})</option>)} |
| 32 | </select> |
| 33 | <input value={target} onChange={(e) => setTarget(e.target.value)} placeholder="example.com" /> |
| 34 | <button className="primary" onClick={run} disabled={loading}>{loading ? "Running…" : "Run"}</button> |
| 35 | </div> |
| 36 | {err && <div className="badge danger">{err}</div>} |
| 37 | {result && ( |
| 38 | <div className="card"> |
| 39 | <div className="row" style={{ justifyContent: "space-between" }}> |
| 40 | <strong>{result.enricher} → {result.target}</strong> |
| 41 | <span className="badge">confidence {result.confidence}</span> |
| 42 | </div> |
| 43 | {result.error && <div className="badge danger" style={{ marginTop: 8 }}>{result.error}</div>} |
| 44 | <h2>Entities</h2> |
| 45 | <table> |
| 46 | <thead><tr><th>Kind</th><th>Value</th></tr></thead> |
| 47 | <tbody>{(result.entities || []).map((e: any, i: number) => ( |
| 48 | <tr key={i}><td><span className="badge">{e.kind}</span></td><td>{e.value}</td></tr> |
| 49 | ))}</tbody> |
| 50 | </table> |
| 51 | {result.relationships?.length > 0 && ( |
| 52 | <> |
| 53 | <h2>Relationships</h2> |
| 54 | <ul>{result.relationships.map((r: any, i: number) => ( |
| 55 | <li key={i} className="muted">{r.src_value} -{r.relation}→ {r.dst_value}</li> |
| 56 | ))}</ul> |
| 57 | </> |
| 58 | )} |
| 59 | </div> |
| 60 | )} |
| 61 | </div> |
| 62 | ); |
| 63 | } |