185 lines
26 KiB
HTML
185 lines
26 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>OSINT Intelligence Graph</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/graphology@0.25.4/dist/graphology.umd.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/graphology-library@0.8.0/dist/graphology-library.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/sigma@2.4.0/build/sigma.min.js"></script>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { background: #1a1a2e; color: #eee; font-family: 'Segoe UI', system-ui, sans-serif; overflow: hidden; }
|
|
#container { width: 100vw; height: 100vh; }
|
|
#panel {
|
|
position: absolute; top: 12px; right: 12px;
|
|
background: rgba(10, 10, 30, 0.88);
|
|
border: 1px solid rgba(255,255,255,0.12);
|
|
padding: 16px; border-radius: 10px;
|
|
z-index: 10; min-width: 200px; max-width: 260px;
|
|
backdrop-filter: blur(6px);
|
|
}
|
|
#panel h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: #a0c4ff; letter-spacing: 0.5px; }
|
|
#stats { font-size: 11px; color: #888; margin-bottom: 12px; }
|
|
#filters { display: flex; flex-direction: column; gap: 6px; }
|
|
.filter-item { display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer; }
|
|
.filter-item input { cursor: pointer; accent-color: #a0c4ff; }
|
|
.color-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
|
#tooltip {
|
|
position: absolute; display: none;
|
|
background: rgba(5, 5, 20, 0.95);
|
|
border: 1px solid rgba(255,255,255,0.15);
|
|
padding: 10px 14px; border-radius: 8px;
|
|
pointer-events: none; z-index: 20;
|
|
max-width: 300px; font-size: 12px; line-height: 1.6;
|
|
}
|
|
#tooltip .tt-title { font-weight: 600; color: #a0c4ff; margin-bottom: 6px; font-size: 13px; }
|
|
#tooltip .tt-row { display: flex; gap: 6px; }
|
|
#tooltip .tt-key { color: #888; min-width: 80px; }
|
|
#tooltip .tt-val { color: #eee; word-break: break-all; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="container"></div>
|
|
<div id="panel">
|
|
<h3>OSINT Intelligence Graph</h3>
|
|
<div id="stats"></div>
|
|
<div id="filters"></div>
|
|
</div>
|
|
<div id="tooltip"></div>
|
|
|
|
<script>
|
|
(function () {
|
|
const graphData = {"nodes": [{"key": "person_001", "attributes": {"label": "Viktor Petrov", "entity_type": "person", "color": "#e74c3c", "size": 13.25, "domain": "cybersecurity", "status": "active", "risk_score": 92, "country": "RU", "aliases": ["darkside_v", "vp_shadow"], "first_seen": "2023-06-15", "last_seen": "2025-11-20", "role": "operator"}}, {"key": "person_002", "attributes": {"label": "Li Wei", "entity_type": "person", "color": "#e74c3c", "size": 11.9, "domain": "cybersecurity", "status": "active", "risk_score": 78, "country": "CN", "aliases": ["ghost_dragon"], "first_seen": "2023-09-01", "last_seen": "2025-10-15", "role": "developer"}}, {"key": "person_003", "attributes": {"label": "Andrei Volkov", "entity_type": "person", "color": "#e74c3c", "size": 10.475, "domain": "cybersecurity", "status": "active", "risk_score": 65, "country": "UA", "aliases": ["a_v_cyber"], "first_seen": "2024-01-10", "last_seen": "2025-12-01", "role": "money_mule"}}, {"key": "org_001", "attributes": {"label": "Quantum Digital Ltd", "entity_type": "organization", "color": "#3498db", "size": 11.9, "domain": "cybersecurity", "status": "active", "jurisdiction": "BVI", "registered_date": "2023-03-22", "risk_score": 88}}, {"key": "org_002", "attributes": {"label": "NovaTech Solutions", "entity_type": "organization", "color": "#3498db", "size": 10.55, "domain": "cybersecurity", "status": "active", "jurisdiction": "CY", "registered_date": "2022-11-05", "risk_score": 72}}, {"key": "ip_001", "attributes": {"label": "C2 Primary", "entity_type": "ip_address", "color": "#2ecc71", "size": 12.725000000000001, "domain": "cybersecurity", "status": "active", "address": "185.220.101.42", "asn": "AS9009", "country": "NL", "first_seen": "2023-08-15", "last_seen": "2025-11-30", "risk_score": 95}}, {"key": "ip_002", "attributes": {"label": "C2 Backup", "entity_type": "ip_address", "color": "#2ecc71", "size": 12.2, "domain": "cybersecurity", "status": "active", "address": "91.215.85.17", "asn": "AS48693", "country": "RU", "first_seen": "2024-02-01", "last_seen": "2025-10-22", "risk_score": 90}}, {"key": "ip_003", "attributes": {"label": "Proxy Node", "entity_type": "ip_address", "color": "#2ecc71", "size": 9.65, "domain": "cybersecurity", "status": "active", "address": "45.33.32.156", "asn": "AS63949", "country": "US", "first_seen": "2024-05-10", "last_seen": "2025-09-15", "risk_score": 60}}, {"key": "domain_001", "attributes": {"label": "secure-update.xyz", "entity_type": "domain", "color": "#f39c12", "size": 12.425, "domain": "cybersecurity", "status": "active", "registrar": "NameCheap", "created_date": "2024-01-15", "category": "c2", "risk_score": 95}}, {"key": "domain_002", "attributes": {"label": "cloud-services-auth.com", "entity_type": "domain", "color": "#f39c12", "size": 11.9, "domain": "cybersecurity", "status": "active", "registrar": "Njalla", "created_date": "2024-06-20", "category": "phishing", "risk_score": 88}}, {"key": "domain_003", "attributes": {"label": "fileshare-cdn.net", "entity_type": "domain", "color": "#f39c12", "size": 11.45, "domain": "cybersecurity", "status": "active", "registrar": "NameSilo", "created_date": "2023-11-03", "category": "payload_delivery", "risk_score": 82}}, {"key": "wallet_001", "attributes": {"label": "APT Primary BTC", "entity_type": "crypto_wallet", "color": "#f1c40f", "size": 12.05, "domain": "cybersecurity", "status": "active", "currency": "BTC", "address": "1oBRNtq3FaBNfBMohNkxAxrVK7VFF1mxth", "balance": 2.45, "first_tx": "2023-07-20", "last_tx": "2025-11-15", "risk_score": 90}}, {"key": "wallet_002", "attributes": {"label": "Mixer Output 1", "entity_type": "crypto_wallet", "color": "#f1c40f", "size": 10.925, "domain": "cybersecurity", "status": "active", "currency": "BTC", "address": "18Xizm1jrCPSEPFSnXXNR6omKtKY9oiqzw", "balance": 0.78, "first_tx": "2024-01-10", "last_tx": "2025-08-22", "risk_score": 75}}, {"key": "wallet_003", "attributes": {"label": "ETH Laundering", "entity_type": "crypto_wallet", "color": "#f1c40f", "size": 11.825, "domain": "cybersecurity", "status": "active", "currency": "ETH", "address": "0x30543fe5a6e743849e63f8101ac616f8fd0ba84a", "balance": 15.3, "first_tx": "2024-03-05", "last_tx": "2025-12-01", "risk_score": 85}}, {"key": "malware_001", "attributes": {"label": "ShadowRAT v3", "entity_type": "malware", "color": "#c0392b", "size": 12.35, "domain": "cybersecurity", "status": "active", "family": "RAT", "hash_sha256": "b9c5425e328e78ae760ac1111c3ce251d03e48de25dc30bf9491ffd61e26855f", "first_seen": "2023-08-20", "detection_rate": 0.35, "risk_score": 92}}, {"key": "malware_002", "attributes": {"label": "CryptoStealer", "entity_type": "malware", "color": "#c0392b", "size": 12.05, "domain": "cybersecurity", "status": "active", "family": "stealer", "hash_sha256": "6656d618dc437b7909ef61f44780fc1013b51638744b6fac5e3ce97b08cd4b7a", "first_seen": "2024-04-12", "detection_rate": 0.22, "risk_score": 88}}, {"key": "vuln_001", "attributes": {"label": "CVE-2024-3400", "entity_type": "vulnerability", "color": "#e67e22", "size": 12.5, "domain": "cybersecurity", "status": "active", "cvss": 9.8, "affected_product": "PAN-OS", "patch_available": true, "exploited_in_wild": true, "risk_score": 98}}, {"key": "vuln_002", "attributes": {"label": "CVE-2024-21887", "entity_type": "vulnerability", "color": "#e67e22", "size": 11.525, "domain": "cybersecurity", "status": "active", "cvss": 8.2, "affected_product": "Ivanti Connect Secure", "patch_available": true, "exploited_in_wild": true, "risk_score": 85}}, {"key": "email_001", "attributes": {"label": "vp_shadow@proton.me", "entity_type": "email", "color": "#1abc9c", "size": 10.399999999999999, "domain": "cybersecurity", "status": "active", "provider": "protonmail", "verified": true, "associated_breaches": 0, "risk_score": 70}}, {"key": "email_002", "attributes": {"label": "ghost.dragon@tutanota.com", "entity_type": "email", "color": "#1abc9c", "size": 11.15, "domain": "cybersecurity", "status": "active", "provider": "tutanota", "verified": false, "associated_breaches": 2, "risk_score": 80}}, {"key": "person_004", "attributes": {"label": "Sarah Chen", "entity_type": "person", "color": "#e74c3c", "size": 11.3, "domain": "finance", "status": "active", "risk_score": 70, "country": "US", "aliases": ["s_chen_insider"], "first_seen": "2024-02-15", "last_seen": "2025-12-01", "role": "insider"}}, {"key": "person_005", "attributes": {"label": "Marcus Webb", "entity_type": "person", "color": "#e74c3c", "size": 12.65, "domain": "finance", "status": "active", "risk_score": 82, "country": "UK", "aliases": ["m_webb_trades"], "first_seen": "2024-03-01", "last_seen": "2025-11-28", "role": "trader"}}, {"key": "person_006", "attributes": {"label": "Dmitri Sokolov", "entity_type": "person", "color": "#e74c3c", "size": 11.225, "domain": "finance", "status": "active", "risk_score": 75, "country": "RU", "aliases": ["d_sok"], "first_seen": "2024-01-20", "last_seen": "2025-11-30", "role": "facilitator"}}, {"key": "org_003", "attributes": {"label": "Apex Capital Partners", "entity_type": "organization", "color": "#3498db", "size": 9.275, "domain": "finance", "status": "active", "jurisdiction": "UK", "registered_date": "2019-06-15", "risk_score": 55, "aum_millions": 340}}, {"key": "org_004", "attributes": {"label": "Pacific Rim Brokers", "entity_type": "organization", "color": "#3498db", "size": 9.95, "domain": "finance", "status": "active", "jurisdiction": "HK", "registered_date": "2021-02-10", "risk_score": 62}}, {"key": "domain_004", "attributes": {"label": "apex-secure-comms.io", "entity_type": "domain", "color": "#f39c12", "size": 8.675, "domain": "finance", "status": "active", "registrar": "Cloudflare", "created_date": "2024-04-01", "category": "communications", "risk_score": 45}}, {"key": "wallet_004", "attributes": {"label": "Trading Fund BTC", "entity_type": "crypto_wallet", "color": "#f1c40f", "size": 9.425, "domain": "finance", "status": "active", "currency": "BTC", "address": "1Gbc72RfL42TomJvhwgAUB52zTD26EeEvv", "balance": 8.2, "first_tx": "2024-04-10", "last_tx": "2025-11-25", "risk_score": 55}}, {"key": "wallet_005", "attributes": {"label": "Webb Personal ETH", "entity_type": "crypto_wallet", "color": "#f1c40f", "size": 8.75, "domain": "finance", "status": "active", "currency": "ETH", "address": "0x2784d638b34fa781359331a3ed13a32e97cc3166", "balance": 42.7, "first_tx": "2024-05-01", "last_tx": "2025-12-01", "risk_score": 48}}, {"key": "ip_004", "attributes": {"label": "Trading VPN", "entity_type": "ip_address", "color": "#2ecc71", "size": 7.925, "domain": "finance", "status": "active", "address": "104.21.45.89", "asn": "AS13335", "country": "US", "first_seen": "2024-06-01", "last_seen": "2025-11-30", "risk_score": 35}}, {"key": "signal_001", "attributes": {"label": "SOL-USD long signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 7.1, "domain": "finance", "status": "active", "symbol": "SOL-USD", "direction": "long", "confidence": 0.44, "timestamp": "2026-01-15", "pnl_percent": 5.4, "risk_score": 26}}, {"key": "signal_002", "attributes": {"label": "MSFT long signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 7.925000000000001, "domain": "finance", "status": "active", "symbol": "MSFT", "direction": "long", "confidence": 0.71, "timestamp": "2024-05-23", "pnl_percent": 10.3, "risk_score": 37}}, {"key": "signal_003", "attributes": {"label": "SOL-USD long signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 9.725, "domain": "finance", "status": "active", "symbol": "SOL-USD", "direction": "long", "confidence": 0.66, "timestamp": "2025-07-17", "pnl_percent": -9.1, "risk_score": 61}}, {"key": "signal_004", "attributes": {"label": "AAPL long signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 8.525, "domain": "finance", "status": "active", "symbol": "AAPL", "direction": "long", "confidence": 0.5, "timestamp": "2025-05-12", "pnl_percent": 13.9, "risk_score": 45}}, {"key": "signal_005", "attributes": {"label": "AAPL long signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 10.399999999999999, "domain": "finance", "status": "active", "symbol": "AAPL", "direction": "short", "confidence": 0.4, "timestamp": "2025-02-03", "pnl_percent": 0.9, "risk_score": 70}}, {"key": "signal_006", "attributes": {"label": "SOL-USD short signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 9.275, "domain": "finance", "status": "active", "symbol": "SOL-USD", "direction": "short", "confidence": 0.8, "timestamp": "2026-01-18", "pnl_percent": 44.7, "risk_score": 55}}, {"key": "signal_007", "attributes": {"label": "SOL-USD long signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 9.2, "domain": "finance", "status": "active", "symbol": "SOL-USD", "direction": "long", "confidence": 0.57, "timestamp": "2024-02-29", "pnl_percent": 19.8, "risk_score": 54}}, {"key": "signal_008", "attributes": {"label": "AAPL short signal", "entity_type": "trading_signal", "color": "#9b59b6", "size": 9.125, "domain": "finance", "status": "active", "symbol": "AAPL", "direction": "long", "confidence": 0.43, "timestamp": "2025-05-03", "pnl_percent": 15.2, "risk_score": 53}}, {"key": "wallet_bridge", "attributes": {"label": "Bridge Wallet BTC", "entity_type": "crypto_wallet", "color": "#f1c40f", "size": 11.3, "domain": "cybersecurity", "status": "active", "currency": "BTC", "address": "1K7ELkqPG3Qkmajg6MsPYpTUuEc99ENR63", "balance": 0.15, "first_tx": "2024-09-01", "last_tx": "2025-11-10", "risk_score": 78, "note": "Links APT laundering to insider trading payments"}}], "edges": [{"key": "rel_001", "source": "person_001", "target": "domain_001", "attributes": {"label": "operates", "weight": 0.95, "type": "arrow"}}, {"key": "rel_002", "source": "person_001", "target": "domain_002", "attributes": {"label": "operates", "weight": 0.85, "type": "arrow"}}, {"key": "rel_003", "source": "person_002", "target": "domain_003", "attributes": {"label": "operates", "weight": 0.9, "type": "arrow"}}, {"key": "rel_004", "source": "person_001", "target": "ip_001", "attributes": {"label": "controls", "weight": 0.92, "type": "arrow"}}, {"key": "rel_005", "source": "person_001", "target": "ip_002", "attributes": {"label": "controls", "weight": 0.88, "type": "arrow"}}, {"key": "rel_006", "source": "person_002", "target": "ip_003", "attributes": {"label": "uses", "weight": 0.7, "type": "arrow"}}, {"key": "rel_007", "source": "domain_001", "target": "ip_001", "attributes": {"label": "resolves_to", "weight": 1.0, "type": "arrow"}}, {"key": "rel_008", "source": "domain_002", "target": "ip_001", "attributes": {"label": "resolves_to", "weight": 1.0, "type": "arrow"}}, {"key": "rel_009", "source": "domain_003", "target": "ip_002", "attributes": {"label": "resolves_to", "weight": 1.0, "type": "arrow"}}, {"key": "rel_010", "source": "ip_001", "target": "malware_001", "attributes": {"label": "hosts", "weight": 0.95, "type": "arrow"}}, {"key": "rel_011", "source": "ip_002", "target": "malware_002", "attributes": {"label": "hosts", "weight": 0.9, "type": "arrow"}}, {"key": "rel_012", "source": "person_002", "target": "malware_001", "attributes": {"label": "develops", "weight": 0.85, "type": "arrow"}}, {"key": "rel_013", "source": "person_002", "target": "malware_002", "attributes": {"label": "develops", "weight": 0.8, "type": "arrow"}}, {"key": "rel_014", "source": "malware_001", "target": "vuln_001", "attributes": {"label": "exploits", "weight": 0.95, "type": "arrow"}}, {"key": "rel_015", "source": "malware_002", "target": "vuln_002", "attributes": {"label": "exploits", "weight": 0.88, "type": "arrow"}}, {"key": "rel_016", "source": "person_001", "target": "wallet_001", "attributes": {"label": "owns", "weight": 0.9, "type": "arrow"}}, {"key": "rel_017", "source": "wallet_001", "target": "wallet_002", "attributes": {"label": "transfers_to", "weight": 0.95, "type": "arrow"}}, {"key": "rel_018", "source": "wallet_002", "target": "wallet_003", "attributes": {"label": "transfers_to", "weight": 0.92, "type": "arrow"}}, {"key": "rel_019", "source": "wallet_003", "target": "wallet_bridge", "attributes": {"label": "transfers_to", "weight": 0.85, "type": "arrow"}}, {"key": "rel_020", "source": "person_003", "target": "wallet_003", "attributes": {"label": "owns", "weight": 0.75, "type": "arrow"}}, {"key": "rel_021", "source": "org_001", "target": "person_001", "attributes": {"label": "employs", "weight": 0.8, "type": "arrow"}}, {"key": "rel_022", "source": "org_002", "target": "person_002", "attributes": {"label": "employs", "weight": 0.75, "type": "arrow"}}, {"key": "rel_023", "source": "org_001", "target": "person_003", "attributes": {"label": "employs", "weight": 0.7, "type": "arrow"}}, {"key": "rel_024", "source": "person_001", "target": "person_002", "attributes": {"label": "communicates_with", "weight": 0.95, "type": "arrow"}}, {"key": "rel_025", "source": "person_001", "target": "person_003", "attributes": {"label": "communicates_with", "weight": 0.8, "type": "arrow"}}, {"key": "rel_026", "source": "person_001", "target": "email_001", "attributes": {"label": "uses_email", "weight": 0.95, "type": "arrow"}}, {"key": "rel_027", "source": "person_002", "target": "email_002", "attributes": {"label": "uses_email", "weight": 0.9, "type": "arrow"}}, {"key": "rel_028", "source": "org_003", "target": "person_004", "attributes": {"label": "employs", "weight": 0.95, "type": "arrow"}}, {"key": "rel_029", "source": "org_004", "target": "person_005", "attributes": {"label": "employs", "weight": 0.9, "type": "arrow"}}, {"key": "rel_030", "source": "person_004", "target": "person_005", "attributes": {"label": "communicates_with", "weight": 0.88, "type": "arrow"}}, {"key": "rel_031", "source": "person_005", "target": "person_006", "attributes": {"label": "communicates_with", "weight": 0.82, "type": "arrow"}}, {"key": "rel_032", "source": "person_006", "target": "org_004", "attributes": {"label": "facilitates", "weight": 0.78, "type": "arrow"}}, {"key": "rel_033", "source": "person_005", "target": "wallet_004", "attributes": {"label": "owns", "weight": 0.9, "type": "arrow"}}, {"key": "rel_034", "source": "person_005", "target": "wallet_005", "attributes": {"label": "owns", "weight": 0.95, "type": "arrow"}}, {"key": "rel_035", "source": "person_005", "target": "domain_004", "attributes": {"label": "uses", "weight": 0.85, "type": "arrow"}}, {"key": "rel_036", "source": "person_005", "target": "ip_004", "attributes": {"label": "uses", "weight": 0.8, "type": "arrow"}}, {"key": "rel_037", "source": "domain_004", "target": "ip_004", "attributes": {"label": "resolves_to", "weight": 1.0, "type": "arrow"}}, {"key": "rel_038", "source": "wallet_bridge", "target": "wallet_004", "attributes": {"label": "transfers_to", "weight": 0.72, "type": "arrow"}}, {"key": "rel_039", "source": "person_003", "target": "person_006", "attributes": {"label": "communicates_with", "weight": 0.65, "type": "arrow"}}, {"key": "rel_040", "source": "person_006", "target": "wallet_bridge", "attributes": {"label": "owns", "weight": 0.7, "type": "arrow"}}, {"key": "rel_sig_001", "source": "person_005", "target": "signal_001", "attributes": {"label": "generates", "weight": 0.66, "type": "arrow"}}, {"key": "rel_sig_002", "source": "person_005", "target": "signal_002", "attributes": {"label": "generates", "weight": 0.81, "type": "arrow"}}, {"key": "rel_sig_003", "source": "person_004", "target": "signal_003", "attributes": {"label": "generates", "weight": 0.83, "type": "arrow"}}, {"key": "rel_sig_004", "source": "person_005", "target": "signal_004", "attributes": {"label": "generates", "weight": 0.93, "type": "arrow"}}, {"key": "rel_sig_005", "source": "person_004", "target": "signal_005", "attributes": {"label": "generates", "weight": 0.93, "type": "arrow"}}, {"key": "rel_sig_006", "source": "person_004", "target": "signal_006", "attributes": {"label": "generates", "weight": 0.69, "type": "arrow"}}, {"key": "rel_sig_007", "source": "person_004", "target": "signal_007", "attributes": {"label": "generates", "weight": 0.86, "type": "arrow"}}, {"key": "rel_sig_008", "source": "person_004", "target": "signal_008", "attributes": {"label": "generates", "weight": 0.7, "type": "arrow"}}]};
|
|
|
|
// ── Build graphology graph ──────────────────────────────────────────────
|
|
const Graph = graphology.Graph || graphology;
|
|
const g = new Graph({ multi: true, type: 'directed' });
|
|
|
|
// Assign random initial positions
|
|
graphData.nodes.forEach(function (n) {
|
|
g.addNode(n.key, Object.assign({
|
|
x: (Math.random() - 0.5) * 10,
|
|
y: (Math.random() - 0.5) * 10,
|
|
}, n.attributes));
|
|
});
|
|
|
|
graphData.edges.forEach(function (e) {
|
|
try {
|
|
g.addEdgeWithKey(e.key, e.source, e.target, e.attributes || {});
|
|
} catch (err) {
|
|
// skip duplicate edge keys gracefully
|
|
}
|
|
});
|
|
|
|
// ── ForceAtlas2 layout (synchronous, 500 iterations) ───────────────────
|
|
const FA2 = graphologyLibrary.layoutForceAtlas2;
|
|
FA2.assign(g, {
|
|
iterations: 500,
|
|
settings: {
|
|
gravity: 1,
|
|
scalingRatio: 2,
|
|
slowDown: 5,
|
|
barnesHutOptimize: g.order > 300,
|
|
},
|
|
});
|
|
|
|
// ── Sigma renderer ──────────────────────────────────────────────────────
|
|
const renderer = new Sigma(g, document.getElementById('container'), {
|
|
renderEdgeLabels: false,
|
|
defaultEdgeColor: '#444',
|
|
defaultNodeColor: '#95a5a6',
|
|
labelColor: { color: '#ccc' },
|
|
labelSize: 11,
|
|
edgeReducer: function (edge, data) {
|
|
return Object.assign({}, data, { size: Math.max(1, (data.weight || 1) * 0.8) });
|
|
},
|
|
});
|
|
|
|
// ── Stats panel ─────────────────────────────────────────────────────────
|
|
document.getElementById('stats').textContent =
|
|
graphData.nodes.length + ' nodes · ' + graphData.edges.length + ' edges';
|
|
|
|
// ── Filter panel by node type ───────────────────────────────────────────
|
|
const typeColors = {};
|
|
graphData.nodes.forEach(function (n) {
|
|
const t = n.attributes.entity_type || 'unknown';
|
|
typeColors[t] = n.attributes.color || '#95a5a6';
|
|
});
|
|
|
|
const hiddenTypes = new Set();
|
|
const filtersDiv = document.getElementById('filters');
|
|
|
|
Object.keys(typeColors).sort().forEach(function (type) {
|
|
const color = typeColors[type];
|
|
const label = document.createElement('label');
|
|
label.className = 'filter-item';
|
|
|
|
const cb = document.createElement('input');
|
|
cb.type = 'checkbox';
|
|
cb.checked = true;
|
|
cb.addEventListener('change', function () {
|
|
if (cb.checked) hiddenTypes.delete(type);
|
|
else hiddenTypes.add(type);
|
|
renderer.refresh();
|
|
});
|
|
|
|
const dot = document.createElement('span');
|
|
dot.className = 'color-dot';
|
|
dot.style.background = color;
|
|
|
|
label.appendChild(cb);
|
|
label.appendChild(dot);
|
|
label.appendChild(document.createTextNode(type));
|
|
filtersDiv.appendChild(label);
|
|
});
|
|
|
|
// Node reducer applies type filter
|
|
renderer.setSetting('nodeReducer', function (node, data) {
|
|
if (hiddenTypes.has(data.entity_type)) return Object.assign({}, data, { hidden: true });
|
|
return data;
|
|
});
|
|
|
|
// ── Tooltip on hover ────────────────────────────────────────────────────
|
|
const tooltip = document.getElementById('tooltip');
|
|
|
|
renderer.on('enterNode', function (ref) {
|
|
const nodeAttrs = g.getNodeAttributes(ref.node);
|
|
const reserved = new Set(['x', 'y', 'size', 'color', 'label', 'type', 'hidden']);
|
|
|
|
let html = '<div class="tt-title">' + escHtml(nodeAttrs.label || ref.node) + '</div>';
|
|
html += '<div class="tt-row"><span class="tt-key">type</span><span class="tt-val">' + escHtml(nodeAttrs.entity_type || '') + '</span></div>';
|
|
html += '<div class="tt-row"><span class="tt-key">status</span><span class="tt-val">' + escHtml(nodeAttrs.status || '') + '</span></div>';
|
|
html += '<div class="tt-row"><span class="tt-key">domain</span><span class="tt-val">' + escHtml(nodeAttrs.domain || '') + '</span></div>';
|
|
|
|
Object.keys(nodeAttrs).sort().forEach(function (k) {
|
|
if (!reserved.has(k) && !['status', 'domain', 'type', 'label'].includes(k)) {
|
|
html += '<div class="tt-row"><span class="tt-key">' + escHtml(k) + '</span><span class="tt-val">' + escHtml(String(nodeAttrs[k])) + '</span></div>';
|
|
}
|
|
});
|
|
|
|
tooltip.innerHTML = html;
|
|
tooltip.style.display = 'block';
|
|
});
|
|
|
|
renderer.on('leaveNode', function () {
|
|
tooltip.style.display = 'none';
|
|
});
|
|
|
|
document.getElementById('container').addEventListener('mousemove', function (e) {
|
|
tooltip.style.left = (e.clientX + 16) + 'px';
|
|
tooltip.style.top = (e.clientY + 16) + 'px';
|
|
});
|
|
|
|
function escHtml(str) {
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|