Build IP Sentinel operations console
This commit is contained in:
93
app/page.tsx
Normal file
93
app/page.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
type Event = { ip: string; country: string; city: string; network: string; score: number; status: "Review" | "Blocked" | "Allowed"; seen: string; requests: string };
|
||||
|
||||
const initialEvents: Event[] = [
|
||||
{ ip: "185.220.101.42", country: "Germany", city: "Falkenstein", network: "AS60729 · Zwiebelfreunde", score: 96, status: "Review", seen: "12 sec ago", requests: "842" },
|
||||
{ ip: "45.134.26.91", country: "Netherlands", city: "Amsterdam", network: "AS9009 · M247 Europe", score: 88, status: "Blocked", seen: "1 min ago", requests: "391" },
|
||||
{ ip: "104.28.208.87", country: "United States", city: "San Francisco", network: "AS13335 · Cloudflare", score: 18, status: "Allowed", seen: "3 min ago", requests: "126" },
|
||||
{ ip: "193.32.162.14", country: "Romania", city: "Bucharest", network: "AS209605 · UAB Host Baltic", score: 73, status: "Review", seen: "7 min ago", requests: "214" },
|
||||
{ ip: "34.160.111.145", country: "United States", city: "Mountain View", network: "AS396982 · Google Cloud", score: 7, status: "Allowed", seen: "11 min ago", requests: "98" },
|
||||
];
|
||||
|
||||
function riskLabel(score: number) { return score >= 80 ? "Critical" : score >= 60 ? "Elevated" : "Low"; }
|
||||
|
||||
export default function Home() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [filter, setFilter] = useState("All traffic");
|
||||
const [events, setEvents] = useState(initialEvents);
|
||||
const [selected, setSelected] = useState<Event>(initialEvents[0]);
|
||||
const [notice, setNotice] = useState("");
|
||||
const visible = useMemo(() => events.filter((e) => filter === "All traffic" || e.status === filter), [events, filter]);
|
||||
|
||||
const inspect = () => {
|
||||
const value = query.trim();
|
||||
if (!value) return;
|
||||
const found = events.find((e) => e.ip === value);
|
||||
if (found) setSelected(found);
|
||||
else setSelected({ ip: value, country: "Unknown", city: "Unresolved", network: "No network record", score: 42, status: "Review", seen: "Just now", requests: "1" });
|
||||
setNotice(`Inspection opened for ${value}`);
|
||||
setTimeout(() => setNotice(""), 2400);
|
||||
};
|
||||
|
||||
const decide = (status: "Blocked" | "Allowed") => {
|
||||
setSelected((current) => ({ ...current, status }));
|
||||
setEvents((current) => current.map((event) => event.ip === selected.ip ? { ...event, status } : event));
|
||||
setNotice(`${selected.ip} ${status.toLowerCase()}`);
|
||||
setTimeout(() => setNotice(""), 2400);
|
||||
};
|
||||
|
||||
return (
|
||||
<main>
|
||||
<header className="topbar">
|
||||
<div className="brand"><span className="brandMark">IP</span><span>Sentinel</span></div>
|
||||
<nav aria-label="Primary navigation">
|
||||
<button className="navActive">Overview</button><button>Investigations</button><button>Rules</button><button>Reports</button>
|
||||
</nav>
|
||||
<div className="topActions"><button className="iconButton" aria-label="Notifications">●</button><div className="avatar">MK</div><div><strong>Maya Kim</strong><small>Security Ops</small></div></div>
|
||||
</header>
|
||||
|
||||
<div className="workspace">
|
||||
<section className="hero">
|
||||
<div><p className="eyebrow">NETWORK INTELLIGENCE</p><h1>Good morning, Maya.</h1><p>Here’s what your perimeter has seen in the last 24 hours.</p></div>
|
||||
<div className="health"><span className="pulse"/><div><strong>All systems operational</strong><small>Last sync 18 seconds ago</small></div></div>
|
||||
</section>
|
||||
|
||||
<section className="lookup" aria-label="IP address lookup">
|
||||
<div className="lookupIcon">⌕</div><div className="lookupCopy"><label htmlFor="ip">Inspect an IP address</label><span>Reputation, location, network and recent activity</span></div>
|
||||
<input id="ip" value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && inspect()} placeholder="Enter IPv4 or IPv6 address" />
|
||||
<button className="primary" onClick={inspect}>Inspect IP <span>→</span></button>
|
||||
</section>
|
||||
|
||||
<section className="metrics" aria-label="Traffic summary">
|
||||
<article><div className="metricTop"><span>REQUESTS SCANNED</span><i className="blue">↗</i></div><strong>2.48M</strong><p><b>+12.4%</b> from yesterday</p></article>
|
||||
<article><div className="metricTop"><span>UNIQUE ADDRESSES</span><i className="violet">◎</i></div><strong>18,294</strong><p><b>+3.1%</b> from yesterday</p></article>
|
||||
<article><div className="metricTop"><span>THREATS BLOCKED</span><i className="red">!</i></div><strong>1,847</strong><p><em>−8.2%</em> from yesterday</p></article>
|
||||
<article><div className="metricTop"><span>REVIEW QUEUE</span><i className="amber">⌁</i></div><strong>24</strong><p><b>7 critical</b> need attention</p></article>
|
||||
</section>
|
||||
|
||||
<div className="mainGrid">
|
||||
<section className="panel activity">
|
||||
<div className="panelHead"><div><h2>Recent activity</h2><p>Addresses with notable behavior</p></div><div className="filters">{["All traffic","Review","Blocked","Allowed"].map((name) => <button key={name} className={filter === name ? "active" : ""} onClick={() => setFilter(name)}>{name}</button>)}</div></div>
|
||||
<div className="tableWrap"><table><thead><tr><th>IP ADDRESS</th><th>LOCATION / NETWORK</th><th>REQUESTS</th><th>RISK</th><th>STATUS</th><th></th></tr></thead><tbody>{visible.map((event) => <tr key={event.ip} className={selected.ip === event.ip ? "selected" : ""} onClick={() => setSelected(event)}><td><code>{event.ip}</code><small>{event.seen}</small></td><td><strong>{event.city}, {event.country}</strong><small>{event.network}</small></td><td>{event.requests}</td><td><span className={`score ${riskLabel(event.score).toLowerCase()}`}>{event.score}</span></td><td><span className={`status ${event.status.toLowerCase()}`}>{event.status}</span></td><td>›</td></tr>)}</tbody></table></div>
|
||||
<button className="viewAll">View all investigations →</button>
|
||||
</section>
|
||||
|
||||
<aside className="panel detail">
|
||||
<div className="detailHead"><div><p>SELECTED ADDRESS</p><code>{selected.ip}</code></div><span className={`status ${selected.status.toLowerCase()}`}>{selected.status}</span></div>
|
||||
<div className="riskBlock"><div className={`riskRing ${riskLabel(selected.score).toLowerCase()}`}><strong>{selected.score}</strong><span>/100</span></div><div><p>Risk assessment</p><h3>{riskLabel(selected.score)} risk</h3><small>Based on 14 intelligence signals</small></div></div>
|
||||
<div className="signal"><span>TOR EXIT NODE</span><strong>{selected.score > 80 ? "Detected" : "Not detected"}</strong></div>
|
||||
<div className="signal"><span>ABUSE REPORTS</span><strong>{selected.score > 80 ? "47 in 30 days" : "None recent"}</strong></div>
|
||||
<div className="signal"><span>NETWORK</span><strong>{selected.network.split(" · ")[0]}</strong></div>
|
||||
<div className="signal"><span>LOCATION</span><strong>{selected.city}, {selected.country}</strong></div>
|
||||
<div className="actions"><button className="danger" onClick={() => decide("Blocked")}>Block address</button><button onClick={() => decide("Allowed")}>Add to allowlist</button></div>
|
||||
<button className="fullReport">Open full investigation →</button>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
{notice && <div className="toast" role="status"><span>✓</span>{notice}</div>}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user