@volter/world-console 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/console.tsx +24 -5
- package/dist/client/console.bundle.js +13 -13
- package/dist/client/console.tsx +24 -5
- package/dist/client/main.bundle.js +57 -57
- package/package.json +3 -3
package/dist/client/console.tsx
CHANGED
|
@@ -611,6 +611,10 @@ function WorldView({ name, token, twin, tab, arg, sub, admin, snapshot, parentTo
|
|
|
611
611
|
|
|
612
612
|
const current: Tab = (TABS as readonly string[]).includes(tab) ? (tab as Tab) : 'overview';
|
|
613
613
|
const vendors = twins.state === 'ok' ? twins.data.twins.map((t) => t.twin) : [];
|
|
614
|
+
// an address naming no vendor of this World (mistyped, or guessed) says so, never shows as a vendor that stores nothing
|
|
615
|
+
if (twin && twins.state === 'ok' && !vendors.includes(twin)) {
|
|
616
|
+
return <main className="center"><Empty title={`${name} has no ${twin}`} action={<a href={`${BASE}/${name}`} onClick={(e) => { e.preventDefault(); go(name.split('/')); }}>Open {name}</a>}><p>No vendor of this World is called {twin}. Its changesets are under Changes › History.</p></Empty></main>;
|
|
617
|
+
}
|
|
614
618
|
const chosen = current === 'vendors' && arg ? arg : undefined;
|
|
615
619
|
const retry = (): void => { setScope('pending'); setAttempt((a) => a + 1); };
|
|
616
620
|
// ALL SCREENS is a canvas app's own page: the whole window, the World's pages a click away (Figma's, tldraw's)
|
|
@@ -1091,6 +1095,10 @@ function Workspace({ name, token, view, sub, arg, win, who, snapshot, onReturn,
|
|
|
1091
1095
|
const diff = useDoor<Diff>(token, `/-/${name}/diff`, every);
|
|
1092
1096
|
const sets = useDoor<{ changesets: ChangesetView[] }>(token, `/-/${name}/changesets`, every);
|
|
1093
1097
|
const rules = useDoor<{ changesets: ChangesetRules }>(token, `/-/${name}/rules`, every);
|
|
1098
|
+
// the sites this World's twins answer, each read at `<hostname>.<this World's origin>` (only on a World's own local origin)
|
|
1099
|
+
const sites = useDoor<{ sites: Array<{ twin: string; host: string }> }>(token, `/-/${name}/sites`, every);
|
|
1100
|
+
const siteAt = (host: string): string | null => (/^[^.]+--[^.]+\.localhost$/.test(window.location.hostname) ? `${window.location.protocol}//${host}.${window.location.host}/` : null);
|
|
1101
|
+
const siteLinks = sites.state === 'ok' ? sites.data.sites.flatMap((x) => { const at = siteAt(x.host); return at ? [{ ...x, at }] : []; }) : [];
|
|
1094
1102
|
const [picked, setPicked] = useState<string | null>(null);
|
|
1095
1103
|
const to = (parts: string[]) => (e?: React.MouseEvent): void => { e?.preventDefault(); go([...name.split('/'), '~', ...parts]); };
|
|
1096
1104
|
const vendors = twins.state === 'ok' ? twins.data.twins.map((t) => t.twin) : [];
|
|
@@ -1196,6 +1204,8 @@ function Workspace({ name, token, view, sub, arg, win, who, snapshot, onReturn,
|
|
|
1196
1204
|
{place((view === 'events' && !arg) || view === 'trace', <Icon name="activity" size={16} />, 'Events', 'events', to(['events']))}
|
|
1197
1205
|
<p className="side-group">Records</p>
|
|
1198
1206
|
{vendors.map((v) => <React.Fragment key={v}>{place(view === 'vendor' && arg === v, <Mark vendor={v} />, vendorName(v), `vendors/${v}`, to(['vendors', v]), held(v) ? String(held(v)) : undefined)}</React.Fragment>)}
|
|
1207
|
+
{siteLinks.length ? <p className="side-group">Sites</p> : null}
|
|
1208
|
+
{siteLinks.map((x) => <a key={x.host} href={x.at} target="_blank" rel="noreferrer" title={`${x.host} as ${vendorName(x.twin)} serves it in this World`}><Icon name="globe" size={16} /><span>{x.host}</span><Icon name="external-link" size={14} /></a>)}
|
|
1199
1209
|
<p className="side-group">Remote</p>
|
|
1200
1210
|
{place(false, <Icon name="cloud" size={16} />, there ? 'origin' : 'Name a remote…', 'remote', to(['remote']), waiting ? `↑${waiting}` : undefined)}
|
|
1201
1211
|
</nav>
|
|
@@ -1791,12 +1801,19 @@ const changesetChanges = (c: ChangesetView): Array<{ twin: string; entry: Entry
|
|
|
1791
1801
|
* here), or a push receipt `confirmed` (the remote performed it). Every action performed is Deployed, some is Partly
|
|
1792
1802
|
* deployed; a refused or failed one is said as a failure; otherwise the push decides: every change replayed at the remote
|
|
1793
1803
|
* is Pushed (the remote World, never a vendor), else Not sent. */
|
|
1794
|
-
|
|
1795
|
-
|
|
1804
|
+
/** Whether an action is the World's own (a door, a screen, the gap, the clock or a seed made it): no deploy offers it to
|
|
1805
|
+
* the vendor (the kernel's settlingEntries), so it is never counted as waiting to be performed. */
|
|
1806
|
+
const worldOwn = (a: ChangesetView['actions'][number]): boolean => {
|
|
1807
|
+
const input = ((a.action as { input?: Record<string, unknown> }).input ?? {});
|
|
1808
|
+
return !(typeof input.operationId === 'string' || (input.graphql !== undefined && input.graphql !== null));
|
|
1809
|
+
};
|
|
1810
|
+
function sentOf(c: ChangesetView): { label: string; tone: 'done' | 'good' | 'warn' | 'bad' | 'plain'; performed: number; failed: number; total: number; own: number } {
|
|
1811
|
+
const own = c.actions.filter(worldOwn).length;
|
|
1812
|
+
const total = c.actions.length - own;
|
|
1796
1813
|
const pushed = c.applied?.receipts ?? []; const deployed = c.deployed ?? [];
|
|
1797
1814
|
const performed = new Set([...deployed.filter((r) => r.status === 'deployed'), ...pushed.filter((r) => r.status === 'confirmed')].map((r) => r.actionId)).size;
|
|
1798
1815
|
const failed = new Set([...deployed.filter((r) => r.status === 'refused' || r.status === 'failed'), ...pushed.filter((r) => r.status === 'failed')].map((r) => r.actionId)).size;
|
|
1799
|
-
const n = { performed, failed, total };
|
|
1816
|
+
const n = { performed, failed, total, own };
|
|
1800
1817
|
if (total > 0 && performed >= total) return { label: 'Deployed', tone: 'done', ...n };
|
|
1801
1818
|
if (performed > 0) return { label: `Partly deployed: ${performed} of ${total}`, tone: 'warn', ...n };
|
|
1802
1819
|
if (failed > 0) return { label: `${failed} refused or failed`, tone: 'bad', ...n };
|
|
@@ -1899,7 +1916,7 @@ function ChangesetPage({ name, token, signsAs, c, canWrite, rooted, refused, onC
|
|
|
1899
1916
|
<h4>Where it went</h4>
|
|
1900
1917
|
<div className="block where">
|
|
1901
1918
|
{sent.performed > 0 ? (
|
|
1902
|
-
<p className="pad">{sent.performed >= sent.total ? 'Deployed' : 'Partly deployed'}: {sent.performed} of {sent.total} change{sent.total === 1 ? '' : 's'} performed at {listed([...new Set([...here.map((r) => r.service), ...receipts.filter((r) => r.status === 'confirmed').map((r) => r.service)].map(vendorName))])}{here.length ? ` (${here.length} from here` : ' ('}{here.length && byRemote ? ', ' : ''}{byRemote ? `${byRemote} by the remote` : ''}).</p>
|
|
1919
|
+
<p className="pad">{sent.performed >= sent.total ? 'Deployed' : 'Partly deployed'}: {sent.performed} of {sent.total} change{sent.total === 1 ? '' : 's'}{sent.own ? ` (and ${sent.own} of the World's own, which stay in it)` : ''} performed at {listed([...new Set([...here.map((r) => r.service), ...receipts.filter((r) => r.status === 'confirmed').map((r) => r.service)].map(vendorName))])}{here.length ? ` (${here.length} from here` : ' ('}{here.length && byRemote ? ', ' : ''}{byRemote ? `${byRemote} by the remote` : ''}).</p>
|
|
1903
1920
|
) : sent.label === 'Pushed' ? (
|
|
1904
1921
|
<p className="pad">Pushed to the remote{there ? <> <code>{there}</code></> : null}{c.applied ? <> · <At iso={c.applied.at} /></> : null}: every change was replayed there. No vendor has performed any of it.</p>
|
|
1905
1922
|
) : sent.failed === 0 ? (
|
|
@@ -2123,7 +2140,9 @@ function usePushPull(name: string, token: string, tick: number, refused: (r: Res
|
|
|
2123
2140
|
};
|
|
2124
2141
|
const there = origin.state === 'ok' && origin.data.origin ? originAddress(origin.data.origin) : null;
|
|
2125
2142
|
const changes = diff.state === 'ok' ? diff.data.changes.length : null;
|
|
2126
|
-
|
|
2143
|
+
// what waits to be pushed: this World's own changesets, and only when it has a remote to push them to (a shared World
|
|
2144
|
+
// holds what others pushed, which is theirs)
|
|
2145
|
+
const waiting = sets.state === 'ok' && there ? unpushed(sets.data.changesets.filter((c) => !c.pushedBy)).sort((a, b) => a.createdAt.localeCompare(b.createdAt)) : [];
|
|
2127
2146
|
const push = async (): Promise<void> => {
|
|
2128
2147
|
const b = await send<{ pushed: Array<{ changeset: string; outcome: string; receipts?: Array<{ status: string }>; refusal?: string[] }> }>('Push', 'origin/push', { method: 'POST' });
|
|
2129
2148
|
if (!b) return;
|