import { useRef, useState } from "react"; import { api, fmtBytes, fmtTime, type Session, type Segment } from "../api"; import { Badge, Modal, go, toast, usePoll } from "../ui"; export default function Library() { const [sessions, err, reload] = usePoll("/api/captures", 5000); const [q, setQ] = useState(""); const [edit, setEdit] = useState(null); const [del, setDel] = useState(null); const [info, setInfo] = useState<{ s: Session; segs: Segment[]; verify?: any[] } | null>(null); const [uploading, setUploading] = useState(false); const file = useRef(null); const list = (sessions ?? []).filter((s) => !q || (s.name + " " + s.tags + " " + s.description + " " + s.notes).toLowerCase().includes(q.toLowerCase())); const upload = async (f: File) => { setUploading(true); try { const s = await api(`/api/captures/import?name=${encodeURIComponent(f.name)}`, { raw: f }); toast(`Imported ${f.name}; indexing…`); reload(); go("/analyse/" + s.id); } catch (e) { toast((e as Error).message, true); } setUploading(false); }; return (

Capture Library

Stored sessions. Original packet files are kept unmodified, with SHA-256 hashes.

setQ(e.target.value)} /> e.target.files?.[0] && upload(e.target.files[0])} />
{err &&
{err}
}
{list.map((s) => ( ))} {list.length === 0 && }
NameStateStartedDurationPacketsSizeSource
{s.name}{s.preserved && <> preserved}{s.tags &&
{s.tags}
}
{s.state}{s.error &&
{s.error.slice(0, 60)}
}
{fmtTime(s.started_at)} {s.ended_at ? ((s.ended_at - s.started_at) / 1000).toFixed(0) + " s" : "–"} {s.packets.toLocaleString()} {fmtBytes(s.bytes)} {s.kind === "import" ? "import" : (s.interfaces ?? []).join(",")}{s.capture_filter &&
{s.capture_filter}
}
Export
No sessions.
setEdit(null)} footer={<> }> {edit && <>