@braidlabs/console 0.1.1
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/README.md +131 -0
- package/package.json +43 -0
- package/src/app/console-app.d.ts +17 -0
- package/src/app/console-app.js +48 -0
- package/src/app/console-app.js.map +1 -0
- package/src/app/main.d.ts +1 -0
- package/src/app/main.js +32 -0
- package/src/app/main.js.map +1 -0
- package/src/index.d.ts +38 -0
- package/src/index.js +30 -0
- package/src/index.js.map +1 -0
- package/src/lib/access-panel.d.ts +21 -0
- package/src/lib/access-panel.js +35 -0
- package/src/lib/access-panel.js.map +1 -0
- package/src/lib/client.d.ts +94 -0
- package/src/lib/client.js +109 -0
- package/src/lib/client.js.map +1 -0
- package/src/lib/draft.d.ts +45 -0
- package/src/lib/draft.js +85 -0
- package/src/lib/draft.js.map +1 -0
- package/src/lib/integration-panel.d.ts +13 -0
- package/src/lib/integration-panel.js +85 -0
- package/src/lib/integration-panel.js.map +1 -0
- package/src/lib/integration.d.ts +36 -0
- package/src/lib/integration.js +148 -0
- package/src/lib/integration.js.map +1 -0
- package/src/lib/registry-console.d.ts +46 -0
- package/src/lib/registry-console.js +88 -0
- package/src/lib/registry-console.js.map +1 -0
- package/src/lib/registry-editor.d.ts +26 -0
- package/src/lib/registry-editor.js +133 -0
- package/src/lib/registry-editor.js.map +1 -0
- package/src/lib/styles.d.ts +27 -0
- package/src/lib/styles.js +470 -0
- package/src/lib/styles.js.map +1 -0
- package/src/lib/topology-graph.d.ts +47 -0
- package/src/lib/topology-graph.js +124 -0
- package/src/lib/topology-graph.js.map +1 -0
- package/src/lib/topology.d.ts +86 -0
- package/src/lib/topology.js +122 -0
- package/src/lib/topology.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FragmentManifest } from '@braidlabs/gateway';
|
|
2
|
+
import { type RegistryDiff, type RegistryFinding } from '@braidlabs/registry';
|
|
3
|
+
/**
|
|
4
|
+
* A draft registry, held in the browser.
|
|
5
|
+
*
|
|
6
|
+
* Drafts are deliberately **not** server state. Only published snapshots are, which keeps the
|
|
7
|
+
* write API to three routes and sidesteps multi-editor reconciliation entirely. The cost is that
|
|
8
|
+
* a draft does not follow you between devices — the right trade until someone actually needs that.
|
|
9
|
+
*
|
|
10
|
+
* Pure functions over plain data, so the interesting behavior is testable without rendering
|
|
11
|
+
* anything.
|
|
12
|
+
*/
|
|
13
|
+
export interface Draft {
|
|
14
|
+
/** The manifests being edited. */
|
|
15
|
+
manifests: FragmentManifest[];
|
|
16
|
+
/** What was pinned when this draft started, for diffing and for discarding. */
|
|
17
|
+
base: FragmentManifest[];
|
|
18
|
+
/** Snapshot id the draft was branched from, if any. */
|
|
19
|
+
baseId: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function createDraft(base: readonly FragmentManifest[], baseId?: string | null): Draft;
|
|
22
|
+
export interface DraftStatus {
|
|
23
|
+
findings: RegistryFinding[];
|
|
24
|
+
diff: RegistryDiff;
|
|
25
|
+
/** True when the draft has errors and cannot be published. */
|
|
26
|
+
blocked: boolean;
|
|
27
|
+
/** True when nothing has been changed. */
|
|
28
|
+
clean: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Everything the UI needs to decide what to show and whether to allow publishing.
|
|
32
|
+
*
|
|
33
|
+
* Runs the same `validateRegistry` the server runs. Doing it here is a convenience for whoever is
|
|
34
|
+
* typing, not a substitute: the server validates again, because a client check is advice and the
|
|
35
|
+
* server's is the decision.
|
|
36
|
+
*/
|
|
37
|
+
export declare function draftStatus(draft: Draft): DraftStatus;
|
|
38
|
+
export declare function updateFragment(draft: Draft, index: number, patch: Partial<FragmentManifest>): Draft;
|
|
39
|
+
export declare function addFragment(draft: Draft): Draft;
|
|
40
|
+
export declare function removeFragment(draft: Draft, index: number): Draft;
|
|
41
|
+
/** Discards every edit, returning to the snapshot the draft branched from. */
|
|
42
|
+
export declare function resetDraft(draft: Draft): Draft;
|
|
43
|
+
/** Parses a comma-separated input into a list, dropping blanks. */
|
|
44
|
+
export declare function parseList(value: string): string[];
|
|
45
|
+
export declare function formatList(value: readonly string[] | undefined): string;
|
package/src/lib/draft.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { diffRegistries, validateRegistry } from '@braidlabs/registry';
|
|
2
|
+
export function createDraft(base, baseId = null) {
|
|
3
|
+
return { manifests: clone(base), base: clone(base), baseId };
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Everything the UI needs to decide what to show and whether to allow publishing.
|
|
7
|
+
*
|
|
8
|
+
* Runs the same `validateRegistry` the server runs. Doing it here is a convenience for whoever is
|
|
9
|
+
* typing, not a substitute: the server validates again, because a client check is advice and the
|
|
10
|
+
* server's is the decision.
|
|
11
|
+
*/
|
|
12
|
+
export function draftStatus(draft) {
|
|
13
|
+
const findings = validateRegistry(draft.manifests);
|
|
14
|
+
const diff = diffRegistries(draft.base, draft.manifests);
|
|
15
|
+
return {
|
|
16
|
+
findings,
|
|
17
|
+
diff,
|
|
18
|
+
blocked: findings.some((finding) => finding.severity === 'error'),
|
|
19
|
+
clean: diff.identical,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function updateFragment(draft, index, patch) {
|
|
23
|
+
const manifests = draft.manifests.map((manifest, position) => position === index ? prune({ ...manifest, ...patch }) : manifest);
|
|
24
|
+
return { ...draft, manifests };
|
|
25
|
+
}
|
|
26
|
+
export function addFragment(draft) {
|
|
27
|
+
return {
|
|
28
|
+
...draft,
|
|
29
|
+
manifests: [...draft.manifests, { id: uniqueId(draft.manifests), endpoint: '' }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function removeFragment(draft, index) {
|
|
33
|
+
return { ...draft, manifests: draft.manifests.filter((_, position) => position !== index) };
|
|
34
|
+
}
|
|
35
|
+
/** Discards every edit, returning to the snapshot the draft branched from. */
|
|
36
|
+
export function resetDraft(draft) {
|
|
37
|
+
return createDraft(draft.base, draft.baseId);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Drops fields the editor cleared.
|
|
41
|
+
*
|
|
42
|
+
* An empty string is how a text input says "unset", and the difference matters: a manifest
|
|
43
|
+
* carrying `title: ''` is not the same as one that omits `title`, because an omitted field is what
|
|
44
|
+
* lets a fragment descriptor supply it.
|
|
45
|
+
*/
|
|
46
|
+
function prune(manifest) {
|
|
47
|
+
const result = { ...manifest };
|
|
48
|
+
for (const [field, value] of Object.entries(result)) {
|
|
49
|
+
const empty = value === undefined ||
|
|
50
|
+
value === '' ||
|
|
51
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
52
|
+
(isPlainObject(value) && Object.keys(value).length === 0);
|
|
53
|
+
// id and endpoint stay even when blank, so validation can report them as the errors they are
|
|
54
|
+
if (empty && field !== 'id' && field !== 'endpoint')
|
|
55
|
+
delete result[field];
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/** Parses a comma-separated input into a list, dropping blanks. */
|
|
60
|
+
export function parseList(value) {
|
|
61
|
+
return value
|
|
62
|
+
.split(',')
|
|
63
|
+
.map((part) => part.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
export function formatList(value) {
|
|
67
|
+
return (value ?? []).join(', ');
|
|
68
|
+
}
|
|
69
|
+
function uniqueId(manifests) {
|
|
70
|
+
const taken = new Set(manifests.map((manifest) => manifest.id));
|
|
71
|
+
let candidate = 'new-fragment';
|
|
72
|
+
let suffix = 2;
|
|
73
|
+
while (taken.has(candidate))
|
|
74
|
+
candidate = `new-fragment-${suffix++}`;
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
function clone(manifests) {
|
|
78
|
+
// structuredClone would choke on a `fetch`-function endpoint, which a manifest may legitimately
|
|
79
|
+
// carry; those cannot be edited in a UI anyway, so they pass through by reference.
|
|
80
|
+
return manifests.map((manifest) => ({ ...manifest }));
|
|
81
|
+
}
|
|
82
|
+
function isPlainObject(value) {
|
|
83
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/draft.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DiscoveryEntry } from '@braidlabs/gateway';
|
|
2
|
+
export interface IntegrationPanelProps {
|
|
3
|
+
entry: DiscoveryEntry;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* "How do I embed this?", answered next to the thing being embedded.
|
|
8
|
+
*
|
|
9
|
+
* A dialog rather than a row expansion: the snippet is the whole reason the reader is here, and a
|
|
10
|
+
* table that reflows every time someone asks this question loses the row they were comparing it
|
|
11
|
+
* against.
|
|
12
|
+
*/
|
|
13
|
+
export declare function IntegrationPanel({ entry, onClose }: IntegrationPanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { INTEGRATION_TARGETS, integrationSnippet, integrationWarnings, } from './integration.js';
|
|
4
|
+
/**
|
|
5
|
+
* "How do I embed this?", answered next to the thing being embedded.
|
|
6
|
+
*
|
|
7
|
+
* A dialog rather than a row expansion: the snippet is the whole reason the reader is here, and a
|
|
8
|
+
* table that reflows every time someone asks this question loses the row they were comparing it
|
|
9
|
+
* against.
|
|
10
|
+
*/
|
|
11
|
+
export function IntegrationPanel({ entry, onClose }) {
|
|
12
|
+
const [target, setTarget] = useState('html');
|
|
13
|
+
const [copyState, setCopyState] = useState('idle');
|
|
14
|
+
const dialog = useRef(null);
|
|
15
|
+
const closeButton = useRef(null);
|
|
16
|
+
const code = useRef(null);
|
|
17
|
+
const snippet = integrationSnippet(entry, target);
|
|
18
|
+
const warnings = integrationWarnings(entry);
|
|
19
|
+
// Focus moves into the dialog on open, so a keyboard user is not left where the trigger was.
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
closeButton.current?.focus();
|
|
22
|
+
}, []);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const onKeyDown = (event) => {
|
|
25
|
+
if (event.key === 'Escape')
|
|
26
|
+
onClose();
|
|
27
|
+
if (event.key !== 'Tab' || !dialog.current)
|
|
28
|
+
return;
|
|
29
|
+
// A modal that lets focus wander behind it is a modal only for people using a mouse.
|
|
30
|
+
const focusable = dialog.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
31
|
+
if (focusable.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
const first = focusable[0];
|
|
34
|
+
const last = focusable[focusable.length - 1];
|
|
35
|
+
if (!event.shiftKey && document.activeElement === last) {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
first.focus();
|
|
38
|
+
}
|
|
39
|
+
else if (event.shiftKey && document.activeElement === first) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
last.focus();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
document.addEventListener('keydown', onKeyDown);
|
|
45
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
46
|
+
}, [onClose]);
|
|
47
|
+
/**
|
|
48
|
+
* Copies, and degrades usefully when it cannot.
|
|
49
|
+
*
|
|
50
|
+
* `navigator.clipboard` is unavailable on an insecure origin and can be refused by permission —
|
|
51
|
+
* and an internal console on plain HTTP is a completely ordinary deployment, not an edge case.
|
|
52
|
+
* A button that silently does nothing there is worse than no button, so the fallback selects the
|
|
53
|
+
* snippet and says to press the shortcut, which leaves the reader one keystroke from the same
|
|
54
|
+
* result.
|
|
55
|
+
*/
|
|
56
|
+
async function copy() {
|
|
57
|
+
try {
|
|
58
|
+
if (!navigator.clipboard)
|
|
59
|
+
throw new Error('no clipboard api');
|
|
60
|
+
await navigator.clipboard.writeText(snippet.code);
|
|
61
|
+
setCopyState('copied');
|
|
62
|
+
setTimeout(() => setCopyState('idle'), 1600);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
selectSnippet();
|
|
66
|
+
setCopyState('unavailable');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Puts the whole snippet in the selection, so the keyboard fallback is one keystroke. */
|
|
70
|
+
function selectSnippet() {
|
|
71
|
+
const node = code.current;
|
|
72
|
+
const selection = window.getSelection();
|
|
73
|
+
if (!node || !selection)
|
|
74
|
+
return;
|
|
75
|
+
const range = document.createRange();
|
|
76
|
+
range.selectNodeContents(node);
|
|
77
|
+
selection.removeAllRanges();
|
|
78
|
+
selection.addRange(range);
|
|
79
|
+
}
|
|
80
|
+
return (_jsx("div", { className: "braid-console__scrim", onClick: onClose, children: _jsxs("div", { className: "braid-console__dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "braid-integrate-title", ref: dialog, onClick: (event) => event.stopPropagation(), children: [_jsxs("div", { className: "braid-console__dialoghead", children: [_jsxs("h3", { className: "braid-console__dialogtitle", id: "braid-integrate-title", children: ["Embed ", _jsx("code", { children: entry.id })] }), _jsx("button", { type: "button", className: "braid-console__detailclose", onClick: onClose, "aria-label": "Close", ref: closeButton, children: "\u2715" })] }), warnings.map((warning) => (_jsxs("p", { className: "braid-console__notice", role: "status", children: [_jsx("span", { "aria-hidden": "true", children: "\u25C6" }), _jsx("span", { children: warning })] }, warning))), _jsx("div", { className: "braid-console__tabs", role: "tablist", "aria-label": "Framework", children: INTEGRATION_TARGETS.map(({ id, label }) => (_jsx("button", { type: "button", role: "tab", className: "braid-console__tab", "aria-selected": target === id, onClick: () => {
|
|
81
|
+
setTarget(id);
|
|
82
|
+
setCopyState('idle');
|
|
83
|
+
}, children: label }, id))) }), _jsx("pre", { className: "braid-console__code", tabIndex: 0, ref: code, children: _jsx("code", { children: snippet.code }) }), _jsxs("div", { className: "braid-console__dialogfoot", children: [_jsx("button", { type: "button", className: "braid-console__primary", onClick: copy, children: copyState === 'copied' ? 'Copied' : 'Copy' }), _jsx("span", { className: "braid-console__hint", role: "status", children: copyState === 'unavailable' ? (_jsxs(_Fragment, { children: ["This browser will not let the page write to the clipboard \u2014 the snippet is selected, so press ", _jsx("kbd", { children: "\u2318C" }), " / ", _jsx("kbd", { children: "Ctrl+C" }), "."] })) : (_jsxs(_Fragment, { children: ["Needs ", _jsx("code", { children: "@braidlabs/core" }), " installed in the host, and this gateway in front of it."] })) })] })] }) }));
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=integration-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-panel.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/integration-panel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DiscoveryEntry } from '@braidlabs/gateway';
|
|
2
|
+
/**
|
|
3
|
+
* The code a host application needs to embed a registered fragment.
|
|
4
|
+
*
|
|
5
|
+
* **Generated from the manifest, not from a template someone keeps in sync by hand.** Everything
|
|
6
|
+
* that varies between fragments — whether it needs a `src`, whether it takes props, whether it
|
|
7
|
+
* emits events — is already in the discovery entry, and the reason to generate the snippet here
|
|
8
|
+
* rather than document it once is that the differences are exactly what a reader gets wrong.
|
|
9
|
+
* A widget embedded without its `src` renders an empty shell on every page, and that is a
|
|
10
|
+
* question answered by a field the console is already displaying.
|
|
11
|
+
*
|
|
12
|
+
* The snippets deliberately show the *whole* integration, one-time setup included. A snippet that
|
|
13
|
+
* shows only the markup produces a slot that never boots, and the person who copied it has no way
|
|
14
|
+
* to know which half they are missing.
|
|
15
|
+
*/
|
|
16
|
+
export type IntegrationTarget = 'html' | 'angular' | 'react';
|
|
17
|
+
export interface IntegrationSnippet {
|
|
18
|
+
target: IntegrationTarget;
|
|
19
|
+
label: string;
|
|
20
|
+
language: string;
|
|
21
|
+
code: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const INTEGRATION_TARGETS: {
|
|
24
|
+
id: IntegrationTarget;
|
|
25
|
+
label: string;
|
|
26
|
+
}[];
|
|
27
|
+
export declare function integrationSnippet(entry: DiscoveryEntry, target: IntegrationTarget): IntegrationSnippet;
|
|
28
|
+
export declare function allIntegrationSnippets(entry: DiscoveryEntry): IntegrationSnippet[];
|
|
29
|
+
/**
|
|
30
|
+
* What a reader needs to know before pasting, when there is anything.
|
|
31
|
+
*
|
|
32
|
+
* Only states that change what the host must *do*. "This fragment is gated" is worth a line
|
|
33
|
+
* because the slot will render nothing for a user who lacks the role, and someone debugging that
|
|
34
|
+
* without knowing it will go looking in the wrong place entirely.
|
|
35
|
+
*/
|
|
36
|
+
export declare function integrationWarnings(entry: DiscoveryEntry): string[];
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const INTEGRATION_TARGETS = [
|
|
2
|
+
{ id: 'html', label: 'HTML' },
|
|
3
|
+
{ id: 'angular', label: 'Angular' },
|
|
4
|
+
{ id: 'react', label: 'React' },
|
|
5
|
+
];
|
|
6
|
+
/** True when the fragment is a widget — no route of its own, so a host must say where to load it. */
|
|
7
|
+
function isWidget(entry) {
|
|
8
|
+
return entry.bound === false;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The `src` a widget needs, or null.
|
|
12
|
+
*
|
|
13
|
+
* A widget whose manifest declares no `src` is a real configuration gap rather than something to
|
|
14
|
+
* paper over with a plausible-looking default: the gateway would ask the fragment's endpoint for
|
|
15
|
+
* the *host's* path, which means nothing to it. The snippet says so instead of guessing.
|
|
16
|
+
*/
|
|
17
|
+
function srcOf(entry) {
|
|
18
|
+
if (!isWidget(entry))
|
|
19
|
+
return null;
|
|
20
|
+
return entry.src ?? null;
|
|
21
|
+
}
|
|
22
|
+
function widgetNote(entry, comment) {
|
|
23
|
+
if (!isWidget(entry))
|
|
24
|
+
return [];
|
|
25
|
+
const src = srcOf(entry);
|
|
26
|
+
return src
|
|
27
|
+
? [comment(`A widget: it has no route of its own, so "src" says where its content lives.`)]
|
|
28
|
+
: [
|
|
29
|
+
comment(`This widget declares no "src" in its manifest, so there is no path to put here.`),
|
|
30
|
+
comment(`Add one to the manifest — without it the endpoint is asked for the host's path.`),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
/** Custom-element fragments take props and emit events; that is the whole reason to show them. */
|
|
34
|
+
function takesProps(entry) {
|
|
35
|
+
return entry.adapter === 'custom-element';
|
|
36
|
+
}
|
|
37
|
+
export function integrationSnippet(entry, target) {
|
|
38
|
+
switch (target) {
|
|
39
|
+
case 'html':
|
|
40
|
+
return { target, label: 'HTML', language: 'html', code: htmlSnippet(entry) };
|
|
41
|
+
case 'angular':
|
|
42
|
+
return { target, label: 'Angular', language: 'typescript', code: angularSnippet(entry) };
|
|
43
|
+
case 'react':
|
|
44
|
+
return { target, label: 'React', language: 'tsx', code: reactSnippet(entry) };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function allIntegrationSnippets(entry) {
|
|
48
|
+
return INTEGRATION_TARGETS.map(({ id }) => integrationSnippet(entry, id));
|
|
49
|
+
}
|
|
50
|
+
const html = (text) => `<!-- ${text} -->`;
|
|
51
|
+
const js = (text) => `// ${text}`;
|
|
52
|
+
function htmlSnippet(entry) {
|
|
53
|
+
const src = srcOf(entry);
|
|
54
|
+
const notes = widgetNote(entry, html);
|
|
55
|
+
const slot = `<fragment-slot name="${entry.id}"${src ? ` src="${src}"` : ''}></fragment-slot>`;
|
|
56
|
+
// A custom-element fragment takes props, and in plain HTML there is no binding syntax to do it
|
|
57
|
+
// with — they are assigned as a DOM *property* so the value crosses the realm boundary as data
|
|
58
|
+
// rather than being stringified into an attribute.
|
|
59
|
+
const props = takesProps(entry)
|
|
60
|
+
? [
|
|
61
|
+
``,
|
|
62
|
+
js(`Props are a property, not an attribute — objects must cross as data.`),
|
|
63
|
+
`const slot = document.querySelector('fragment-slot[name="${entry.id}"]');`,
|
|
64
|
+
`slot.props = { };`,
|
|
65
|
+
`slot.addEventListener('braid:event', (event) => console.log(event.detail));`,
|
|
66
|
+
]
|
|
67
|
+
: [];
|
|
68
|
+
return [
|
|
69
|
+
js(`Once, anywhere in the host's own bundle:`),
|
|
70
|
+
`import { initBraid } from '@braidlabs/core';`,
|
|
71
|
+
`initBraid();`,
|
|
72
|
+
``,
|
|
73
|
+
...notes,
|
|
74
|
+
slot,
|
|
75
|
+
...props,
|
|
76
|
+
].join('\n');
|
|
77
|
+
}
|
|
78
|
+
function angularSnippet(entry) {
|
|
79
|
+
const src = srcOf(entry);
|
|
80
|
+
const notes = widgetNote(entry, html);
|
|
81
|
+
const attributes = [`name="${entry.id}"`, ...(src ? [`src="${src}"`] : [])];
|
|
82
|
+
if (takesProps(entry))
|
|
83
|
+
attributes.push(`[props]="{ }"`, `(fragmentEvent)="onFragmentEvent($event)"`);
|
|
84
|
+
return [
|
|
85
|
+
js(`1. Once, in the application's providers:`),
|
|
86
|
+
`import { provideBraid } from '@braidlabs/angular';`,
|
|
87
|
+
``,
|
|
88
|
+
`bootstrapApplication(App, { providers: [provideBraid()] });`,
|
|
89
|
+
``,
|
|
90
|
+
js(`2. In the component that shows it:`),
|
|
91
|
+
`import { BraidFragment } from '@braidlabs/angular';`,
|
|
92
|
+
``,
|
|
93
|
+
`@Component({`,
|
|
94
|
+
` imports: [BraidFragment],`,
|
|
95
|
+
` template: \``,
|
|
96
|
+
...notes.map((note) => ` ${note}`),
|
|
97
|
+
` <braid-fragment ${attributes.join(' ')} />`,
|
|
98
|
+
` \`,`,
|
|
99
|
+
`})`,
|
|
100
|
+
`export class HostComponent {}`,
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
103
|
+
function reactSnippet(entry) {
|
|
104
|
+
const src = srcOf(entry);
|
|
105
|
+
const notes = widgetNote(entry, (text) => `{/* ${text} */}`);
|
|
106
|
+
const attributes = [`name="${entry.id}"`, ...(src ? [`src="${src}"`] : [])];
|
|
107
|
+
if (takesProps(entry))
|
|
108
|
+
attributes.push(`props={{}}`, `onFragmentEvent={(event) => console.log(event)}`);
|
|
109
|
+
return [
|
|
110
|
+
js(`1. Once, before anything renders:`),
|
|
111
|
+
`import { initBraidReact } from '@braidlabs/react';`,
|
|
112
|
+
`initBraidReact();`,
|
|
113
|
+
``,
|
|
114
|
+
js(`2. Where it should appear:`),
|
|
115
|
+
`import { BraidFragment } from '@braidlabs/react';`,
|
|
116
|
+
``,
|
|
117
|
+
`export function Host() {`,
|
|
118
|
+
` return (`,
|
|
119
|
+
...notes.map((note) => ` ${note}`),
|
|
120
|
+
` <BraidFragment ${attributes.join(' ')} />`,
|
|
121
|
+
` );`,
|
|
122
|
+
`}`,
|
|
123
|
+
].join('\n');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* What a reader needs to know before pasting, when there is anything.
|
|
127
|
+
*
|
|
128
|
+
* Only states that change what the host must *do*. "This fragment is gated" is worth a line
|
|
129
|
+
* because the slot will render nothing for a user who lacks the role, and someone debugging that
|
|
130
|
+
* without knowing it will go looking in the wrong place entirely.
|
|
131
|
+
*/
|
|
132
|
+
export function integrationWarnings(entry) {
|
|
133
|
+
const warnings = [];
|
|
134
|
+
if (!entry.loadable) {
|
|
135
|
+
warnings.push('You may list this fragment but not load it. The markup below is correct; the slot will stay empty until your principal is allowed to fetch it.');
|
|
136
|
+
}
|
|
137
|
+
if (isWidget(entry) && !entry.src) {
|
|
138
|
+
warnings.push('This widget declares no `src`, so no host can embed it correctly yet. Fix the manifest first.');
|
|
139
|
+
}
|
|
140
|
+
if (!isWidget(entry) && entry.pierce?.length) {
|
|
141
|
+
warnings.push(`This fragment renders the page's route. It is server-rendered into ${entry.pierce.join(', ')} — place the slot on one of those routes.`);
|
|
142
|
+
}
|
|
143
|
+
if (!isWidget(entry) && !entry.pierce?.length) {
|
|
144
|
+
warnings.push('This fragment renders the page\'s route but pierces no route patterns, so it will only ever boot on the client.');
|
|
145
|
+
}
|
|
146
|
+
return warnings;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/integration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DiscoveryEntry } from '@braidlabs/gateway';
|
|
2
|
+
import { type ConsoleApi, type RegistryListing } from './client.js';
|
|
3
|
+
export interface RegistryConsoleProps {
|
|
4
|
+
/** How to reach the gateway. Defaults to this origin's `/__braid/registry`. */
|
|
5
|
+
api?: ConsoleApi;
|
|
6
|
+
/**
|
|
7
|
+
* Force a theme. Omit to follow the viewer's OS setting — which is right for a standalone
|
|
8
|
+
* deployment and usually wrong inside an admin shell that has already made the choice.
|
|
9
|
+
*/
|
|
10
|
+
theme?: 'light' | 'dark';
|
|
11
|
+
/** Extra class on the root, for hosts that want to size or position it. */
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Called whenever a listing loads, for hosts that want their own chrome (counts, timestamps). */
|
|
14
|
+
onLoaded?: (listing: RegistryListing) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Hide the title bar, for a host that already renders its own page heading.
|
|
17
|
+
*
|
|
18
|
+
* Defaults to showing it, because the standalone deployment is a whole page and a page with no
|
|
19
|
+
* title is disorienting. An admin shell embedding this usually has a heading already, and two
|
|
20
|
+
* competing ones read as a bug.
|
|
21
|
+
*/
|
|
22
|
+
chrome?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Which panel to show, when the host wants to own the tab strip.
|
|
25
|
+
*
|
|
26
|
+
* Uncontrolled by default — the console renders its own tabs, which is what a library embedder
|
|
27
|
+
* dropping it into a page wants. A host that has its own chrome (or that deep-links views into
|
|
28
|
+
* its router) passes this and renders the switcher itself, rather than ending up with two rows
|
|
29
|
+
* of tabs disagreeing about which one is selected.
|
|
30
|
+
*/
|
|
31
|
+
view?: ConsoleView;
|
|
32
|
+
}
|
|
33
|
+
export type ConsoleView = 'fragments' | 'topology';
|
|
34
|
+
/**
|
|
35
|
+
* A read-only view of what a gateway has registered.
|
|
36
|
+
*
|
|
37
|
+
* **Needs nothing deployed.** It reads the discovery endpoint the gateway already serves, so it
|
|
38
|
+
* works against a gateway whose manifests are defined in code — which is every gateway today. No
|
|
39
|
+
* snapshot store, no write API.
|
|
40
|
+
*
|
|
41
|
+
* What it deliberately does not do: own the URL, own a session, or style anything outside itself.
|
|
42
|
+
* See `styles.ts` for the second, and `ConsoleApi` for the first two.
|
|
43
|
+
*/
|
|
44
|
+
export declare function RegistryConsole({ api, theme, className, onLoaded, chrome, view }: RegistryConsoleProps): import("react").JSX.Element;
|
|
45
|
+
/** Matches across everything visible in a row, so the filter finds what the eye can see. */
|
|
46
|
+
export declare function filterEntries(entries: readonly DiscoveryEntry[], query: string): DiscoveryEntry[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { fetchRegistry } from './client.js';
|
|
4
|
+
import { ensureStyles } from './styles.js';
|
|
5
|
+
import { buildTopology } from './topology.js';
|
|
6
|
+
import { TopologyGraph } from './topology-graph.js';
|
|
7
|
+
import { IntegrationPanel } from './integration-panel.js';
|
|
8
|
+
/**
|
|
9
|
+
* A read-only view of what a gateway has registered.
|
|
10
|
+
*
|
|
11
|
+
* **Needs nothing deployed.** It reads the discovery endpoint the gateway already serves, so it
|
|
12
|
+
* works against a gateway whose manifests are defined in code — which is every gateway today. No
|
|
13
|
+
* snapshot store, no write API.
|
|
14
|
+
*
|
|
15
|
+
* What it deliberately does not do: own the URL, own a session, or style anything outside itself.
|
|
16
|
+
* See `styles.ts` for the second, and `ConsoleApi` for the first two.
|
|
17
|
+
*/
|
|
18
|
+
export function RegistryConsole({ api, theme, className, onLoaded, chrome = true, view }) {
|
|
19
|
+
const [state, setState] = useState({ status: 'loading' });
|
|
20
|
+
const [query, setQuery] = useState('');
|
|
21
|
+
const [ownTab, setOwnTab] = useState('fragments');
|
|
22
|
+
const tab = view ?? ownTab;
|
|
23
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
24
|
+
/** The fragment whose embed snippet is open, if any. */
|
|
25
|
+
const [embedding, setEmbedding] = useState(null);
|
|
26
|
+
// The api object is usually an inline literal, so a new identity every render. Depending on it
|
|
27
|
+
// directly would refetch forever; these fields are what actually determine the request.
|
|
28
|
+
const apiKey = `${api?.baseUrl ?? ''}|${api?.discoveryPath ?? ''}`;
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
ensureStyles();
|
|
31
|
+
}, []);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
let current = true;
|
|
35
|
+
setState({ status: 'loading' });
|
|
36
|
+
fetchRegistry(api, controller.signal).then((listing) => {
|
|
37
|
+
if (!current)
|
|
38
|
+
return;
|
|
39
|
+
setState({ status: 'ready', listing });
|
|
40
|
+
onLoaded?.(listing);
|
|
41
|
+
}, (error) => {
|
|
42
|
+
if (!current || controller.signal.aborted)
|
|
43
|
+
return;
|
|
44
|
+
setState({ status: 'error', error: error instanceof Error ? error : new Error(String(error)) });
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
current = false;
|
|
48
|
+
controller.abort();
|
|
49
|
+
};
|
|
50
|
+
// Deliberately not exhaustive: `apiKey` stands in for the api object (see above), and
|
|
51
|
+
// `onLoaded` is a notification — depending on it would retrigger the fetch that produced it.
|
|
52
|
+
}, [apiKey, reloadToken]);
|
|
53
|
+
const entries = state.status === 'ready' ? state.listing.entries : [];
|
|
54
|
+
const visible = useMemo(() => filterEntries(entries, query), [entries, query]);
|
|
55
|
+
// The graph reads the *filtered* set, so the search box narrows both views the same way. A graph
|
|
56
|
+
// that ignored the filter would contradict the table sitting one tab away from it.
|
|
57
|
+
const topology = useMemo(() => buildTopology(visible), [visible]);
|
|
58
|
+
const reload = useCallback(() => setReloadToken((token) => token + 1), []);
|
|
59
|
+
return (_jsx("div", { className: `braid-console${className ? ` ${className}` : ''}`, ...(theme ? { 'data-theme': theme } : {}), children: state.status === 'error' ? (_jsx(ErrorPanel, { error: state.error, onRetry: reload })) : (_jsxs("div", { className: "braid-console__shell", children: [chrome && (_jsxs("div", { className: "braid-console__titlebar", children: [_jsx("h2", { className: "braid-console__producttitle", children: "Fragment registry" }), _jsxs("span", { className: "braid-console__breadcrumb", children: [_jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("code", { children: api?.baseUrl || 'this origin' }), state.status === 'ready' && _jsxs("span", { children: ["protocol ", state.listing.protocolVersion] })] })] })), _jsxs("div", { className: "braid-console__commandbar", children: [view === undefined && (_jsxs("div", { className: "braid-console__tabs", role: "tablist", "aria-label": "Registry views", children: [_jsxs("button", { type: "button", role: "tab", className: "braid-console__tab", "aria-selected": tab === 'fragments', onClick: () => setOwnTab('fragments'), children: ["Fragments", _jsx("span", { className: "braid-console__tabcount", children: visible.length })] }), _jsxs("button", { type: "button", role: "tab", className: "braid-console__tab", "aria-selected": tab === 'topology', onClick: () => setOwnTab('topology'), children: ["Topology", _jsx("span", { className: "braid-console__tabcount", children: topology.routes.length })] })] })), _jsx("span", { className: "braid-console__spacer" }), _jsxs("button", { type: "button", className: "braid-console__command", onClick: reload, disabled: state.status === 'loading', children: [_jsx("span", { className: "braid-console__commandicon", "aria-hidden": "true", children: "\u27F3" }), state.status === 'loading' ? 'Refreshing…' : 'Refresh'] }), _jsx("span", { className: "braid-console__commandsep", "aria-hidden": "true" }), _jsx("input", { className: "braid-console__search", type: "search", value: query, placeholder: "Filter by id, title, tag, route", "aria-label": "Filter fragments", onChange: (event) => setQuery(event.target.value) })] }), state.status === 'ready' && _jsx(StatStrip, { listing: state.listing, entries: visible, topology: topology }), state.status === 'ready' && state.listing.unfiltered && (_jsxs("p", { className: "braid-console__notice", role: "status", children: [_jsx("span", { "aria-hidden": "true", children: "\u25C6" }), _jsxs("span", { children: ["This gateway is in ", _jsx("strong", { children: "development mode" }), ", so the listing skips access filtering and may include endpoints. A production gateway shows only what the caller may list."] })] })), state.status === 'loading' ? (_jsx("p", { className: "braid-console__empty", children: "Loading the registry\u2026" })) : visible.length === 0 ? (_jsx("p", { className: "braid-console__empty", children: entries.length === 0 ? 'No fragments are registered.' : `Nothing matches “${query}”.` })) : tab === 'fragments' ? (_jsx(FragmentTable, { entries: visible, onEmbed: setEmbedding })) : (_jsx(TopologyGraph, { entries: visible, onEmbed: setEmbedding })), embedding && _jsx(IntegrationPanel, { entry: embedding, onClose: () => setEmbedding(null) })] })) }));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The four numbers worth reading before scrolling.
|
|
63
|
+
*
|
|
64
|
+
* "Shared routes" earns its place over a prettier metric because it is the only one that can be
|
|
65
|
+
* wrong: it counts pages where two fragments compose, which is either the design or the overlap
|
|
66
|
+
* bug, and an operator who does not know which should be looking.
|
|
67
|
+
*/
|
|
68
|
+
function StatStrip({ listing, entries, topology, }) {
|
|
69
|
+
const shared = topology.routes.filter((route) => route.shared).length;
|
|
70
|
+
const gated = entries.filter((entry) => !entry.loadable).length;
|
|
71
|
+
return (_jsxs("div", { className: "braid-console__stats", children: [_jsxs("div", { className: "braid-console__stat", children: [_jsx("span", { className: "braid-console__statvalue", children: listing.total }), _jsx("span", { className: "braid-console__statlabel", children: "Fragments registered" })] }), _jsxs("div", { className: "braid-console__stat", children: [_jsx("span", { className: "braid-console__statvalue", children: topology.routes.length }), _jsx("span", { className: "braid-console__statlabel", children: "Route patterns" })] }), _jsxs("div", { className: `braid-console__stat${shared > 0 ? ' braid-console__stat--warn' : ''}`, children: [_jsx("span", { className: "braid-console__statvalue", children: shared }), _jsx("span", { className: "braid-console__statlabel", children: "Shared routes" })] }), _jsxs("div", { className: "braid-console__stat", children: [_jsx("span", { className: "braid-console__statvalue", children: gated }), _jsx("span", { className: "braid-console__statlabel", children: "Gated for you" })] })] }));
|
|
72
|
+
}
|
|
73
|
+
function FragmentTable({ entries, onEmbed }) {
|
|
74
|
+
return (_jsxs("table", { className: "braid-console__table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Fragment" }), _jsx("th", { scope: "col", children: "Adapter" }), _jsx("th", { scope: "col", children: "Pierces" }), _jsx("th", { scope: "col", children: "Mount" }), _jsx("th", { scope: "col", children: "Access" }), _jsx("th", { scope: "col", children: _jsx("span", { className: "braid-console__sronly", children: "Actions" }) })] }) }), _jsx("tbody", { children: entries.map((entry) => (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx("div", { className: "braid-console__id", children: entry.id }), entry.title !== entry.id && _jsx("div", { className: "braid-console__desc", children: entry.title }), entry.description && _jsx("div", { className: "braid-console__desc", children: entry.description }), entry.tags?.map((tag) => (_jsx("span", { className: "braid-console__tag", children: tag }, tag)))] }), _jsx("td", { className: "braid-console__mono", children: entry.adapter }), _jsx("td", { children: _jsx("div", { className: "braid-console__patterns", children: entry.pierce?.length ? (entry.pierce.map((pattern) => (_jsx("span", { className: "braid-console__mono", children: pattern }, pattern)))) : (_jsx("span", { className: "braid-console__mono", children: "\u2014" })) }) }), _jsx("td", { className: "braid-console__mono", children: entry.mount }), _jsx("td", { children: _jsx("span", { className: `braid-console__badge braid-console__badge--${entry.loadable ? 'loadable' : 'gated'}`, title: entry.loadable ? 'You may load this fragment' : 'Listed, but you may not load it', children: entry.loadable ? 'loadable' : 'gated' }) }), _jsx("td", { children: _jsx("button", { type: "button", className: "braid-console__ghost", onClick: () => onEmbed(entry), "aria-label": `Show code to embed ${entry.id}`, children: "Embed" }) })] }, entry.id))) })] }));
|
|
75
|
+
}
|
|
76
|
+
function ErrorPanel({ error, onRetry }) {
|
|
77
|
+
return (_jsxs("div", { className: "braid-console__error", role: "alert", children: [_jsx("h3", { children: "Could not read the registry" }), _jsx("p", { children: error.message }), _jsx("button", { className: "braid-console__retry", type: "button", onClick: onRetry, children: "Try again" })] }));
|
|
78
|
+
}
|
|
79
|
+
/** Matches across everything visible in a row, so the filter finds what the eye can see. */
|
|
80
|
+
export function filterEntries(entries, query) {
|
|
81
|
+
const needle = query.trim().toLowerCase();
|
|
82
|
+
if (!needle)
|
|
83
|
+
return [...entries];
|
|
84
|
+
return entries.filter((entry) => [entry.id, entry.title, entry.description, entry.adapter, ...(entry.tags ?? []), ...(entry.pierce ?? [])]
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.some((field) => String(field).toLowerCase().includes(needle)));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=registry-console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-console.js","sourceRoot":"","sources":["../../../../../libs/braid-console/src/lib/registry-console.tsx"],"names":[],"mappings":"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"}
|