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. Where those files come from depends on whether a shard is linked: with uo-link configured the shard serves them over the bridge and importing is something you do here, when a map changes. Without one, the site reads a local tree and re-imports itself on every server start. Either way the atlas is shard content rather than shard state, so what is loaded keeps serving in full while the shard is down.
A local ServUO tree the website can read directly — 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.
{status?.source === 'bridge'
? ' It is not in use right now: this site has a shard linked, and the shard serves its' +
' own files over the bridge. Unlink or disable uo-link to fall back to a path.'
: ' Leave it blank to turn the atlas off.'}
Applies a map change without restarting — and on a linked shard it is the only thing that does, because boot deliberately never calls the shard for this. An unchanged source costs almost nothing: the file list and its hashes are read first (about 32 KB over the bridge) and no file is transferred when they match. A refresh that would remove a facet still comes back here for approval rather than being applied.