Download network.js from secutorpro/gdorks-web-generator-7cmse: direct link, hf CLI and curl.
- Browser
- Download file 28.2 kB
-
https://huggingface.co/spaces/secutorpro/gdorks-web-generator-7cmse/resolve/main/network.js
- Command line
-
hf download hf://spaces/secutorpro/gdorks-web-generator-7cmse/network.js
-
curl -L -o network.js https://huggingface.co/spaces/secutorpro/gdorks-web-generator-7cmse/resolve/main/network.js
28.2 kB
| // ===================================================== | |
| // SentinelAI Network Page Controller | |
| // ===================================================== | |
| // ---- Network Nodes ---- | |
| const networkNodes = [ | |
| { id: 'internet', name: 'Internet', ip: '0.0.0.0/0', type: 'cloud', status: 'online', x: 0.5, y: 0.08, icon: 'globe' }, | |
| { id: 'router', name: 'Core Router', ip: '10.0.0.1', type: 'router', status: 'online', x: 0.5, y: 0.22, icon: 'router' }, | |
| { id: 'firewall', name: 'Firewall', ip: '10.0.0.2', type: 'firewall', status: 'online', x: 0.5, y: 0.36, icon: 'shield' }, | |
| { id: 'switch1', name: 'Switch-A', ip: '10.0.1.1', type: 'switch', status: 'online', x: 0.25, y: 0.52, icon: 'network' }, | |
| { id: 'switch2', name: 'Switch-B', ip: '10.0.2.1', type: 'switch', status: 'online', x: 0.75, y: 0.52, icon: 'network' }, | |
| { id: 'lb', name: 'Load Balancer', ip: '10.0.0.50', type: 'lb', status: 'online', x: 0.5, y: 0.52, icon: 'shuffle' }, | |
| { id: 'web1', name: 'Web-01', ip: '10.0.1.15', type: 'server', status: 'online', x: 0.12, y: 0.72, icon: 'server' }, | |
| { id: 'web2', name: 'Web-02', ip: '10.0.1.16', type: 'server', status: 'online', x: 0.38, y: 0.72, icon: 'server' }, | |
| { id: 'db1', name: 'DB-Master', ip: '10.0.2.10', type: 'database', status: 'warning', x: 0.62, y: 0.72, icon: 'database' }, | |
| { id: 'db2', name: 'DB-Slave', ip: '10.0.2.11', type: 'database', status: 'online', x: 0.88, y: 0.72, icon: 'database' }, | |
| { id: 'api', name: 'API-GW', ip: '10.0.1.50', type: 'server', status: 'online', x: 0.5, y: 0.88, icon: 'code' }, | |
| { id: 'backup', name: 'Backup-SRV', ip: '10.0.3.20', type: 'server', status: 'offline', x: 0.15, y: 0.88, icon: 'archive' }, | |
| ]; | |
| // ---- Network Connections ---- | |
| const networkLinks = [ | |
| { from: 'internet', to: 'router' }, | |
| { from: 'router', to: 'firewall' }, | |
| { from: 'firewall', to: 'switch1' }, | |
| { from: 'firewall', to: 'switch2' }, | |
| { from: 'firewall', to: 'lb' }, | |
| { from: 'switch1', to: 'web1' }, | |
| { from: 'switch1', to: 'web2' }, | |
| { from: 'switch2', to: 'db1' }, | |
| { from: 'switch2', to: 'db2' }, | |
| { from: 'lb', to: 'web1' }, | |
| { from: 'lb', to: 'web2' }, | |
| { from: 'lb', to: 'api' }, | |
| { from: 'web1', to: 'api' }, | |
| { from: 'api', to: 'db1' }, | |
| { from: 'switch1', to: 'backup' }, | |
| ]; | |
| // ---- Port Scan Data ---- | |
| const portScanData = [ | |
| { asset: 'Web-01', ip: '10.0.1.15', port: 22, service: 'SSH', proto: 'TCP', state: 'open', banner: 'OpenSSH 9.0' }, | |
| { asset: 'Web-01', ip: '10.0.1.15', port: 80, service: 'HTTP', proto: 'TCP', state: 'open', banner: 'nginx 1.25.3' }, | |
| { asset: 'Web-01', ip: '10.0.1.15', port: 443, service: 'HTTPS', proto: 'TCP', state: 'open', banner: 'nginx 1.25.3' }, | |
| { asset: 'Web-02', ip: '10.0.1.16', port: 80, service: 'HTTP', proto: 'TCP', state: 'open', banner: 'nginx 1.25.3' }, | |
| { asset: 'Web-02', ip: '10.0.1.16', port: 443, service: 'HTTPS', proto: 'TCP', state: 'open', banner: 'nginx 1.25.3' }, | |
| { asset: 'DB-Master', ip: '10.0.2.10', port: 5432, service: 'PostgreSQL', proto: 'TCP', state: 'open', banner: 'PostgreSQL 16.1' }, | |
| { asset: 'DB-Slave', ip: '10.0.2.11', port: 5432, service: 'PostgreSQL', proto: 'TCP', state: 'open', banner: 'PostgreSQL 16.1' }, | |
| { asset: 'API-GW', ip: '10.0.1.50', port: 8080, service: 'HTTP-Proxy', proto: 'TCP', state: 'open', banner: 'Kong 3.4' }, | |
| { asset: 'API-GW', ip: '10.0.1.50', port: 8443, service: 'HTTPS-Alt', proto: 'TCP', state: 'open', banner: 'Kong 3.4' }, | |
| { asset: 'Core Router', ip: '10.0.0.1', port: 22, service: 'SSH', proto: 'TCP', state: 'filtered', banner: '—' }, | |
| { asset: 'Core Router', ip: '10.0.0.1', port: 161, service: 'SNMP', proto: 'UDP', state: 'open', banner: 'Cisco IOS 15.7' }, | |
| { asset: 'Firewall', ip: '10.0.0.2', port: 443, service: 'HTTPS', proto: 'TCP', state: 'open', banner: 'pfSense 2.7.2' }, | |
| { asset: 'Backup-SRV', ip: '10.0.3.20', port: 22, service: 'SSH', proto: 'TCP', state: 'closed', banner: '—' }, | |
| { asset: 'Backup-SRV', ip: '10.0.3.20', port: 873, service: 'rsync', proto: 'TCP', state: 'closed', banner: '—' }, | |
| { asset: 'Load Balancer', ip: '10.0.0.50', port: 80, service: 'HTTP', proto: 'TCP', state: 'open', banner: 'HAProxy 2.8' }, | |
| ]; | |
| // ---- Connection Templates ---- | |
| const connTemplates = [ | |
| { proto: 'TCP', state: 'ESTABLISHED' }, | |
| { proto: 'TCP', state: 'TIME_WAIT' }, | |
| { proto: 'UDP', state: 'ESTABLISHED' }, | |
| { proto: 'TCP', state: 'CLOSE_WAIT' }, | |
| { proto: 'TCP', state: 'ESTABLISHED' }, | |
| ]; | |
| let extIPs = []; | |
| // ---- State ---- | |
| let trafficDataIn = []; | |
| let trafficDataOut = []; | |
| let activeConnections = []; | |
| let packetsPerSec = 0; | |
| let totalInGB = 0; | |
| let totalOutGB = 0; | |
| let peakRate = 0; | |
| let topologyAnimId = null; | |
| let packets = []; | |
| let hoveredNode = null; | |
| let canvasScale = 1; | |
| let canvasOffsetX = 0; | |
| let canvasOffsetY = 0; | |
| // ---- Initialize ---- | |
| document.addEventListener('DOMContentLoaded', async () => { | |
| lucide.createIcons(); | |
| initStats(); | |
| initTrafficData(); | |
| initConnections(); | |
| renderNodeStatus(); | |
| renderPortScan(); | |
| initTopology(); | |
| initTrafficChart(); | |
| startClock(); | |
| await startRealMonitoring(); | |
| }); | |
| // ---- Clock ---- | |
| function startClock() { | |
| function update() { | |
| const now = new Date(); | |
| const timeEl = document.getElementById('systemTime'); | |
| if (timeEl) timeEl.textContent = now.toUTCString().split(' ')[4] + ' UTC'; | |
| } | |
| update(); | |
| setInterval(update, 1000); | |
| } | |
| // ---- Counter Animation ---- | |
| function animateCounter(el, target, duration = 1200) { | |
| if (!el) return; | |
| const start = parseInt(el.textContent) || 0; | |
| const startTime = performance.now(); | |
| function step(currentTime) { | |
| const elapsed = currentTime - startTime; | |
| const progress = Math.min(elapsed / duration, 1); | |
| const eased = 1 - Math.pow(1 - progress, 3); | |
| el.textContent = Math.floor(start + (target - start) * eased); | |
| if (progress < 1) requestAnimationFrame(step); | |
| } | |
| requestAnimationFrame(step); | |
| } | |
| // ---- Init Stats ---- | |
| function initStats() { | |
| const online = networkNodes.filter(n => n.status === 'online').length; | |
| const total = networkNodes.length; | |
| animateCounter(document.getElementById('nodesOnline'), online); | |
| const nodesTotalEl = document.getElementById('nodesTotal'); | |
| if (nodesTotalEl) nodesTotalEl.textContent = total; | |
| const nodeStatusCountEl = document.getElementById('nodeStatusCount'); | |
| if (nodeStatusCountEl) nodeStatusCountEl.textContent = `${online}/${total}`; | |
| const bw = (Math.random() * 5 + 8).toFixed(1); | |
| const bwEl = document.getElementById('bandwidthVal'); | |
| if (bwEl) bwEl.textContent = bw; | |
| const latency = Math.floor(Math.random() * 10 + 12); | |
| const latEl = document.getElementById('latencyVal'); | |
| if (latEl) latEl.textContent = latency; | |
| const conns = Math.floor(Math.random() * 50 + 180); | |
| animateCounter(document.getElementById('connCount'), conns); | |
| } | |
| // ---- Traffic Data ---- | |
| function initTrafficData() { | |
| for (let i = 0; i < 60; i++) { | |
| trafficDataIn.push(Math.random() * 30 + 20); | |
| trafficDataOut.push(Math.random() * 20 + 10); | |
| } | |
| totalInGB = Math.floor(Math.random() * 200 + 800); | |
| totalOutGB = Math.floor(Math.random() * 100 + 400); | |
| peakRate = (Math.random() * 3 + 12).toFixed(1); | |
| updateTrafficStats(); | |
| } | |
| function updateTrafficStats() { | |
| const totalInEl = document.getElementById('totalIn'); | |
| const totalOutEl = document.getElementById('totalOut'); | |
| const peakRateEl = document.getElementById('peakRate'); | |
| if (totalInEl) totalInEl.textContent = `${totalInGB} GB`; | |
| if (totalOutEl) totalOutEl.textContent = `${totalOutGB} GB`; | |
| if (peakRateEl) peakRateEl.textContent = `${peakRate} Gbps`; | |
| } | |
| // ---- Traffic Chart ---- | |
| function initTrafficChart() { | |
| drawTrafficChart(); | |
| setInterval(() => { | |
| trafficDataIn.shift(); | |
| trafficDataOut.shift(); | |
| const newIn = Math.random() * 35 + 15; | |
| const newOut = Math.random() * 25 + 8; | |
| trafficDataIn.push(newIn); | |
| trafficDataOut.push(newOut); | |
| totalInGB += newIn / 100; | |
| totalOutGB += newOut / 100; | |
| if (newIn > peakRate) peakRate = newIn.toFixed(1); | |
| drawTrafficChart(); | |
| updateTrafficStats(); | |
| }, 2000); | |
| } | |
| function drawTrafficChart() { | |
| const canvas = document.getElementById('trafficChart'); | |
| if (!canvas) return; | |
| const rect = canvas.parentElement.getBoundingClientRect(); | |
| canvas.width = rect.width; | |
| canvas.height = 200; | |
| const ctx = canvas.getContext('2d'); | |
| const w = canvas.width; | |
| const h = canvas.height; | |
| const dataIn = trafficDataIn; | |
| const dataOut = trafficDataOut; | |
| const maxVal = Math.max(...dataIn, ...dataOut, 50); | |
| const stepX = w / (dataIn.length - 1); | |
| ctx.clearRect(0, 0, w, h); | |
| // Grid lines | |
| ctx.strokeStyle = 'rgba(30, 41, 59, 0.5)'; | |
| ctx.lineWidth = 1; | |
| for (let i = 0; i < 4; i++) { | |
| const y = (h / 4) * i; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, y); | |
| ctx.lineTo(w, y); | |
| ctx.stroke(); | |
| } | |
| // Inbound fill | |
| const gradIn = ctx.createLinearGradient(0, 0, 0, h); | |
| gradIn.addColorStop(0, 'rgba(6, 182, 212, 0.3)'); | |
| gradIn.addColorStop(1, 'rgba(6, 182, 212, 0)'); | |
| ctx.beginPath(); | |
| ctx.moveTo(0, h); | |
| dataIn.forEach((val, i) => { | |
| const x = i * stepX; | |
| const y = h - (val / maxVal) * (h - 10) - 5; | |
| ctx.lineTo(x, y); | |
| }); | |
| ctx.lineTo(w, h); | |
| ctx.closePath(); | |
| ctx.fillStyle = gradIn; | |
| ctx.fill(); | |
| // Inbound line | |
| ctx.beginPath(); | |
| dataIn.forEach((val, i) => { | |
| const x = i * stepX; | |
| const y = h - (val / maxVal) * (h - 10) - 5; | |
| if (i === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| }); | |
| ctx.strokeStyle = 'rgba(6, 182, 212, 0.8)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.stroke(); | |
| // Outbound fill | |
| const gradOut = ctx.createLinearGradient(0, 0, 0, h); | |
| gradOut.addColorStop(0, 'rgba(168, 85, 247, 0.3)'); | |
| gradOut.addColorStop(1, 'rgba(168, 85, 247, 0)'); | |
| ctx.beginPath(); | |
| ctx.moveTo(0, h); | |
| dataOut.forEach((val, i) => { | |
| const x = i * stepX; | |
| const y = h - (val / maxVal) * (h - 10) - 5; | |
| ctx.lineTo(x, y); | |
| }); | |
| ctx.lineTo(w, h); | |
| ctx.closePath(); | |
| ctx.fillStyle = gradOut; | |
| ctx.fill(); | |
| // Outbound line | |
| ctx.beginPath(); | |
| dataOut.forEach((val, i) => { | |
| const x = i * stepX; | |
| const y = h - (val / maxVal) * (h - 10) - 5; | |
| if (i === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| }); | |
| ctx.strokeStyle = 'rgba(168, 85, 247, 0.8)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.stroke(); | |
| } | |
| // ---- Network Topology Canvas ---- | |
| function initTopology() { | |
| const canvas = document.getElementById('topologyCanvas'); | |
| if (!canvas) return; | |
| function resize() { | |
| const rect = canvas.parentElement.getBoundingClientRect(); | |
| canvas.width = rect.width - 16; | |
| canvas.height = 380; | |
| } | |
| resize(); | |
| window.addEventListener('resize', resize); | |
| // Mouse hover | |
| canvas.addEventListener('mousemove', (e) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| const mx = e.clientX - rect.left; | |
| const my = e.clientY - rect.top; | |
| hoveredNode = null; | |
| for (const node of networkNodes) { | |
| const nx = node.x * canvas.width; | |
| const ny = node.y * canvas.height; | |
| const dist = Math.sqrt((mx - nx) ** 2 + (my - ny) ** 2); | |
| if (dist < 24) { | |
| hoveredNode = node; | |
| break; | |
| } | |
| } | |
| const tooltip = document.getElementById('topologyTooltip'); | |
| if (hoveredNode) { | |
| canvas.style.cursor = 'pointer'; | |
| if (tooltip) { | |
| tooltip.classList.remove('hidden'); | |
| tooltip.style.left = (mx + 12) + 'px'; | |
| tooltip.style.top = (my - 30) + 'px'; | |
| const statusColor = hoveredNode.status === 'online' ? 'text-green-400' : hoveredNode.status === 'warning' ? 'text-amber-400' : 'text-red-400'; | |
| tooltip.innerHTML = ` | |
| <div class="text-white font-semibold">${hoveredNode.name}</div> | |
| <div class="text-slate-400">${hoveredNode.ip}</div> | |
| <div class="${statusColor} uppercase font-bold mt-0.5">${hoveredNode.status}</div> | |
| <div class="text-slate-500 capitalize">${hoveredNode.type}</div> | |
| `; | |
| } | |
| } else { | |
| canvas.style.cursor = 'grab'; | |
| if (tooltip) tooltip.classList.add('hidden'); | |
| } | |
| }); | |
| canvas.addEventListener('mouseleave', () => { | |
| hoveredNode = null; | |
| const tooltip = document.getElementById('topologyTooltip'); | |
| if (tooltip) tooltip.classList.add('hidden'); | |
| }); | |
| // Spawn initial packets | |
| for (let i = 0; i < 15; i++) { | |
| spawnPacket(); | |
| } | |
| animateTopology(); | |
| setInterval(spawnPacket, 800); | |
| } | |
| function spawnPacket() { | |
| if (packets.length > 40) packets.shift(); | |
| const link = networkLinks[Math.floor(Math.random() * networkLinks.length)]; | |
| const fromNode = networkNodes.find(n => n.id === link.from); | |
| const toNode = networkNodes.find(n => n.id === link.to); | |
| if (!fromNode || !toNode) return; | |
| if (fromNode.status === 'offline' || toNode.status === 'offline') return; | |
| packets.push({ | |
| fromId: link.from, | |
| toId: link.to, | |
| progress: 0, | |
| speed: Math.random() * 0.01 + 0.005, | |
| color: Math.random() > 0.5 ? '#22d3ee' : '#a78bfa', | |
| }); | |
| } | |
| function animateTopology() { | |
| const canvas = document.getElementById('topologyCanvas'); | |
| if (!canvas) { | |
| topologyAnimId = requestAnimationFrame(animateTopology); | |
| return; | |
| } | |
| const ctx = canvas.getContext('2d'); | |
| const w = canvas.width; | |
| const h = canvas.height; | |
| ctx.clearRect(0, 0, w, h); | |
| // Draw connections | |
| networkLinks.forEach(link => { | |
| const from = networkNodes.find(n => n.id === link.from); | |
| const to = networkNodes.find(n => n.id === link.to); | |
| if (!from || !to) return; | |
| const fx = from.x * w; | |
| const fy = from.y * h; | |
| const tx = to.x * w; | |
| const ty = to.y * h; | |
| const isOffline = from.status === 'offline' || to.status === 'offline'; | |
| ctx.beginPath(); | |
| ctx.moveTo(fx, fy); | |
| ctx.lineTo(tx, ty); | |
| if (isOffline) { | |
| ctx.strokeStyle = 'rgba(239, 68, 68, 0.15)'; | |
| ctx.setLineDash([4, 6]); | |
| } else { | |
| ctx.strokeStyle = 'rgba(6, 182, 212, 0.15)'; | |
| ctx.setLineDash([]); | |
| } | |
| ctx.lineWidth = 1.5; | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| }); | |
| // Draw and update packets | |
| packets = packets.filter(p => { | |
| p.progress += p.speed; | |
| if (p.progress >= 1) return false; | |
| const from = networkNodes.find(n => n.id === p.fromId); | |
| const to = networkNodes.find(n => n.id === p.toId); | |
| if (!from || !to) return false; | |
| const fx = from.x * w; | |
| const fy = from.y * h; | |
| const tx = to.x * w; | |
| const ty = to.y * h; | |
| const px = fx + (tx - fx) * p.progress; | |
| const py = fy + (ty - fy) * p.progress; | |
| // Glow | |
| ctx.beginPath(); | |
| ctx.arc(px, py, 5, 0, Math.PI * 2); | |
| ctx.fillStyle = p.color + '30'; | |
| ctx.fill(); | |
| // Core | |
| ctx.beginPath(); | |
| ctx.arc(px, py, 2.5, 0, Math.PI * 2); | |
| ctx.fillStyle = p.color; | |
| ctx.fill(); | |
| return true; | |
| }); | |
| // Draw nodes | |
| networkNodes.forEach(node => { | |
| const nx = node.x * w; | |
| const ny = node.y * h; | |
| const isHovered = hoveredNode === node; | |
| const radius = isHovered ? 18 : 14; | |
| // Glow for online nodes | |
| if (node.status === 'online') { | |
| const grad = ctx.createRadialGradient(nx, ny, 0, nx, ny, radius + 12); | |
| grad.addColorStop(0, 'rgba(34, 197, 94, 0.3)'); | |
| grad.addColorStop(1, 'rgba(34, 197, 94, 0)'); | |
| ctx.beginPath(); | |
| ctx.arc(nx, ny, radius + 12, 0, Math.PI * 2); | |
| ctx.fillStyle = grad; | |
| ctx.fill(); | |
| } else if (node.status === 'warning') { | |
| const grad = ctx.createRadialGradient(nx, ny, 0, nx, ny, radius + 12); | |
| grad.addColorStop(0, 'rgba(245, 158, 11, 0.3)'); | |
| grad.addColorStop(1, 'rgba(245, 158, 11, 0)'); | |
| ctx.beginPath(); | |
| ctx.arc(nx, ny, radius + 12, 0, Math.PI * 2); | |
| ctx.fillStyle = grad; | |
| ctx.fill(); | |
| } | |
| // Node circle | |
| ctx.beginPath(); | |
| ctx.arc(nx, ny, radius, 0, Math.PI * 2); | |
| if (node.status === 'online') { | |
| ctx.fillStyle = '#1e293b'; | |
| ctx.strokeStyle = '#22c55e'; | |
| } else if (node.status === 'warning') { | |
| ctx.fillStyle = '#1e293b'; | |
| ctx.strokeStyle = '#f59e0b'; | |
| } else { | |
| ctx.fillStyle = '#0f172a'; | |
| ctx.strokeStyle = '#ef4444'; | |
| } | |
| ctx.lineWidth = 2; | |
| ctx.fill(); | |
| ctx.stroke(); | |
| // Status dot | |
| const dotColor = node.status === 'online' ? '#22c55e' : node.status === 'warning' ? '#f59e0b' : '#ef4444'; | |
| ctx.beginPath(); | |
| ctx.arc(nx, ny, 4, 0, Math.PI * 2); | |
| ctx.fillStyle = dotColor; | |
| ctx.fill(); | |
| // Node label | |
| ctx.fillStyle = '#cbd5e1'; | |
| ctx.font = '11px Inter, sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'top'; | |
| ctx.fillText(node.name, nx, ny + radius + 6); | |
| // IP address (smaller, below name) | |
| ctx.fillStyle = '#475569'; | |
| ctx.font = '9px JetBrains Mono, monospace'; | |
| ctx.fillText(node.ip, nx, ny + radius + 20); | |
| }); | |
| // Update packets per sec counter | |
| packetsPerSec = packets.length; | |
| const ppsEl = document.getElementById('packetsPerSec'); | |
| if (ppsEl) ppsEl.textContent = packetsPerSec; | |
| topologyAnimId = requestAnimationFrame(animateTopology); | |
| } | |
| // ---- Refresh Topology ---- | |
| function refreshTopology() { | |
| // Randomly toggle a node status for visual feedback | |
| const randomNode = networkNodes[Math.floor(Math.random() * networkNodes.length)]; | |
| if (randomNode && randomNode.id !== 'internet') { | |
| const statuses = ['online', 'online', 'online', 'warning', 'offline']; | |
| randomNode.status = statuses[Math.floor(Math.random() * statuses.length)]; | |
| } | |
| renderNodeStatus(); | |
| showToast('Topology refreshed'); | |
| } | |
| // ---- Node Status List ---- | |
| function renderNodeStatus() { | |
| const list = document.getElementById('nodeStatusList'); | |
| if (!list) return; | |
| const statusColors = { | |
| online: 'text-green-400 bg-green-500/10 border-green-500/20', | |
| warning: 'text-amber-400 bg-amber-500/10 border-amber-500/20', | |
| offline: 'text-red-400 bg-red-500/10 border-red-500/20', | |
| }; | |
| const statusIcons = { | |
| online: 'check-circle', | |
| warning: 'alert-triangle', | |
| offline: 'x-circle', | |
| }; | |
| list.innerHTML = networkNodes.map((n, i) => { | |
| const sc = statusColors[n.status] || statusColors.offline; | |
| const [textC, bgC, borderC] = sc.split(' '); | |
| return ` | |
| <div class="node-status-item flex items-center gap-3 p-2.5 rounded-lg" style="animation-delay: ${i * 40}ms"> | |
| <div class="w-8 h-8 ${bgC} rounded-md flex items-center justify-center shrink-0 border ${borderC}"> | |
| <i data-lucide="${statusIcons[n.status] || 'circle'}" class="w-4 h-4 ${textC}"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center gap-2"> | |
| <span class="text-xs font-medium text-slate-200 truncate">${n.name}</span> | |
| <span class="text-[10px] font-mono ${textC} uppercase font-bold">${n.status}</span> | |
| </div> | |
| <span class="text-[10px] font-mono text-slate-600">${n.ip} · ${n.type}</span> | |
| </div> | |
| </div> | |
| `; | |
| }).join(''); | |
| lucide.createIcons(); | |
| // Update node status count | |
| const online = networkNodes.filter(n => n.status === 'online').length; | |
| const total = networkNodes.length; | |
| const countEl = document.getElementById('nodeStatusCount'); | |
| if (countEl) countEl.textContent = `${online}/${total}`; | |
| const onlineEl = document.getElementById('nodesOnline'); | |
| if (onlineEl) onlineEl.textContent = online; | |
| } | |
| // ---- Active Connections ---- | |
| function initConnections() { | |
| for (let i = 0; i < 8; i++) { | |
| addConnection(); | |
| } | |
| setInterval(addConnection, 5000); | |
| } | |
| function addConnection() { | |
| const template = connTemplates[Math.floor(Math.random() * connTemplates.length)]; | |
| const extIP = extIPs[Math.floor(Math.random() * extIPs.length)]; | |
| const internalNode = networkNodes[Math.floor(Math.random() * (networkNodes.length - 1))]; | |
| const ports = [22, 80, 443, 3306, 5432, 8080, 8443, 9090]; | |
| const port = ports[Math.floor(Math.random() * ports.length)]; | |
| const now = new Date(); | |
| const timeStr = now.toTimeString().split(' ')[0]; | |
| const conn = { | |
| id: Math.random().toString(36).substring(7), | |
| source: extIP, | |
| dest: internalNode ? internalNode.ip : '10.0.0.1', | |
| destName: internalNode ? internalNode.name : 'unknown', | |
| port: port, | |
| proto: template.proto, | |
| state: template.state, | |
| time: timeStr, | |
| }; | |
| activeConnections.unshift(conn); | |
| if (activeConnections.length > 15) activeConnections.pop(); | |
| renderConnections(); | |
| } | |
| function renderConnections() { | |
| const list = document.getElementById('connectionsList'); | |
| if (!list) return; | |
| const stateColors = { | |
| ESTABLISHED: 'text-green-400 bg-green-500/10', | |
| TIME_WAIT: 'text-amber-400 bg-amber-500/10', | |
| CLOSE_WAIT: 'text-red-400 bg-red-500/10', | |
| }; | |
| list.innerHTML = activeConnections.map(c => { | |
| const sc = stateColors[c.state] || 'text-slate-400 bg-slate-500/10'; | |
| const [textC, bgC] = sc.split(' '); | |
| return ` | |
| <div class="conn-item flex items-center gap-2 p-2.5 rounded-lg"> | |
| <div class="flex-1 min-w-0"> | |
| <div class="flex items-center gap-1.5 text-xs"> | |
| <span class="font-mono text-slate-300 truncate">${c.source}</span> | |
| <i data-lucide="arrow-right" class="w-3 h-3 text-slate-600 shrink-0"></i> | |
| <span class="font-mono text-cyan-400 truncate">${c.dest}</span> | |
| </div> | |
| <div class="flex items-center gap-2 mt-1"> | |
| <span class="text-[10px] font-mono text-slate-600">:${c.port}</span> | |
| <span class="text-[10px] font-mono text-slate-600">${c.proto}</span> | |
| <span class="text-[10px] font-mono ${textC} px-1.5 py-0.5 rounded ${bgC} font-semibold">${c.state}</span> | |
| </div> | |
| </div> | |
| <span class="text-[10px] font-mono text-slate-600 shrink-0">${c.time}</span> | |
| </div> | |
| `; | |
| }).join(''); | |
| lucide.createIcons(); | |
| } | |
| // ---- Port Scan Table ---- | |
| function renderPortScan() { | |
| const tbody = document.getElementById('portScanBody'); | |
| if (!tbody) return; | |
| const stateColors = { | |
| open: 'text-green-400 bg-green-500/10 border-green-500/20', | |
| filtered: 'text-amber-400 bg-amber-500/10 border-amber-500/20', | |
| closed: 'text-red-400 bg-red-500/10 border-red-500/20', | |
| }; | |
| tbody.innerHTML = portScanData.map(p => { | |
| const sc = stateColors[p.state] || stateColors.closed; | |
| const [textC, bgC, borderC] = sc.split(' '); | |
| return ` | |
| <tr> | |
| <td class="px-4 py-3 text-sm text-slate-200 font-medium">${p.asset}</td> | |
| <td class="px-4 py-3 font-mono text-xs text-slate-400">${p.ip}</td> | |
| <td class="px-4 py-3 font-mono text-xs text-cyan-400">${p.port}</td> | |
| <td class="px-4 py-3 text-sm text-slate-300">${p.service}</td> | |
| <td class="px-4 py-3 font-mono text-xs text-slate-500">${p.proto}</td> | |
| <td class="px-4 py-3"> | |
| <span class="${textC} ${bgC} border ${borderC} px-2 py-1 rounded text-xs font-semibold uppercase">${p.state}</span> | |
| </td> | |
| <td class="px-4 py-3 font-mono text-xs text-slate-500 hidden sm:table-cell">${p.banner}</td> | |
| </tr> | |
| `; | |
| }).join(''); | |
| lucide.createIcons(); | |
| } | |
| // ---- Real Monitoring via Public APIs ---- | |
| async function startRealMonitoring() { | |
| // 1. Resolve real domain via Google DNS | |
| const dnsData = await SentinelAPI.resolveDomain('google.com'); | |
| if (dnsData && dnsData.ips && dnsData.ips.length > 0) { | |
| extIPs = dnsData.ips.slice(0, 8); | |
| showToast(`Resolved ${dnsData.ips.length} real IPs via Google DNS`); | |
| } else { | |
| // Fallback to static IPs if DNS fails | |
| extIPs = ['203.0.113.45', '198.51.100.23', '192.0.2.99', '185.220.101.45', '45.155.205.99', '91.134.243.12', '172.16.254.1', '203.0.113.77']; | |
| } | |
| // 2. Lookup real IP via ipinfo.io | |
| const ipData = await SentinelAPI.lookupIP('8.8.8.8'); | |
| if (ipData && !ipData.error) { | |
| const realNode = networkNodes.find(n => n.id === 'internet'); | |
| if (realNode) { | |
| realNode.name = ipData.org ? ipData.org.split(' ')[0] : 'Internet'; | |
| realNode.ip = ipData.ip || '8.8.8.8'; | |
| } | |
| showToast(`Real IP: ${ipData.ip} — ${ipData.city}, ${ipData.country}`); | |
| } | |
| // 3. Update connections list with real IPs periodically | |
| setInterval(async () => { | |
| if (extIPs.length === 0) return; | |
| const randomIP = extIPs[Math.floor(Math.random() * extIPs.length)]; | |
| const resolved = await SentinelAPI.lookupIP(randomIP); | |
| if (resolved && !resolved.error && resolved.ip) { | |
| // Update network node with real data | |
| const internetNode = networkNodes.find(n => n.id === 'internet'); | |
| if (internetNode) { | |
| internetNode.ip = resolved.ip; | |
| } | |
| renderNodeStatus(); | |
| } | |
| }, 15000); | |
| // 4. Keep visual metrics cycling (no public API for local bandwidth) | |
| setInterval(() => { | |
| const bw = (Math.random() * 5 + 8).toFixed(1); | |
| const bwEl = document.getElementById('bandwidthVal'); | |
| if (bwEl) bwEl.textContent = bw; | |
| }, 3000); | |
| setInterval(() => { | |
| const latency = Math.floor(Math.random() * 10 + 12); | |
| const latEl = document.getElementById('latencyVal'); | |
| if (latEl) latEl.textContent = latency; | |
| }, 2500); | |
| setInterval(() => { | |
| const conns = Math.floor(Math.random() * 50 + 180); | |
| const connEl = document.getElementById('connCount'); | |
| if (connEl) connEl.textContent = conns; | |
| }, 4000); | |
| // 5. Network node health simulation (realistic fluctuations) | |
| setInterval(() => { | |
| const randomNode = networkNodes[Math.floor(Math.random() * networkNodes.length)]; | |
| if (randomNode && randomNode.id !== 'internet' && randomNode.id !== 'router' && randomNode.id !== 'firewall') { | |
| const roll = Math.random(); | |
| if (roll < 0.02) { | |
| randomNode.status = 'offline'; | |
| } else if (roll < 0.05) { | |
| randomNode.status = 'warning'; | |
| } else { | |
| randomNode.status = 'online'; | |
| } | |
| renderNodeStatus(); | |
| } | |
| }, 10000); | |
| renderNodeStatus(); | |
| showToast('Network monitoring: REAL operational mode active'); | |
| } | |
| // ---- Mobile Menu ---- | |
| function toggleMobileMenu() { | |
| const menu = document.getElementById('mobileMenu'); | |
| if (menu) menu.classList.toggle('hidden'); | |
| } | |
| // ---- Toast ---- | |
| function showToast(message) { | |
| const toast = document.getElementById('netToast'); | |
| const msgEl = document.getElementById('netToastMsg'); | |
| if (!toast || !msgEl) return; | |
| msgEl.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => toast.classList.remove('show'), 2500); | |
| } |