fix(dashboard): Restore preflight display with gold shield + geo
- Show gold shield icon with city/state for qualified workers - Restore IP address, fingerprint, and antidetect status rows - Keep geo session fields in worker interface 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -334,67 +334,97 @@ function ResourceBadge({ worker }: { worker: Worker }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Preflight Summary - shows qualification status with geo region
|
||||
// Preflight Summary - shows IP, fingerprint, antidetect status, and qualification
|
||||
function PreflightSummary({ worker }: { worker: Worker }) {
|
||||
const httpStatus = worker.preflight_http_status || 'pending';
|
||||
const isQualified = worker.is_qualified || httpStatus === 'passed';
|
||||
const httpIp = worker.http_ip;
|
||||
const fingerprint = worker.fingerprint_data;
|
||||
const httpError = worker.preflight_http_error;
|
||||
const httpMs = worker.preflight_http_ms;
|
||||
const geoState = worker.current_state;
|
||||
const geoCity = worker.current_city;
|
||||
|
||||
// Build tooltip
|
||||
// Build detailed tooltip
|
||||
const tooltipLines: string[] = [];
|
||||
tooltipLines.push(`HTTP Preflight: ${httpStatus.toUpperCase()}`);
|
||||
if (httpIp) tooltipLines.push(`IP: ${httpIp}`);
|
||||
if (httpMs) tooltipLines.push(`Response: ${httpMs}ms`);
|
||||
if (fingerprint?.browser) tooltipLines.push(`Browser: ${fingerprint.browser}`);
|
||||
if (fingerprint?.timezone) tooltipLines.push(`Timezone: ${fingerprint.timezone}`);
|
||||
if (fingerprint?.productsReturned !== undefined) tooltipLines.push(`Products returned: ${fingerprint.productsReturned}`);
|
||||
if (fingerprint?.botDetection) {
|
||||
const bd = fingerprint.botDetection;
|
||||
tooltipLines.push(`Bot detection - webdriver: ${bd.webdriver ? 'detected' : 'hidden'}`);
|
||||
}
|
||||
if (geoState) tooltipLines.push(`Geo: ${geoCity ? `${geoCity}, ` : ''}${geoState}`);
|
||||
if (worker.session_task_count !== undefined) {
|
||||
tooltipLines.push(`Session: ${worker.session_task_count}/${worker.session_max_tasks || 7} tasks`);
|
||||
}
|
||||
if (httpError) tooltipLines.push(`Error: ${httpError}`);
|
||||
|
||||
// Qualified - show icon + geo
|
||||
// Qualification styling - gold shield + geo for qualified
|
||||
if (isQualified) {
|
||||
return (
|
||||
<div className="flex items-center gap-2" title={tooltipLines.join('\n')}>
|
||||
<div className="p-1 rounded bg-gradient-to-r from-amber-100 to-yellow-100 border border-amber-300">
|
||||
<ShieldCheck className="w-3.5 h-3.5 text-amber-600" />
|
||||
<div className="flex flex-col gap-1" title={tooltipLines.join('\n')}>
|
||||
{/* Gold shield + city/state */}
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<ShieldCheck className="w-5 h-5 text-amber-500" />
|
||||
<span className="font-semibold text-gray-800">
|
||||
{geoCity && geoState ? `${geoCity}, ${geoState}` :
|
||||
geoState ? geoState :
|
||||
'No geo assigned'}
|
||||
</span>
|
||||
</div>
|
||||
{geoState ? (
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
<MapPin className="w-3 h-3 text-gray-400" />
|
||||
<span className="font-medium text-gray-700">
|
||||
{geoCity ? `${geoCity}, ` : ''}{geoState}
|
||||
</span>
|
||||
{/* IP address */}
|
||||
{httpIp && (
|
||||
<div className="flex items-center gap-1 text-xs text-gray-600">
|
||||
<Globe className="w-3 h-3 text-blue-500" />
|
||||
<span className="font-mono">{httpIp}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-gray-500">No geo assigned</span>
|
||||
)}
|
||||
{/* Fingerprint summary */}
|
||||
{fingerprint?.browser && (
|
||||
<div className="flex items-center gap-1 text-xs text-gray-500">
|
||||
<Fingerprint className="w-3 h-3 text-purple-500" />
|
||||
<span className="truncate max-w-[100px]">{fingerprint.browser}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Antidetect status */}
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
<Shield className="w-3 h-3 text-emerald-500" />
|
||||
<span className="text-emerald-600">Antidetect OK</span>
|
||||
{httpMs && <span className="text-gray-400">({httpMs}ms)</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Failed
|
||||
// Not qualified - show failure state
|
||||
if (httpStatus === 'failed') {
|
||||
return (
|
||||
<div className="flex items-center gap-2" title={tooltipLines.join('\n')}>
|
||||
<div className="p-1 rounded bg-red-100 border border-red-300">
|
||||
<ShieldX className="w-3.5 h-3.5 text-red-600" />
|
||||
<div className="flex flex-col gap-1" title={tooltipLines.join('\n')}>
|
||||
<div className="inline-flex items-center gap-1.5 px-2 py-1 rounded-lg bg-red-100 border border-red-300">
|
||||
<ShieldX className="w-4 h-4 text-red-600" />
|
||||
<span className="text-xs font-bold text-red-700">NOT QUALIFIED</span>
|
||||
</div>
|
||||
<div className="text-xs text-red-600 max-w-[140px] truncate" title={httpError}>
|
||||
{httpError || 'Preflight failed'}
|
||||
</div>
|
||||
<span className="text-xs text-red-600 truncate max-w-[100px]">
|
||||
{httpError || 'Failed'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Pending
|
||||
// Pending state
|
||||
return (
|
||||
<div className="flex items-center gap-2" title={tooltipLines.join('\n')}>
|
||||
<div className="p-1 rounded bg-yellow-100 border border-yellow-300">
|
||||
<Shield className="w-3.5 h-3.5 text-yellow-600 animate-pulse" />
|
||||
<div className="flex flex-col gap-1" title={tooltipLines.join('\n')}>
|
||||
<div className="inline-flex items-center gap-1.5 px-2 py-1 rounded-lg bg-yellow-100 border border-yellow-300">
|
||||
<Shield className="w-4 h-4 text-yellow-600 animate-pulse" />
|
||||
<span className="text-xs font-bold text-yellow-700">QUALIFYING...</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Running preflight check
|
||||
</div>
|
||||
<span className="text-xs text-yellow-700">Qualifying...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user