fix(ui): Worker slot preflight checklist and fingerprint hover
- Fix fingerprint tooltip to use actual API field names (browserName, deviceCategory, detectedTimezone) - Show real preflight steps: HTTP Preflight, Geo Session, Pool Ready - Checkmarks appear as each step completes, spinners while in progress 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -688,21 +688,41 @@ function WorkerSlot({
|
|||||||
const maxTasks = worker?.max_concurrent_tasks || 3;
|
const maxTasks = worker?.max_concurrent_tasks || 3;
|
||||||
const isOverloaded = (worker?.active_task_count || 0) >= maxTasks;
|
const isOverloaded = (worker?.active_task_count || 0) >= maxTasks;
|
||||||
|
|
||||||
// Build fingerprint tooltip
|
// Build fingerprint tooltip - use actual API field names
|
||||||
|
const fp = fingerprint as any; // API returns different shape than interface
|
||||||
const fingerprintTooltip = [
|
const fingerprintTooltip = [
|
||||||
'=== FINGERPRINT ===',
|
'═══ FINGERPRINT ═══',
|
||||||
fingerprint?.browser ? `Browser: ${fingerprint.browser}` : 'Browser: Unknown',
|
fp?.browserName ? `Browser: ${fp.browserName}` : 'Browser: Unknown',
|
||||||
fingerprint?.platform ? `Platform: ${fingerprint.platform}` : '',
|
fp?.deviceCategory ? `Device: ${fp.deviceCategory}` : '',
|
||||||
fingerprint?.timezone ? `Timezone: ${fingerprint.timezone}` : '',
|
fp?.userAgent ? `UA: ${fp.userAgent.slice(0, 50)}...` : '',
|
||||||
'',
|
'',
|
||||||
'=== BOT DETECTION ===',
|
'═══ LOCATION ═══',
|
||||||
fingerprint?.botDetection ? `Webdriver: ${fingerprint.botDetection.webdriver ? '⚠️ DETECTED' : '✓ Hidden'}` : '',
|
fp?.detectedLocation?.city ? `City: ${fp.detectedLocation.city}` : '',
|
||||||
fingerprint?.botDetection ? `Automation: ${fingerprint.botDetection.automationControlled ? '⚠️ DETECTED' : '✓ Hidden'}` : '',
|
fp?.detectedLocation?.region ? `State: ${fp.detectedLocation.region}` : '',
|
||||||
|
fp?.detectedTimezone ? `Timezone: ${fp.detectedTimezone}` : '',
|
||||||
'',
|
'',
|
||||||
'=== PRODUCTS ===',
|
'═══ PROXY ═══',
|
||||||
fingerprint?.productsReturned !== undefined ? `Products returned: ${fingerprint.productsReturned}` : '',
|
httpIp ? `IP: ${httpIp}` : '',
|
||||||
|
fp?.productsReturned !== undefined ? `Test Products: ${fp.productsReturned}` : '',
|
||||||
].filter(Boolean).join('\n');
|
].filter(Boolean).join('\n');
|
||||||
|
|
||||||
|
// Preflight status helpers
|
||||||
|
const httpStatus = worker?.preflight_http_status;
|
||||||
|
const curlStatus = worker?.preflight_curl_status;
|
||||||
|
|
||||||
|
const getStatusIcon = (status: string | undefined, label: string) => {
|
||||||
|
if (status === 'passed') return <CheckCircle className="w-3 h-3 text-emerald-500" />;
|
||||||
|
if (status === 'failed') return <XCircle className="w-3 h-3 text-red-500" />;
|
||||||
|
if (status === 'skipped') return <span className="w-3 h-3 text-gray-400">—</span>;
|
||||||
|
return <RefreshCw className="w-3 h-3 text-yellow-500 animate-spin" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: string | undefined) => {
|
||||||
|
if (status === 'passed') return 'text-emerald-600';
|
||||||
|
if (status === 'failed') return 'text-red-600';
|
||||||
|
return 'text-yellow-600';
|
||||||
|
};
|
||||||
|
|
||||||
// Slot is idle (no task)
|
// Slot is idle (no task)
|
||||||
if (!task) {
|
if (!task) {
|
||||||
// Show preflight checklist if not yet qualified
|
// Show preflight checklist if not yet qualified
|
||||||
@@ -713,34 +733,39 @@ function WorkerSlot({
|
|||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
<div className="text-xs font-semibold text-gray-500 mb-2">Slot {slotIndex + 1}</div>
|
<div className="text-xs font-semibold text-gray-500 mb-2">Slot {slotIndex + 1} - Starting</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
{/* HTTP Preflight */}
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
{isOverloaded ? (
|
{getStatusIcon(httpStatus, 'HTTP')}
|
||||||
<XCircle className="w-3 h-3 text-red-500" />
|
<span className={getStatusColor(httpStatus)}>
|
||||||
) : (
|
HTTP Preflight {httpStatus === 'passed' ? '✓' : httpStatus === 'failed' ? '✗' : '...'}
|
||||||
<CheckCircle className="w-3 h-3 text-emerald-500" />
|
</span>
|
||||||
)}
|
|
||||||
<span className={isOverloaded ? 'text-red-600' : 'text-gray-600'}>Overload?</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* Geo Session */}
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
{isDecommissioning ? (
|
{hasGeo ? (
|
||||||
<XCircle className="w-3 h-3 text-orange-500" />
|
|
||||||
) : (
|
|
||||||
<CheckCircle className="w-3 h-3 text-emerald-500" />
|
<CheckCircle className="w-3 h-3 text-emerald-500" />
|
||||||
|
) : httpStatus === 'passed' ? (
|
||||||
|
<RefreshCw className="w-3 h-3 text-yellow-500 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Clock className="w-3 h-3 text-gray-300" />
|
||||||
)}
|
)}
|
||||||
<span className={isDecommissioning ? 'text-orange-600' : 'text-gray-600'}>Terminating?</span>
|
<span className={hasGeo ? 'text-emerald-600' : 'text-gray-500'}>
|
||||||
|
Geo Session {hasGeo ? '✓' : httpStatus === 'passed' ? '...' : ''}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Pool Ready */}
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
{!poolOpen ? (
|
{isQualified ? (
|
||||||
<Clock className="w-3 h-3 text-yellow-500" />
|
<CheckCircle className="w-3 h-3 text-emerald-500" />
|
||||||
) : hasGeo ? (
|
) : hasGeo ? (
|
||||||
<CheckCircle className="w-3 h-3 text-emerald-500" />
|
<RefreshCw className="w-3 h-3 text-yellow-500 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Clock className="w-3 h-3 text-yellow-500 animate-pulse" />
|
<Clock className="w-3 h-3 text-gray-300" />
|
||||||
)}
|
)}
|
||||||
<span className={!poolOpen || !hasGeo ? 'text-yellow-600' : 'text-gray-600'}>
|
<span className={isQualified ? 'text-emerald-600' : 'text-gray-500'}>
|
||||||
Pool Query?
|
Pool Ready {isQualified ? '✓' : hasGeo ? '...' : ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user