larxius commited on
Commit
6d101ea
Β·
verified Β·
1 Parent(s): 40b2b0b

Update frontend/src/pages/Dashboard.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/Dashboard.jsx +115 -42
frontend/src/pages/Dashboard.jsx CHANGED
@@ -20,6 +20,14 @@ const CustomBarTooltip = ({ active, payload, label }) => {
20
  );
21
  }
22
  return null;
 
 
 
 
 
 
 
 
23
  };
24
 
25
  export const Dashboard = () => {
@@ -359,50 +367,115 @@ export const Dashboard = () => {
359
 
360
  {/* ── Live Scan Terminal ── */}
361
  {activeScan && (
362
- <div className="w-full bg-[#020617] border border-[#1e293b] rounded-xl overflow-hidden shadow-2xl">
363
- <div className="bg-[#0f172a] px-md py-sm border-b border-[#1e293b] flex items-center justify-between">
364
- <div className="flex items-center gap-sm">
365
- <span className="relative flex h-3 w-3">
366
- <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
367
- <span className="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
368
- </span>
369
- <span className="font-label-md text-label-md text-white font-bold tracking-tight ml-1">
370
- LIVE AUDIT β€” {activeScan.target_url}
371
- </span>
372
- </div>
373
- <div className="flex items-center gap-sm">
374
- <span className="font-label-sm text-label-sm text-slate-400 bg-slate-800 px-sm py-[2px] rounded uppercase">
375
- {activeScan.scan_type} Scan
376
- </span>
377
- {recentScans.filter(s => (s.status === 'scanning' || s.status === 'queued') && s.id !== activeScan.id).length > 0 && (
378
- <span className="font-label-sm text-label-sm text-sky-400 bg-sky-950/40 border border-sky-800/40 px-sm py-[2px] rounded uppercase">
379
- +{recentScans.filter(s => (s.status === 'scanning' || s.status === 'queued') && s.id !== activeScan.id).length} Queued Next
380
- </span>
381
- )}
382
- <span className="font-label-sm text-label-sm text-yellow-400 bg-yellow-400/10 border border-yellow-400/20 px-sm py-[2px] rounded uppercase animate-pulse">
383
- ● Running
384
- </span>
385
- </div>
386
- </div>
387
 
388
- <div
389
- ref={logContainerRef}
390
- className="p-md font-mono text-[12.5px] leading-relaxed h-56 overflow-y-auto flex flex-col gap-[2px] scroll-smooth custom-scrollbar"
391
- >
392
- {liveLogs.length === 0 ? (
393
- <div className="text-slate-500 animate-pulse">⏳ Spawning audit worker threads...</div>
394
- ) : (
395
- liveLogs.map((log, i) => (
396
- <div key={i} className={`${getLogColor(log)} leading-snug`}>{log}</div>
397
- ))
398
- )}
399
- </div>
400
 
401
- <div className="bg-[#0a0f1e] border-t border-[#1e293b] px-md py-xs flex items-center justify-between">
402
- <span className="font-label-sm text-label-sm text-slate-500">{liveLogs.length} log entries</span>
403
- <span className="text-slate-400 text-xs animate-pulse">● Scanning in progress...</span>
404
- </div>
405
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  )}
407
 
408
  {/* ── Scan Complete Banner ── */}
 
20
  );
21
  }
22
  return null;
