import React, { useEffect, useMemo, useState } from "react";
function severityForEventId(eventId) {
const id = Number.parseInt(String(eventId ?? ""), 10);
const high = new Set([1, 3, 8, 10]);
const med = new Set([5, 6, 7, 11, 22]);
if (high.has(id)) return "high";
if (med.has(id)) return "medium";
return "low";
}
function fmtTime(ts) {
if (!ts) return "—";
try {
return new Date(ts).toLocaleTimeString();
} catch {
return String(ts);
}
}
export default function ComputerDetailsModal({ open, computer, onClose, onAskAI }) {
const [tab, setTab] = useState("overview");
const [loading, setLoading] = useState(false);
const [details, setDetails] = useState(null);
const [error, setError] = useState("");
const [search, setSearch] = useState("");
// JSON viewer
const [jsonOpen, setJsonOpen] = useState(false);
const [jsonTitle, setJsonTitle] = useState("");
const [jsonData, setJsonData] = useState(null);
// reset when opening / switching pc
useEffect(() => {
if (!open) return;
setTab("overview");
setSearch("");
setError("");
setDetails(null);
setJsonOpen(false);
setJsonTitle("");
setJsonData(null);
}, [open, computer?.name]);
// close on ESC
useEffect(() => {
if (!open) return;
const onKey = (e) => {
if (e.key === "Escape") onClose?.();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
// load details
useEffect(() => {
if (!open || !computer?.name) return;
let cancelled = false;
async function load() {
setLoading(true);
setError("");
try {
const r = await fetch(`/api/computer/${encodeURIComponent(computer.name)}`, {
credentials: "include",
});
const d = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(d?.error || `HTTP ${r.status}`);
if (!cancelled) setDetails(d);
} catch (e) {
if (!cancelled) {
setError(String(e?.message || e));
setDetails(null);
}
} finally {
if (!cancelled) setLoading(false);
}
}
load();
return () => {
cancelled = true;
};
}, [open, computer?.name]);
const filteredSysmon = useMemo(() => {
const list = details?.sysmon_events || [];
const q = search.trim().toLowerCase();
if (!q) return list;
return list.filter((ev) => {
const msg = (ev.message || "").toLowerCase();
const type = (ev.event_type || "").toLowerCase();
const id = String(ev.event_id ?? "").toLowerCase();
return msg.includes(q) || type.includes(q) || id.includes(q);
});
}, [details, search]);
function openJson(ev) {
setJsonTitle(`Sysmon Event ${ev?.event_id ?? ""} • ${ev?.event_type ?? ""}`);
setJsonData(ev?.details ?? ev);
setJsonOpen(true);
}
function closeJson() {
setJsonOpen(false);
setJsonTitle("");
setJsonData(null);
}
if (!open) return null;
return (
<>
e.stopPropagation()}>
{computer?.name || "Computer"}
{computer?.ip || "—"} • {computer?.user || "—"} • {computer?.status || "—"}
{loading &&
Loading…
}
{!loading && error && (
)}
{!loading && !error && details && tab === "overview" && (
System Overview
Env: {details.computer?.env_name || "default"}
User: {details.computer?.user || "—"}
IP: {details.computer?.ip || "—"}
OS: {details.computer?.os || "—"}
First seen: {details.computer?.first_seen || "—"}
Last seen: {details.computer?.last_seen || "—"}
Total logs: {details.computer?.total_logs ?? "—"}
)}
{!loading && !error && details && tab === "sysmon" && (
| Time |
ID |
Type |
Severity |
Message |
View |
{filteredSysmon.slice(0, 400).map((ev, idx) => {
const sev = severityForEventId(ev.event_id);
return (
| {fmtTime(ev.timestamp)} |
{ev.event_id ?? "—"} |
{ev.event_type || "—"} |
{sev.toUpperCase()} |
{(ev.message || "").slice(0, 220)}
|
|
);
})}
{filteredSysmon.length === 0 && (
|
No sysmon events.
|
)}
{filteredSysmon.length > 400 && (
Showing first 400 (for speed).
)}
)}
{!loading && !error && details && tab === "processes" && (
Recent Processes
{(details.recent_processes || []).length} rows
| Time |
PID |
Name |
User |
CPU% |
RAM MB |
Cmdline |
{(details.recent_processes || []).slice(0, 400).map((p, idx) => (
| {fmtTime(p.timestamp)} |
{p.pid ?? "—"} |
{p.name || "—"} |
{p.username || "—"} |
{p.cpu_percent ?? 0} |
{p.memory_mb ?? 0} |
{(p.cmdline || "").slice(0, 220)}
|
))}
{(details.recent_processes || []).length === 0 && (
|
No process data.
|
)}
)}
{!loading && !error && details && tab === "network" && (
Network Connections
{(details.network_connections || []).length} rows
| Time |
PID |
Process |
Local |
Remote |
Status |
{(details.network_connections || []).slice(0, 400).map((n, idx) => (
| {fmtTime(n.timestamp)} |
{n.pid ?? "—"} |
{n.process_name || "—"} |
{n.local_address || "—"}
|
{n.remote_address || "—"}
|
{n.status || "—"} |
))}
{(details.network_connections || []).length === 0 && (
|
No network data.
|
)}
)}
{!loading && !error && !details && (
No details.
)}
{/* JSON VIEWER */}
{jsonOpen && (
e.stopPropagation()}>
{JSON.stringify(jsonData ?? {}, null, 2)}
)}
>
);
}