Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Update frontend/src/pages/Dashboard.jsx
Browse files- 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 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 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 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ββ */}
|