- Task #5: Create JobDevTools container component (tabs: All/Scraper/Browser/Network/System, level filters, count badges) - Task #11: Add crash report API endpoints (GET /jobs/{id}/crash-report, POST /jobs/{id}/retry?apply_fix=true, GET /crashes/stats) - Task #14: Create SessionPanel component (fingerprint display, bot detection indicators, collapsible sections) - Task #15: Create MetricsDashboard with recharts (extraction rate, cumulative reviews, memory usage, scroll progress) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
212 lines
8.5 KiB
TypeScript
212 lines
8.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { ChevronDown, ChevronRight, User, Globe, Monitor, Cpu, Shield, Check, X, AlertTriangle } from 'lucide-react';
|
|
|
|
export interface SessionFingerprint {
|
|
user_agent: string;
|
|
platform: string;
|
|
language: string;
|
|
languages: string[];
|
|
timezone: string;
|
|
screen: { width: number; height: number; colorDepth: number };
|
|
viewport: { width: number; height: number };
|
|
webgl_vendor: string;
|
|
webgl_renderer: string;
|
|
canvas_fingerprint: string;
|
|
hardware_concurrency: number;
|
|
device_memory: number;
|
|
bot_detection_tests: {
|
|
webdriver_hidden: boolean;
|
|
chrome_runtime: boolean;
|
|
permissions_query: boolean;
|
|
};
|
|
captured_at: string;
|
|
}
|
|
|
|
interface SessionPanelProps {
|
|
fingerprint: SessionFingerprint;
|
|
}
|
|
|
|
function BotTestIndicator({ passed, label }: { passed: boolean | null | undefined; label: string }) {
|
|
if (passed === null || passed === undefined) {
|
|
return (
|
|
<div className="flex items-center gap-2 px-3 py-2 bg-yellow-900/30 border border-yellow-700/50 rounded-lg">
|
|
<AlertTriangle className="w-4 h-4 text-yellow-500" />
|
|
<span className="text-yellow-300 text-sm font-medium">{label}</span>
|
|
<span className="ml-auto text-yellow-500 text-xs font-mono">UNKNOWN</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (passed) {
|
|
return (
|
|
<div className="flex items-center gap-2 px-3 py-2 bg-green-900/30 border border-green-700/50 rounded-lg">
|
|
<Check className="w-4 h-4 text-green-500" />
|
|
<span className="text-green-300 text-sm font-medium">{label}</span>
|
|
<span className="ml-auto text-green-500 text-xs font-mono">PASSED</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 px-3 py-2 bg-red-900/30 border border-red-700/50 rounded-lg">
|
|
<X className="w-4 h-4 text-red-500" />
|
|
<span className="text-red-300 text-sm font-medium">{label}</span>
|
|
<span className="ml-auto text-red-500 text-xs font-mono">FAILED</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SectionHeader({ icon: Icon, title }: { icon: React.ElementType; title: string }) {
|
|
return (
|
|
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-gray-700">
|
|
<Icon className="w-4 h-4 text-blue-400" />
|
|
<h4 className="text-sm font-semibold text-gray-300 uppercase tracking-wide">{title}</h4>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function DataRow({ label, value, mono = true }: { label: string; value: string | number; mono?: boolean }) {
|
|
return (
|
|
<div className="flex flex-col gap-0.5">
|
|
<span className="text-xs text-gray-500 uppercase tracking-wide">{label}</span>
|
|
<span className={`text-sm text-gray-200 ${mono ? 'font-mono' : ''} break-all`}>{value}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function SessionPanel({ fingerprint }: SessionPanelProps) {
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
|
|
// Calculate overall bot detection status
|
|
const tests = fingerprint.bot_detection_tests;
|
|
const testResults = [tests.webdriver_hidden, tests.chrome_runtime, tests.permissions_query];
|
|
const passedCount = testResults.filter(t => t === true).length;
|
|
const failedCount = testResults.filter(t => t === false).length;
|
|
const unknownCount = testResults.filter(t => t === null || t === undefined).length;
|
|
|
|
const overallStatus = failedCount > 0 ? 'warning' : unknownCount > 0 ? 'partial' : 'success';
|
|
const statusColors = {
|
|
success: 'bg-green-900/30 border-green-700/50 text-green-400',
|
|
partial: 'bg-yellow-900/30 border-yellow-700/50 text-yellow-400',
|
|
warning: 'bg-red-900/30 border-red-700/50 text-red-400',
|
|
};
|
|
|
|
return (
|
|
<div className="bg-gray-800 border border-gray-700 rounded-lg overflow-hidden">
|
|
{/* Collapsible Header */}
|
|
<button
|
|
onClick={() => setIsExpanded(!isExpanded)}
|
|
className="w-full flex items-center justify-between px-4 py-3 bg-gray-800 hover:bg-gray-750 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
{isExpanded ? (
|
|
<ChevronDown className="w-5 h-5 text-gray-400" />
|
|
) : (
|
|
<ChevronRight className="w-5 h-5 text-gray-400" />
|
|
)}
|
|
<Shield className="w-5 h-5 text-blue-400" />
|
|
<span className="text-sm font-semibold text-gray-200">What Google Saw</span>
|
|
</div>
|
|
<div className={`flex items-center gap-2 px-2.5 py-1 rounded-full text-xs font-semibold border ${statusColors[overallStatus]}`}>
|
|
{overallStatus === 'success' && <Check className="w-3 h-3" />}
|
|
{overallStatus === 'partial' && <AlertTriangle className="w-3 h-3" />}
|
|
{overallStatus === 'warning' && <X className="w-3 h-3" />}
|
|
<span>
|
|
{passedCount}/{testResults.length} Tests Passed
|
|
</span>
|
|
</div>
|
|
</button>
|
|
|
|
{/* Collapsible Content */}
|
|
{isExpanded && (
|
|
<div className="px-4 pb-4 border-t border-gray-700">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pt-4">
|
|
{/* Identity Section */}
|
|
<div className="space-y-3">
|
|
<SectionHeader icon={User} title="Identity" />
|
|
<div className="space-y-3 bg-gray-900/50 rounded-lg p-3">
|
|
<DataRow label="User Agent" value={fingerprint.user_agent} />
|
|
<DataRow label="Platform" value={fingerprint.platform} />
|
|
<DataRow label="Primary Language" value={fingerprint.language} />
|
|
<DataRow label="Languages" value={fingerprint.languages.join(', ')} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Geolocation Section */}
|
|
<div className="space-y-3">
|
|
<SectionHeader icon={Globe} title="Geolocation" />
|
|
<div className="space-y-3 bg-gray-900/50 rounded-lg p-3">
|
|
<DataRow label="Timezone" value={fingerprint.timezone} />
|
|
<DataRow label="Captured At" value={fingerprint.captured_at} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Display Section */}
|
|
<div className="space-y-3">
|
|
<SectionHeader icon={Monitor} title="Display" />
|
|
<div className="space-y-3 bg-gray-900/50 rounded-lg p-3">
|
|
<DataRow
|
|
label="Screen Resolution"
|
|
value={`${fingerprint.screen.width} x ${fingerprint.screen.height}`}
|
|
/>
|
|
<DataRow
|
|
label="Viewport Size"
|
|
value={`${fingerprint.viewport.width} x ${fingerprint.viewport.height}`}
|
|
/>
|
|
<DataRow
|
|
label="Color Depth"
|
|
value={`${fingerprint.screen.colorDepth}-bit`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Hardware Section */}
|
|
<div className="space-y-3">
|
|
<SectionHeader icon={Cpu} title="Hardware" />
|
|
<div className="space-y-3 bg-gray-900/50 rounded-lg p-3">
|
|
<DataRow label="WebGL Vendor" value={fingerprint.webgl_vendor} />
|
|
<DataRow label="WebGL Renderer" value={fingerprint.webgl_renderer} />
|
|
<DataRow label="CPU Cores" value={fingerprint.hardware_concurrency} />
|
|
<DataRow label="Device Memory" value={`${fingerprint.device_memory} GB`} />
|
|
<DataRow label="Canvas Fingerprint" value={fingerprint.canvas_fingerprint} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bot Detection Section - Full Width */}
|
|
<div className="mt-6 space-y-3">
|
|
<SectionHeader icon={Shield} title="Bot Detection Tests" />
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
<BotTestIndicator
|
|
passed={tests.webdriver_hidden}
|
|
label="WebDriver Hidden"
|
|
/>
|
|
<BotTestIndicator
|
|
passed={tests.chrome_runtime}
|
|
label="Chrome Runtime"
|
|
/>
|
|
<BotTestIndicator
|
|
passed={tests.permissions_query}
|
|
label="Permissions Query"
|
|
/>
|
|
</div>
|
|
<div className="mt-3 text-xs text-gray-500 bg-gray-900/30 rounded-lg p-3">
|
|
<p>
|
|
<span className="text-green-400 font-semibold">Green checkmark</span> = Test passed (bot detection evaded)
|
|
</p>
|
|
<p>
|
|
<span className="text-red-400 font-semibold">Red X</span> = Test failed (may have been detected as a bot)
|
|
</p>
|
|
<p>
|
|
<span className="text-yellow-400 font-semibold">Yellow warning</span> = Test result unknown
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|