23
+ const getCleanDomain = (url) => {
24
+ if (!url) return '';
25
+ try {
26
+ const u = new URL(url.startsWith('http') ? url : `https://${url}`);
27
+ return u.hostname || url;
28
+ } catch (_) {
29
+ return url.replace(/^https?:\/\//, '').split('/')[0];
30
+ }
31
  };
32
 
33
  export const Dashboard = () => {
 
367
 
368
  {/* ── Live Scan Terminal ── */}
369
  {activeScan && (
370
+ (() => {
371
+ const runningScansList = recentScans.filter(
372
+ s => (s.status === 'scanning' || s.status === 'queued') && s.id !== completedScanIdRef.current
373
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
+ return (
376
+ <div className="w-full bg-[#020617] border border-[#1e293b] rounded-xl overflow-hidden shadow-2xl">
377
+ <div className="bg-[#0f172a] px-md py-sm border-b border-[#1e293b] flex flex-wrap items-center justify-between gap-sm">
378
+ <div className="flex items-center gap-sm">
379
+ <span className="relative flex h-3 w-3">
380
+ <span className={`animate-ping absolute inline-flex h-full w-full rounded-full ${activeScan.status === 'scanning' ? 'bg-red-400' : 'bg-yellow-400'} opacity-75`}></span>
381
+ <span className={`relative inline-flex rounded-full h-3 w-3 ${activeScan.status === 'scanning' ? 'bg-red-500' : 'bg-yellow-500'}`}></span>
382
+ </span>
383
+ <span className="font-label-md text-label-md text-white font-bold tracking-tight ml-1">
384
+ LIVE AUDIT β€” {activeScan.target_url}
385
+ </span>
386
+ </div>
387
 
388
+ <div className="flex items-center gap-sm flex-wrap">
389
+ {/* Interactive Scan Tabs when multiple scans are active or queued */}
390
+ {runningScansList.length > 1 && (
391
+ <div className="flex items-center gap-1 bg-[#020617] p-1 rounded-lg border border-[#1e293b]">
392
+ {runningScansList.map((s) => {
393
+ const isSelected = activeScan.id === s.id;
394
+ const domainName = getCleanDomain(s.target_url);
395
+ return (
396
+ <button
397
+ key={s.id}
398
+ onClick={() => {
399
+ if (activeScan.id !== s.id) {
400
+ setActiveScan(s);
401
+ activeScanRef.current = s;
402
+ setLiveLogs([]);
403
+ startLogPolling(s);
404
+ }
405
+ }}
406
+ className={`px-2.5 py-1 rounded text-xs font-mono transition-all flex items-center gap-1.5 cursor-pointer ${
407
+ isSelected
408
+ ? 'bg-sky-500/20 text-sky-300 border border-sky-500/40 font-semibold shadow-sm shadow-sky-500/20'
409
+ : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/80 border border-transparent'
410
+ }`}
411
+ title={`Click to inspect audit logs for ${s.target_url}`}
412
+ >
413
+ <span className={`h-2 w-2 rounded-full ${s.status === 'scanning' ? 'bg-emerald-400 animate-pulse' : 'bg-yellow-400'}`} />
414
+ <span className="max-w-[130px] truncate">{domainName}</span>
415
+ <span className="text-[10px] uppercase opacity-75">
416
+ [{s.status === 'scanning' ? 'Active' : 'Queued'}]
417
+ </span>
418
+ </button>
419
+ );
420
+ })}
421
+ </div>
422
+ )}
423
+
424
+ <span className="font-label-sm text-label-sm text-slate-400 bg-slate-800 px-sm py-[2px] rounded uppercase">
425
+ {activeScan.scan_type} Scan
426
+ </span>
427
+
428
+ {/* Clickable +N Queued Next button to switch to the next queued scan */}
429
+ {runningScansList.filter(s => s.id !== activeScan.id).length > 0 && (
430
+ <button
431
+ onClick={() => {
432
+ const remaining = runningScansList.filter(s => s.id !== activeScan.id);
433
+ if (remaining.length > 0) {
434
+ const nextTarget = remaining[0];
435
+ setActiveScan(nextTarget);
436
+ activeScanRef.current = nextTarget;
437
+ setLiveLogs([]);
438
+ startLogPolling(nextTarget);
439
+ }
440
+ }}
441
+ className="font-label-sm text-label-sm text-sky-400 bg-sky-950/40 border border-sky-800/60 hover:bg-sky-900/60 hover:border-sky-700 px-sm py-[2px] rounded uppercase cursor-pointer transition-colors flex items-center gap-1 group"
442
+ title="Click to view next queued scan in audit terminal"
443
+ >
444
+ <span>+{runningScansList.filter(s => s.id !== activeScan.id).length} Queued Next</span>
445
+ <span className="material-symbols-outlined text-xs group-hover:translate-x-0.5 transition-transform">arrow_forward</span>
446
+ </button>
447
+ )}
448
+
449
+ <span className={`font-label-sm text-label-sm ${activeScan.status === 'scanning' ? 'text-yellow-400 bg-yellow-400/10 border-yellow-400/20' : 'text-sky-400 bg-sky-400/10 border-sky-400/20'} border px-sm py-[2px] rounded uppercase animate-pulse`}>
450
+ ● {activeScan.status === 'scanning' ? 'Running' : 'Queued'}
451
+ </span>
452
+ </div>
453
+ </div>
454
+
455
+ <div
456
+ ref={logContainerRef}
457
+ className="p-md font-mono text-[12.5px] leading-relaxed h-56 overflow-y-auto flex flex-col gap-[2px] scroll-smooth custom-scrollbar"
458
+ >
459
+ {liveLogs.length === 0 ? (
460
+ <div className="text-slate-500 animate-pulse">
461
+ {activeScan.status === 'queued' ? '⏳ Waiting in execution queue (prior scan running)...' : '⏳ Spawning audit worker threads...'}
462
+ </div>
463
+ ) : (
464
+ liveLogs.map((log, i) => (
465
+ <div key={i} className={`${getLogColor(log)} leading-snug`}>{log}</div>
466
+ ))
467
+ )}
468
+ </div>
469
+
470
+ <div className="bg-[#0a0f1e] border-t border-[#1e293b] px-md py-xs flex items-center justify-between">
471
+ <span className="font-label-sm text-label-sm text-slate-500">{liveLogs.length} log entries</span>
472
+ <span className="text-slate-400 text-xs animate-pulse">
473
+ ● {activeScan.status === 'scanning' ? 'Scanning in progress...' : 'Queued β€” awaiting execution...'}
474
+ </span>
475
+ </div>
476
+ </div>
477
+ );
478
+ })()
479
  )}
480
 
481
  {/* ── Scan Complete Banner ── */}