import { useCallback, useEffect, useState } from 'react' import api from '../../api.js' import { ErrorState, Loading } from '../../core.js' // ── Admin · Spawn atlas ───────────────────────────────────────────────────── // // The atlas re-derives itself from the shard's ServUO tree on every boot, so // this panel exists for the three things a restart cannot do: // // • point it at a different tree, // • apply a map change without restarting, and // • answer a refresh that was parsed but deliberately NOT applied because it // would remove a facet. // // That last one is the reason the panel is worth building. Losing a facet looks // exactly like a half-copied or mid-update tree, and boot cannot tell them // apart — so it stages the decision for a human instead of guessing. Until // someone decides here, the site keeps serving the atlas it already had. // A refresh reports its outcome rather than throwing (the boot path must never // be stopped by a bad tree), so these are answers, not errors — the panel says // what happened in the shard's terms instead of showing a failure box. const OUTCOME = { imported: (r) => `Imported — ${r.counts?.points?.toLocaleString() ?? '?'} spawners, ${r.counts?.creatures?.toLocaleString() ?? '?'} creatures.`, unchanged: (r) => r.reason === 'refresh previously rejected' ? 'Unchanged — this exact tree was already reviewed and declined.' : 'Unchanged — the tree matches what is already loaded.', needsReview: () => 'Staged for review: this refresh would remove a facet, so it was not applied.', unavailable: (r) => `The tree could not be read: ${r.reason || 'unknown reason'}`, skipped: () => 'No ServUO path is configured, so there is nothing to import.', failed: (r) => `Refresh failed: ${r.reason || 'unknown reason'}`, rejected: () => 'Declined. It will not be offered again until the tree changes.', } const describe = (result) => (OUTCOME[result?.status] || (() => `Result: ${result?.status}`))(result) function Row({ label, children }) { return (
{declined ? ( <> This tree was reviewed and declined, so it is not offered again until the files change. Approving now applies it anyway. > ) : ( <> The tree parses cleanly but would remove {pending.removedFacets?.length || 0} facet {(pending.removedFacets?.length || 0) === 1 ? '' : 's'} the site is currently serving. That is what a half-copied or mid-update tree looks like as well as a real map change, so it was not applied. Approving re-parses the tree as it is right now — if you have since fixed the mount, what lands is the corrected import. > )}
The bestiary and spawn map on the public site, parsed from the shard’s own ServUO files. It refreshes itself on every server start; everything here is for the times you don’t want to wait for one. Nothing on this page touches the sidecar — the atlas is shard content, not shard state, and stays complete while the shard is down.
Where the website reads the shard’s spawn files from — the same host, a bind mount or a
shared volume. This setting wins over the SERVUO_PATH deploy default, so the
mount can move without a redeploy. Leave it blank to turn the atlas off.
Applies a map change without restarting. An unchanged tree costs nothing — the source files are hashed first and skipped when they match. A refresh that would remove a facet still comes back here for approval rather than being applied.