@domainkit/react 0.11.0 → 0.12.0
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 +96 -118
- package/dist/index.d.mts +543 -519
- package/dist/index.mjs +734 -2121
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -12
- package/dist/styles.css +0 -1875
package/dist/index.mjs
CHANGED
|
@@ -2,114 +2,14 @@
|
|
|
2
2
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
3
3
|
import { DnsRecord, DomainKit, Reason, Receipt } from "domainkit";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
|
-
import { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState
|
|
5
|
+
import { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import * as Data from "effect/Data";
|
|
7
7
|
import { Transport } from "domainkit/client";
|
|
8
|
-
import {
|
|
9
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
9
|
import * as Exit from "effect/Exit";
|
|
11
10
|
import * as Option from "effect/Option";
|
|
12
|
-
import { Dialog } from "@base-ui/react/dialog";
|
|
13
|
-
import { Collapsible } from "@base-ui/react/collapsible";
|
|
14
|
-
import { Switch } from "@base-ui/react/switch";
|
|
15
|
-
import { Menu } from "@base-ui/react/menu";
|
|
16
|
-
import { Popover } from "@base-ui/react/popover";
|
|
17
11
|
import * as DateTime from "effect/DateTime";
|
|
18
12
|
import { Testing } from "domainkit/testing";
|
|
19
|
-
//#region src/composition.tsx
|
|
20
|
-
function usePart(defaultTagName, componentProps, state, internalProps) {
|
|
21
|
-
const { className, render, style, ...externalProps } = componentProps;
|
|
22
|
-
const parameters = {
|
|
23
|
-
...className === void 0 ? {} : { className },
|
|
24
|
-
defaultTagName,
|
|
25
|
-
props: [internalProps, externalProps],
|
|
26
|
-
...render === void 0 ? {} : { render },
|
|
27
|
-
state,
|
|
28
|
-
...style === void 0 ? {} : { style }
|
|
29
|
-
};
|
|
30
|
-
return useRender(parameters);
|
|
31
|
-
}
|
|
32
|
-
/** A part with no state of its own: one tag, one `data-domainkit-part`, host props on top. */
|
|
33
|
-
function leafPart(defaultTagName, part) {
|
|
34
|
-
return function Leaf(props) {
|
|
35
|
-
return usePart(defaultTagName, props, {}, { "data-domainkit-part": part });
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/icons.tsx
|
|
40
|
-
function Glyph({ children }) {
|
|
41
|
-
return /* @__PURE__ */ jsx("svg", {
|
|
42
|
-
"aria-hidden": "true",
|
|
43
|
-
fill: "none",
|
|
44
|
-
stroke: "currentColor",
|
|
45
|
-
strokeLinecap: "round",
|
|
46
|
-
strokeLinejoin: "round",
|
|
47
|
-
strokeWidth: 2,
|
|
48
|
-
viewBox: "0 0 24 24",
|
|
49
|
-
children
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
const defaultIcons = {
|
|
53
|
-
chevron: /* @__PURE__ */ jsx(Glyph, { children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) }),
|
|
54
|
-
close: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] }),
|
|
55
|
-
copied: /* @__PURE__ */ jsx(Glyph, { children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) }),
|
|
56
|
-
copy: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("rect", {
|
|
57
|
-
height: "14",
|
|
58
|
-
rx: "2",
|
|
59
|
-
width: "14",
|
|
60
|
-
x: "8",
|
|
61
|
-
y: "8"
|
|
62
|
-
}), /* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })] }),
|
|
63
|
-
download: /* @__PURE__ */ jsxs(Glyph, { children: [
|
|
64
|
-
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
65
|
-
/* @__PURE__ */ jsx("path", { d: "m7 10 5 5 5-5" }),
|
|
66
|
-
/* @__PURE__ */ jsx("path", { d: "M12 15V3" })
|
|
67
|
-
] }),
|
|
68
|
-
external: /* @__PURE__ */ jsxs(Glyph, { children: [
|
|
69
|
-
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
70
|
-
/* @__PURE__ */ jsx("path", { d: "M10 14 21 3" }),
|
|
71
|
-
/* @__PURE__ */ jsx("path", { d: "M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5" })
|
|
72
|
-
] }),
|
|
73
|
-
failure: /* @__PURE__ */ jsxs(Glyph, { children: [
|
|
74
|
-
/* @__PURE__ */ jsx("circle", {
|
|
75
|
-
cx: "12",
|
|
76
|
-
cy: "12",
|
|
77
|
-
r: "9"
|
|
78
|
-
}),
|
|
79
|
-
/* @__PURE__ */ jsx("path", { d: "M15 9 9 15" }),
|
|
80
|
-
/* @__PURE__ */ jsx("path", { d: "m9 9 6 6" })
|
|
81
|
-
] }),
|
|
82
|
-
pending: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("circle", {
|
|
83
|
-
cx: "12",
|
|
84
|
-
cy: "12",
|
|
85
|
-
r: "9"
|
|
86
|
-
}), /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })] }),
|
|
87
|
-
success: /* @__PURE__ */ jsxs(Glyph, { children: [/* @__PURE__ */ jsx("circle", {
|
|
88
|
-
cx: "12",
|
|
89
|
-
cy: "12",
|
|
90
|
-
r: "9"
|
|
91
|
-
}), /* @__PURE__ */ jsx("path", { d: "m8.5 12.5 2.5 2.5 4.5-5" })] }),
|
|
92
|
-
warning: /* @__PURE__ */ jsxs(Glyph, { children: [
|
|
93
|
-
/* @__PURE__ */ jsx("path", { d: "M10.3 3.9 2.4 17.5a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" }),
|
|
94
|
-
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
95
|
-
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
96
|
-
] })
|
|
97
|
-
};
|
|
98
|
-
const IconsContext = createContext(defaultIcons);
|
|
99
|
-
function IconsProvider({ children, icons }) {
|
|
100
|
-
const value = useMemo(() => ({
|
|
101
|
-
...defaultIcons,
|
|
102
|
-
...icons
|
|
103
|
-
}), [icons]);
|
|
104
|
-
return /* @__PURE__ */ jsx(IconsContext.Provider, {
|
|
105
|
-
value,
|
|
106
|
-
children
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
function useIcons() {
|
|
110
|
-
return useContext(IconsContext);
|
|
111
|
-
}
|
|
112
|
-
//#endregion
|
|
113
13
|
//#region src/messages.ts
|
|
114
14
|
var messages_exports = /* @__PURE__ */ __exportAll({
|
|
115
15
|
english: () => english,
|
|
@@ -167,7 +67,7 @@ const english = {
|
|
|
167
67
|
approve: "Approve",
|
|
168
68
|
addRecords: (count) => count === 1 ? "Add 1 record" : `Add ${count} records`,
|
|
169
69
|
decline: "Decline",
|
|
170
|
-
everyRecordConflicts: "Resolve the records above at your provider, then
|
|
70
|
+
everyRecordConflicts: "Resolve the records above at your provider, then check again.",
|
|
171
71
|
reviewChanges: "Review changes",
|
|
172
72
|
cleanUp: "Remove records",
|
|
173
73
|
checkDns: "Check DNS",
|
|
@@ -177,7 +77,8 @@ const english = {
|
|
|
177
77
|
useAnotherProvider: "Use a different provider",
|
|
178
78
|
useTokenInstead: "Use an API token instead",
|
|
179
79
|
back: "Back",
|
|
180
|
-
|
|
80
|
+
connectedAccount: (provider, account) => `${provider} · ${account}`,
|
|
81
|
+
recordsAdded: (count) => count === 1 ? "1 added" : `${count} added`,
|
|
181
82
|
needsReconnect: "Needs reconnecting",
|
|
182
83
|
disconnectScope: "This connection serves other domains.",
|
|
183
84
|
disconnectThisDomain: "Only this domain",
|
|
@@ -188,6 +89,10 @@ const english = {
|
|
|
188
89
|
download: "Download",
|
|
189
90
|
useConnection: (label) => `Use ${label}`,
|
|
190
91
|
chooseZone: "Choose the zone that serves this domain",
|
|
92
|
+
zoneSuggestions: "Zones your connected accounts serve",
|
|
93
|
+
recordsGoTo: (provider, account) => `Records go to ${provider} through ${account}.`,
|
|
94
|
+
notInConnectedAccount: "Not in a connected account.",
|
|
95
|
+
reconnectAccount: (account) => `Reconnect ${account}`,
|
|
191
96
|
getToken: "Where do I find this?",
|
|
192
97
|
loading: "Loading…",
|
|
193
98
|
connecting: "Connecting…",
|
|
@@ -203,10 +108,11 @@ const english = {
|
|
|
203
108
|
observing: "Checking DNS…",
|
|
204
109
|
connectTitle: (provider) => `Connect ${provider}`,
|
|
205
110
|
connectAnyTitle: "Connect a DNS provider",
|
|
206
|
-
|
|
111
|
+
hostDetected: (provider) => `${provider} DNS detected`,
|
|
207
112
|
connectDescription: (domain) => `Authorize DNS changes for ${domain}.`,
|
|
208
113
|
connectedTo: (provider) => `${provider} connected`,
|
|
209
114
|
notConnected: "No DNS provider is connected.",
|
|
115
|
+
administratorConnects: (provider) => `An administrator can connect ${provider}`,
|
|
210
116
|
reconnectRequired: (provider) => `${provider} needs to be reconnected`,
|
|
211
117
|
disconnectTitle: (provider) => `Disconnect ${provider}?`,
|
|
212
118
|
disconnectConsent: (domain, provider) => `DomainKit stops managing DNS for ${domain} through ${provider}.`,
|
|
@@ -247,6 +153,23 @@ const english = {
|
|
|
247
153
|
case "missing": return "The record this plan expected is gone.";
|
|
248
154
|
}
|
|
249
155
|
},
|
|
156
|
+
conflictAdvice: (reason) => {
|
|
157
|
+
switch (reason) {
|
|
158
|
+
case "exclusive-name": return "Remove the record on that name at your provider, then check again.";
|
|
159
|
+
case "cname-collision": return "Remove the other records on that name at your provider, then check again.";
|
|
160
|
+
case "value-mismatch": return "Point that record at this value at your provider, or remove it and check again.";
|
|
161
|
+
case "opaque": return "Remove that record at your provider, then check again.";
|
|
162
|
+
case "missing": return "Check again to build a plan from what is there now.";
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
planStatus: (operation) => {
|
|
166
|
+
switch (operation._tag) {
|
|
167
|
+
case "Create": return "Will add";
|
|
168
|
+
case "Noop": return "Already there";
|
|
169
|
+
case "Delete": return "Will remove";
|
|
170
|
+
case "Conflict": return "In the way";
|
|
171
|
+
}
|
|
172
|
+
},
|
|
250
173
|
attemptStatus: (status) => {
|
|
251
174
|
switch (status) {
|
|
252
175
|
case "planned": return "Awaiting review";
|
|
@@ -418,39 +341,17 @@ const failure = (error, catalog, context = {}) => {
|
|
|
418
341
|
return description === void 0 ? `${title}.` : `${title}. ${description}`;
|
|
419
342
|
};
|
|
420
343
|
//#endregion
|
|
421
|
-
//#region src/theme.ts
|
|
422
|
-
var theme_exports = /* @__PURE__ */ __exportAll({ toStyle: () => toStyle });
|
|
423
|
-
const variables = {
|
|
424
|
-
accent: "--domainkit-accent",
|
|
425
|
-
accentContrast: "--domainkit-accent-contrast",
|
|
426
|
-
backdrop: "--domainkit-backdrop",
|
|
427
|
-
background: "--domainkit-background",
|
|
428
|
-
border: "--domainkit-border",
|
|
429
|
-
danger: "--domainkit-danger",
|
|
430
|
-
dangerContrast: "--domainkit-danger-contrast",
|
|
431
|
-
fill: "--domainkit-fill",
|
|
432
|
-
fontFamily: "--domainkit-font-family",
|
|
433
|
-
muted: "--domainkit-muted",
|
|
434
|
-
radius: "--domainkit-radius",
|
|
435
|
-
shadow: "--domainkit-shadow",
|
|
436
|
-
success: "--domainkit-success",
|
|
437
|
-
text: "--domainkit-text",
|
|
438
|
-
warning: "--domainkit-warning"
|
|
439
|
-
};
|
|
440
|
-
function toStyle(theme = {}) {
|
|
441
|
-
return Object.fromEntries(Object.entries(theme).flatMap(([key, value]) => value === void 0 ? [] : [[variables[key], value]]));
|
|
442
|
-
}
|
|
443
|
-
//#endregion
|
|
444
344
|
//#region src/domain-kit.tsx
|
|
445
345
|
var domain_kit_exports = /* @__PURE__ */ __exportAll({
|
|
446
346
|
ReadOnly: () => ReadOnly,
|
|
447
|
-
Root: () => Root
|
|
347
|
+
Root: () => Root,
|
|
448
348
|
useCapabilities: () => useCapabilities,
|
|
449
349
|
useDomainKit: () => useDomainKit,
|
|
350
|
+
useMessages: () => useMessages,
|
|
450
351
|
useReadOnly: () => useReadOnly,
|
|
451
352
|
useTransport: () => useTransport
|
|
452
353
|
});
|
|
453
|
-
const Context
|
|
354
|
+
const Context = createContext(null);
|
|
454
355
|
const navigateInBrowser = (url) => {
|
|
455
356
|
if (typeof window === "undefined") throw new Error("DomainKit provider authorization requires a browser or a `navigate` prop");
|
|
456
357
|
window.location.assign(url);
|
|
@@ -482,70 +383,44 @@ const useStableTransport = (transport) => {
|
|
|
482
383
|
};
|
|
483
384
|
}, [signature]);
|
|
484
385
|
};
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const subscribe = useCallback((notify) => {
|
|
492
|
-
if (ownerDocument === null || typeof MutationObserver === "undefined") return () => {};
|
|
493
|
-
const observer = new MutationObserver(notify);
|
|
494
|
-
observer.observe(ownerDocument, {
|
|
495
|
-
childList: true,
|
|
496
|
-
subtree: true
|
|
497
|
-
});
|
|
498
|
-
return () => observer.disconnect();
|
|
499
|
-
}, [ownerDocument]);
|
|
500
|
-
const getSnapshot = useCallback(() => resolvePortalContainer(container, ownerDocument), [container, ownerDocument]);
|
|
501
|
-
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
502
|
-
};
|
|
503
|
-
function Root$7({ children, colorScheme = "inherit", icons, marks = {}, messages, navigate = navigateInBrowser, onEvent, portalContainer = null, readOnly = false, revision = 0, theme, transport, ...props }) {
|
|
386
|
+
/**
|
|
387
|
+
* The transport, the catalog, and the two host callbacks every hook below reads. It renders no
|
|
388
|
+
* element of its own: `@domainkit/react` supplies behaviour, state, copy, and accessibility, and
|
|
389
|
+
* the host application supplies the markup.
|
|
390
|
+
*/
|
|
391
|
+
function Root({ children, messages, navigate = navigateInBrowser, onEvent, readOnly = false, revision = 0, transport }) {
|
|
504
392
|
const { capabilities, transport: stable } = useStableTransport(transport);
|
|
505
393
|
const onEventRef = useRef(onEvent);
|
|
506
394
|
useLayoutEffect(() => {
|
|
507
395
|
onEventRef.current = onEvent;
|
|
508
396
|
}, [onEvent]);
|
|
509
|
-
const emit = useCallback((event) => {
|
|
510
|
-
try {
|
|
511
|
-
onEventRef.current?.(event);
|
|
512
|
-
} catch {}
|
|
513
|
-
}, []);
|
|
514
|
-
const themeStyle = toStyle(theme);
|
|
515
|
-
const resolvedPortalContainer = usePortalContainer(portalContainer);
|
|
516
|
-
const portalContainerRef = useMemo(() => ({ current: resolvedPortalContainer }), [resolvedPortalContainer]);
|
|
517
397
|
const value = {
|
|
518
398
|
capabilities,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
399
|
+
emit: useCallback((event) => {
|
|
400
|
+
try {
|
|
401
|
+
onEventRef.current?.(event);
|
|
402
|
+
} catch {}
|
|
403
|
+
}, []),
|
|
522
404
|
messages: merge(messages),
|
|
523
405
|
navigate,
|
|
524
|
-
portalContainer: portalContainerRef,
|
|
525
406
|
readOnly,
|
|
526
407
|
revision,
|
|
527
|
-
themeStyle,
|
|
528
408
|
transport: stable
|
|
529
409
|
};
|
|
530
|
-
|
|
531
|
-
children,
|
|
532
|
-
"data-color-scheme": colorScheme,
|
|
533
|
-
"data-domainkit-root": "",
|
|
534
|
-
style: themeStyle
|
|
535
|
-
});
|
|
536
|
-
return /* @__PURE__ */ jsx(Context$1.Provider, {
|
|
410
|
+
return /* @__PURE__ */ jsx(Context.Provider, {
|
|
537
411
|
value,
|
|
538
|
-
children
|
|
539
|
-
...icons === void 0 ? {} : { icons },
|
|
540
|
-
children: content
|
|
541
|
-
})
|
|
412
|
+
children
|
|
542
413
|
});
|
|
543
414
|
}
|
|
544
415
|
function useDomainKit() {
|
|
545
|
-
const value = useContext(Context
|
|
546
|
-
if (value === null) throw new Error("DomainKit
|
|
416
|
+
const value = useContext(Context);
|
|
417
|
+
if (value === null) throw new Error("DomainKit hooks must be called inside DomainKit.Root");
|
|
547
418
|
return value;
|
|
548
419
|
}
|
|
420
|
+
/** The catalog `DomainKit.Root` holds, with the host's overrides already merged in. */
|
|
421
|
+
function useMessages() {
|
|
422
|
+
return useDomainKit().messages;
|
|
423
|
+
}
|
|
549
424
|
/** The transport `DomainKit.Root` holds, with the identity it keeps for the whole mount. */
|
|
550
425
|
function useTransport() {
|
|
551
426
|
return useDomainKit().transport;
|
|
@@ -558,13 +433,13 @@ function ReadOnly({ children, value }) {
|
|
|
558
433
|
children
|
|
559
434
|
});
|
|
560
435
|
}
|
|
561
|
-
/** Whether this
|
|
436
|
+
/** Whether this surface may offer controls that change the domain. */
|
|
562
437
|
function useReadOnly() {
|
|
563
438
|
const scoped = useContext(ReadOnlyContext);
|
|
564
439
|
const root = useDomainKit().readOnly;
|
|
565
440
|
return scoped ?? root;
|
|
566
441
|
}
|
|
567
|
-
/** Which capability groups the host's transport declares, for gating a
|
|
442
|
+
/** Which capability groups the host's transport declares, for gating a surface of your own. */
|
|
568
443
|
function useCapabilities() {
|
|
569
444
|
return useDomainKit().capabilities;
|
|
570
445
|
}
|
|
@@ -634,10 +509,42 @@ function useRunner() {
|
|
|
634
509
|
const State$2 = Data.taggedEnum();
|
|
635
510
|
/** Reasons that mean the plan itself is gone: retrying the same step would fail the same way. */
|
|
636
511
|
const needsNewPlan = (error) => error.reason._tag === "Stale" || error.reason._tag === "Expired" || error.reason._tag === "Conflict";
|
|
512
|
+
/** The plan an attempt holds, whatever step it is on; `null` before there is one. */
|
|
513
|
+
const planOf = (state) => {
|
|
514
|
+
switch (state._tag) {
|
|
515
|
+
case "Planned":
|
|
516
|
+
case "Approving":
|
|
517
|
+
case "Applying":
|
|
518
|
+
case "Rejecting":
|
|
519
|
+
case "Rejected":
|
|
520
|
+
case "Applied": return state.plan;
|
|
521
|
+
case "Idle":
|
|
522
|
+
case "Planning":
|
|
523
|
+
case "Failure": return null;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* The plan still awaiting its apply, which is what a row of records reports; `null` once one has
|
|
528
|
+
* landed, because from then on an observation answers instead.
|
|
529
|
+
*/
|
|
530
|
+
const pendingPlan = (state) => {
|
|
531
|
+
switch (state._tag) {
|
|
532
|
+
case "Planned":
|
|
533
|
+
case "Approving":
|
|
534
|
+
case "Applying": return state.plan;
|
|
535
|
+
case "Idle":
|
|
536
|
+
case "Planning":
|
|
537
|
+
case "Applied":
|
|
538
|
+
case "Rejecting":
|
|
539
|
+
case "Rejected":
|
|
540
|
+
case "Failure": return null;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
637
543
|
function useAttempt(options) {
|
|
638
544
|
const { domain, done, group, key, onDone } = options;
|
|
639
545
|
const { emit } = useDomainKit();
|
|
640
|
-
const
|
|
546
|
+
const inherited = useReadOnly();
|
|
547
|
+
const readOnly = options.readOnly ?? inherited;
|
|
641
548
|
const runner = useRunner();
|
|
642
549
|
const [state, setState] = useState(State$2.Idle());
|
|
643
550
|
const held = useRef({
|
|
@@ -670,7 +577,7 @@ function useAttempt(options) {
|
|
|
670
577
|
}));
|
|
671
578
|
}, [domain, emit]);
|
|
672
579
|
const applyWith = useCallback((plan, approval) => {
|
|
673
|
-
if (group === void 0 || held.current.key !== key) return;
|
|
580
|
+
if (readOnly || group === void 0 || held.current.key !== key) return;
|
|
674
581
|
const command = () => {
|
|
675
582
|
setState(State$2.Applying({
|
|
676
583
|
approval,
|
|
@@ -700,9 +607,11 @@ function useAttempt(options) {
|
|
|
700
607
|
key,
|
|
701
608
|
onDone,
|
|
702
609
|
onFailure,
|
|
610
|
+
readOnly,
|
|
703
611
|
runner
|
|
704
612
|
]);
|
|
705
613
|
const buildPlan = useCallback(() => {
|
|
614
|
+
if (readOnly) return;
|
|
706
615
|
const effect = build.current();
|
|
707
616
|
if (effect === null) return;
|
|
708
617
|
const command = () => {
|
|
@@ -728,11 +637,12 @@ function useAttempt(options) {
|
|
|
728
637
|
}, [
|
|
729
638
|
key,
|
|
730
639
|
onFailure,
|
|
640
|
+
readOnly,
|
|
731
641
|
runner
|
|
732
642
|
]);
|
|
733
643
|
const approve = useCallback((operationIds) => {
|
|
734
644
|
const plan = held.current.plan;
|
|
735
|
-
if (group === void 0 || plan === null || held.current.key !== key) return;
|
|
645
|
+
if (readOnly || group === void 0 || plan === null || held.current.key !== key) return;
|
|
736
646
|
const command = () => {
|
|
737
647
|
setState(State$2.Approving({ plan }));
|
|
738
648
|
runner.run(group.approve({
|
|
@@ -761,11 +671,12 @@ function useAttempt(options) {
|
|
|
761
671
|
group,
|
|
762
672
|
key,
|
|
763
673
|
onFailure,
|
|
674
|
+
readOnly,
|
|
764
675
|
runner
|
|
765
676
|
]);
|
|
766
677
|
const reject = useCallback((reason) => {
|
|
767
678
|
const plan = held.current.plan;
|
|
768
|
-
if (group === void 0 || plan === null || held.current.key !== key) return;
|
|
679
|
+
if (readOnly || group === void 0 || plan === null || held.current.key !== key) return;
|
|
769
680
|
const command = () => {
|
|
770
681
|
setState(State$2.Rejecting({ plan }));
|
|
771
682
|
runner.run(group.reject({
|
|
@@ -796,11 +707,12 @@ function useAttempt(options) {
|
|
|
796
707
|
group,
|
|
797
708
|
key,
|
|
798
709
|
onFailure,
|
|
710
|
+
readOnly,
|
|
799
711
|
runner
|
|
800
712
|
]);
|
|
801
713
|
const apply = useCallback(() => {
|
|
802
714
|
const { approval, plan } = held.current;
|
|
803
|
-
if (approval === null || plan === null) return;
|
|
715
|
+
if (readOnly || approval === null || plan === null) return;
|
|
804
716
|
applyWith(plan, approval);
|
|
805
717
|
}, [applyWith]);
|
|
806
718
|
const retry = useCallback(() => {
|
|
@@ -843,715 +755,18 @@ function useAttempt(options) {
|
|
|
843
755
|
};
|
|
844
756
|
}
|
|
845
757
|
//#endregion
|
|
846
|
-
//#region src/
|
|
847
|
-
/**
|
|
848
|
-
* What a flow says when a step ends badly: media, title, description, and the action that follows.
|
|
849
|
-
* Every flow's `Outcome` binds its controller to these parts, so a failure reads the same way in
|
|
850
|
-
* the connect dialog, the review dialog, and the verification popover.
|
|
851
|
-
*
|
|
852
|
-
* <Connect.Outcome controller={connection} /> the default composition
|
|
853
|
-
*
|
|
854
|
-
* <Connect.Outcome controller={connection}> a composition of your own
|
|
855
|
-
* <Outcome.Header>
|
|
856
|
-
* <Outcome.Media><MyIcon /></Outcome.Media>
|
|
857
|
-
* <Outcome.Title />
|
|
858
|
-
* <Outcome.Description />
|
|
859
|
-
* </Outcome.Header>
|
|
860
|
-
* <Outcome.Content />
|
|
861
|
-
* </Connect.Outcome>
|
|
862
|
-
*/
|
|
863
|
-
var outcome_exports = /* @__PURE__ */ __exportAll({
|
|
864
|
-
Composition: () => Composition,
|
|
865
|
-
Content: () => Content,
|
|
866
|
-
Description: () => Description,
|
|
867
|
-
Header: () => Header$1,
|
|
868
|
-
Media: () => Media,
|
|
869
|
-
Provider: () => Provider,
|
|
870
|
-
Root: () => Root$6,
|
|
871
|
-
Title: () => Title,
|
|
872
|
-
useOutcome: () => useOutcome
|
|
873
|
-
});
|
|
874
|
-
const fallback = {
|
|
875
|
-
description: "",
|
|
876
|
-
layout: "card",
|
|
877
|
-
retry: null,
|
|
878
|
-
retryPart: "flow-retry",
|
|
879
|
-
title: "",
|
|
880
|
-
tone: "danger"
|
|
881
|
-
};
|
|
882
|
-
const Context = createContext(null);
|
|
883
|
-
/** What the surrounding flow resolved, for a part of your own inside `X.Outcome`. */
|
|
884
|
-
function useOutcome() {
|
|
885
|
-
return useContext(Context) ?? fallback;
|
|
886
|
-
}
|
|
887
|
-
/** Binds a resolved outcome to the parts below it. Flows render this; hosts rarely do. */
|
|
888
|
-
function Provider({ children, value }) {
|
|
889
|
-
return /* @__PURE__ */ jsx(Context.Provider, {
|
|
890
|
-
value,
|
|
891
|
-
children
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* The outcome itself. It announces, so a customer reading with assistive tech hears it. A `tone`
|
|
896
|
-
* or `layout` of its own reaches every part below it, so the glyph and the words agree with the
|
|
897
|
-
* root's own attributes.
|
|
898
|
-
*/
|
|
899
|
-
function Root$6({ layout, tone, ...props }) {
|
|
900
|
-
const value = useOutcome();
|
|
901
|
-
const resolved = {
|
|
902
|
-
...value,
|
|
903
|
-
layout: layout ?? value.layout,
|
|
904
|
-
tone: tone ?? value.tone
|
|
905
|
-
};
|
|
906
|
-
const element = usePart("div", props, {
|
|
907
|
-
layout: resolved.layout,
|
|
908
|
-
tone: resolved.tone
|
|
909
|
-
}, {
|
|
910
|
-
"data-domainkit-part": "outcome",
|
|
911
|
-
"data-layout": resolved.layout,
|
|
912
|
-
"data-tone": resolved.tone,
|
|
913
|
-
role: "alert"
|
|
914
|
-
});
|
|
915
|
-
return /* @__PURE__ */ jsx(Provider, {
|
|
916
|
-
value: resolved,
|
|
917
|
-
children: element
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
/** Media, title, and description together, so the action below them can move on its own. */
|
|
921
|
-
function Header$1(props) {
|
|
922
|
-
const { layout, tone } = useOutcome();
|
|
923
|
-
return usePart("div", props, {
|
|
924
|
-
layout,
|
|
925
|
-
tone
|
|
926
|
-
}, { "data-domainkit-part": "outcome-header" });
|
|
927
|
-
}
|
|
928
|
-
/** The glyph for the tone. Children replace it; `icons` on `DomainKit.Root` replaces the set. */
|
|
929
|
-
function Media({ variant = "icon", ...props }) {
|
|
930
|
-
const { layout, tone } = useOutcome();
|
|
931
|
-
const icons = useIcons();
|
|
932
|
-
const glyph = tone === "success" ? icons.success : tone === "warning" ? icons.warning : icons.failure;
|
|
933
|
-
return usePart("div", props, {
|
|
934
|
-
layout,
|
|
935
|
-
tone
|
|
936
|
-
}, {
|
|
937
|
-
"aria-hidden": "true",
|
|
938
|
-
children: glyph,
|
|
939
|
-
"data-domainkit-part": "outcome-media",
|
|
940
|
-
"data-variant": variant
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
/** The heading from the catalog. Children win, for a host that words it itself. */
|
|
944
|
-
function Title(props) {
|
|
945
|
-
const { layout, title, tone } = useOutcome();
|
|
946
|
-
return usePart("div", props, {
|
|
947
|
-
layout,
|
|
948
|
-
tone
|
|
949
|
-
}, {
|
|
950
|
-
children: title,
|
|
951
|
-
"data-domainkit-part": "outcome-title"
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
/** What to do about it, from the catalog. An outcome whose title says everything renders none. */
|
|
955
|
-
function Description(props) {
|
|
956
|
-
const { description, layout, tone } = useOutcome();
|
|
957
|
-
const element = usePart("p", props, {
|
|
958
|
-
layout,
|
|
959
|
-
tone
|
|
960
|
-
}, {
|
|
961
|
-
children: description,
|
|
962
|
-
"data-domainkit-part": "outcome-description"
|
|
963
|
-
});
|
|
964
|
-
return description === "" && props.children === void 0 ? null : element;
|
|
965
|
-
}
|
|
966
|
-
/**
|
|
967
|
-
* The actions under the header. Without children it is the flow's retry, and a flow that allows
|
|
968
|
-
* none renders nothing: read-only hides every control that would start a write again.
|
|
969
|
-
*/
|
|
970
|
-
function Content(props) {
|
|
971
|
-
const { messages } = useDomainKit();
|
|
972
|
-
const { layout, retry, retryPart, tone } = useOutcome();
|
|
973
|
-
const element = usePart("div", props, {
|
|
974
|
-
layout,
|
|
975
|
-
tone
|
|
976
|
-
}, {
|
|
977
|
-
children: retry === null ? null : /* @__PURE__ */ jsx("button", {
|
|
978
|
-
"data-domainkit-part": retryPart,
|
|
979
|
-
onClick: retry,
|
|
980
|
-
type: "button",
|
|
981
|
-
children: messages.retry
|
|
982
|
-
}),
|
|
983
|
-
"data-domainkit-part": "outcome-content"
|
|
984
|
-
});
|
|
985
|
-
return retry === null && props.children === void 0 ? null : element;
|
|
986
|
-
}
|
|
987
|
-
/** Media, title, and description in a header, then the action. What `X.Outcome` renders alone. */
|
|
988
|
-
function Composition() {
|
|
989
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Header$1, { children: [
|
|
990
|
-
/* @__PURE__ */ jsx(Media, {}),
|
|
991
|
-
/* @__PURE__ */ jsx(Title, {}),
|
|
992
|
-
/* @__PURE__ */ jsx(Description, {})
|
|
993
|
-
] }), /* @__PURE__ */ jsx(Content, {})] });
|
|
994
|
-
}
|
|
995
|
-
//#endregion
|
|
996
|
-
//#region src/records.tsx
|
|
997
|
-
var records_exports = /* @__PURE__ */ __exportAll({
|
|
998
|
-
Body: () => Body$1,
|
|
999
|
-
Caption: () => Caption,
|
|
1000
|
-
Card: () => Card$1,
|
|
1001
|
-
CardContent: () => CardContent,
|
|
1002
|
-
CardHeader: () => CardHeader,
|
|
1003
|
-
CardTitle: () => CardTitle,
|
|
1004
|
-
Cell: () => Cell,
|
|
1005
|
-
CopyValue: () => CopyValue,
|
|
1006
|
-
Footer: () => Footer,
|
|
1007
|
-
Head: () => Head,
|
|
1008
|
-
Header: () => Header,
|
|
1009
|
-
Purpose: () => Purpose,
|
|
1010
|
-
Root: () => Root$5,
|
|
1011
|
-
Row: () => Row,
|
|
1012
|
-
Status: () => Status$5,
|
|
1013
|
-
Table: () => Table,
|
|
1014
|
-
Value: () => Value,
|
|
1015
|
-
ZoneFile: () => ZoneFile,
|
|
1016
|
-
copyText: () => copyText,
|
|
1017
|
-
downloadZoneFile: () => downloadZoneFile,
|
|
1018
|
-
identity: () => identity,
|
|
1019
|
-
requirementsKey: () => requirementsKey,
|
|
1020
|
-
statusOf: () => statusOf,
|
|
1021
|
-
toZoneFile: () => toZoneFile,
|
|
1022
|
-
useCopy: () => useCopy
|
|
1023
|
-
});
|
|
1024
|
-
const absolute = (value) => value.endsWith(".") ? value : `${value}.`;
|
|
1025
|
-
const textEncoder = new TextEncoder();
|
|
1026
|
-
const escapeTxtCharacter = (character) => {
|
|
1027
|
-
if (character === "\"" || character === "\\") return `\\${character}`;
|
|
1028
|
-
const codePoint = character.codePointAt(0);
|
|
1029
|
-
if (codePoint !== void 0 && (codePoint < 32 || codePoint === 127)) return `\\${codePoint.toString().padStart(3, "0")}`;
|
|
1030
|
-
return character;
|
|
1031
|
-
};
|
|
1032
|
-
/** TXT strings are capped at 255 bytes each, so long values are split and quoted per chunk. */
|
|
1033
|
-
const txtValue = (value) => {
|
|
1034
|
-
const chunks = [];
|
|
1035
|
-
let chunk = "";
|
|
1036
|
-
let byteLength = 0;
|
|
1037
|
-
for (const character of value) {
|
|
1038
|
-
const characterByteLength = textEncoder.encode(character).byteLength;
|
|
1039
|
-
if (byteLength + characterByteLength > 255) {
|
|
1040
|
-
chunks.push(chunk);
|
|
1041
|
-
chunk = "";
|
|
1042
|
-
byteLength = 0;
|
|
1043
|
-
}
|
|
1044
|
-
chunk += escapeTxtCharacter(character);
|
|
1045
|
-
byteLength += characterByteLength;
|
|
1046
|
-
}
|
|
1047
|
-
chunks.push(chunk);
|
|
1048
|
-
return chunks.map((part) => `"${part}"`).join(" ");
|
|
1049
|
-
};
|
|
1050
|
-
/** The RR type as a zone file spells it. The tag and the type name are the same string. */
|
|
1051
|
-
const rrType = (record) => record._tag;
|
|
1052
|
-
const zoneData = (record) => {
|
|
1053
|
-
switch (record._tag) {
|
|
1054
|
-
case "A":
|
|
1055
|
-
case "AAAA": return record.address;
|
|
1056
|
-
case "CNAME": return absolute(record.target);
|
|
1057
|
-
case "NS": return absolute(record.nameserver);
|
|
1058
|
-
case "TXT": return txtValue(record.value);
|
|
1059
|
-
case "MX": return `${record.priority} ${absolute(record.exchange)}`;
|
|
1060
|
-
case "CAA": return `${record.flags} ${record.tag} "${record.value}"`;
|
|
1061
|
-
case "SRV": return `${record.priority} ${record.weight} ${record.port} ${absolute(record.target)}`;
|
|
1062
|
-
}
|
|
1063
|
-
};
|
|
1064
|
-
const toZoneFile = (records) => `${records.map((record) => `${absolute(record.name)}${record.ttl === null ? "" : ` ${record.ttl}`} IN ${rrType(record)} ${zoneData(record)}`).join("\n")}\n`;
|
|
1065
|
-
const downloadText = (filename, contents) => {
|
|
1066
|
-
if (typeof document === "undefined") return;
|
|
1067
|
-
const blob = new Blob([contents], { type: "text/plain;charset=utf-8" });
|
|
1068
|
-
const url = URL.createObjectURL(blob);
|
|
1069
|
-
const anchor = document.createElement("a");
|
|
1070
|
-
anchor.download = filename;
|
|
1071
|
-
anchor.href = url;
|
|
1072
|
-
document.body.append(anchor);
|
|
1073
|
-
anchor.click();
|
|
1074
|
-
anchor.remove();
|
|
1075
|
-
URL.revokeObjectURL(url);
|
|
1076
|
-
};
|
|
1077
|
-
const downloadZoneFile = (domain, records) => downloadText(`${domain}.txt`, toZoneFile(records));
|
|
1078
|
-
const copyText = (value) => {
|
|
1079
|
-
if (typeof navigator === "undefined" || navigator.clipboard === void 0) return Promise.resolve(false);
|
|
1080
|
-
return navigator.clipboard.writeText(value).then(() => true, () => false);
|
|
1081
|
-
};
|
|
1082
|
-
function useCopy(value, resetAfter = 2e3) {
|
|
1083
|
-
const [copied, setCopied] = useState(false);
|
|
1084
|
-
const reset = useRef(void 0);
|
|
1085
|
-
useEffect(() => () => clearTimeout(reset.current), []);
|
|
1086
|
-
return {
|
|
1087
|
-
copied,
|
|
1088
|
-
copy: () => {
|
|
1089
|
-
copyText(value).then((ok) => {
|
|
1090
|
-
if (!ok) return;
|
|
1091
|
-
setCopied(true);
|
|
1092
|
-
clearTimeout(reset.current);
|
|
1093
|
-
reset.current = setTimeout(() => setCopied(false), resetAfter);
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
function CopyValue({ value, ...props }) {
|
|
1099
|
-
const { messages } = useDomainKit();
|
|
1100
|
-
const icons = useIcons();
|
|
1101
|
-
const { copied, copy } = useCopy(value);
|
|
1102
|
-
return usePart("span", props, { copied }, {
|
|
1103
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("code", { children: value }), /* @__PURE__ */ jsxs("button", {
|
|
1104
|
-
"aria-label": `${copied ? messages.copied : messages.copy} ${value}`,
|
|
1105
|
-
"data-domainkit-part": "copy-action",
|
|
1106
|
-
onClick: copy,
|
|
1107
|
-
type: "button",
|
|
1108
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1109
|
-
"aria-hidden": "true",
|
|
1110
|
-
"data-domainkit-part": "copy-glyph",
|
|
1111
|
-
"data-icon": "",
|
|
1112
|
-
"data-state": "idle",
|
|
1113
|
-
children: icons.copy
|
|
1114
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
1115
|
-
"aria-hidden": "true",
|
|
1116
|
-
"data-domainkit-part": "copy-glyph",
|
|
1117
|
-
"data-icon": "",
|
|
1118
|
-
"data-state": "done",
|
|
1119
|
-
children: icons.copied
|
|
1120
|
-
})]
|
|
1121
|
-
})] }),
|
|
1122
|
-
"data-copied": copied ? "" : void 0,
|
|
1123
|
-
"data-domainkit-part": "copy-value"
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
function ZoneFile({ domain, records, ...props }) {
|
|
1127
|
-
const { messages } = useDomainKit();
|
|
1128
|
-
const icons = useIcons();
|
|
1129
|
-
const { copied, copy } = useCopy(toZoneFile(records));
|
|
1130
|
-
return usePart("div", props, { count: records.length }, {
|
|
1131
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
|
|
1132
|
-
"aria-label": copied ? messages.copied : messages.copyZone,
|
|
1133
|
-
"data-domainkit-part": "zone-copy",
|
|
1134
|
-
onClick: copy,
|
|
1135
|
-
type: "button",
|
|
1136
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1137
|
-
"aria-hidden": "true",
|
|
1138
|
-
"data-icon": "inline-start",
|
|
1139
|
-
children: copied ? icons.copied : icons.copy
|
|
1140
|
-
}), copied ? messages.copied : messages.copyZone]
|
|
1141
|
-
}), /* @__PURE__ */ jsxs("button", {
|
|
1142
|
-
"aria-label": messages.download,
|
|
1143
|
-
"data-domainkit-part": "zone-download",
|
|
1144
|
-
onClick: () => downloadZoneFile(domain, records),
|
|
1145
|
-
type: "button",
|
|
1146
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1147
|
-
"aria-hidden": "true",
|
|
1148
|
-
"data-icon": "inline-start",
|
|
1149
|
-
children: icons.download
|
|
1150
|
-
}), messages.download]
|
|
1151
|
-
})] }),
|
|
1152
|
-
"data-domainkit-part": "zone-file"
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
/** The readiness this record reached, or `null` when nothing has observed it. */
|
|
1156
|
-
const statusOf = (record, readiness) => readiness?.requirements.find((requirement) => DnsRecord.equals(requirement.record, record))?.status ?? null;
|
|
1157
|
-
function Status$5({ status, ...props }) {
|
|
1158
|
-
const { messages } = useDomainKit();
|
|
1159
|
-
const icons = useIcons();
|
|
1160
|
-
const glyph = status === "satisfied" ? icons.success : status === "unknown" ? icons.pending : icons.failure;
|
|
1161
|
-
return usePart("span", props, { status }, {
|
|
1162
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1163
|
-
"aria-hidden": "true",
|
|
1164
|
-
"data-icon": "inline-start",
|
|
1165
|
-
children: glyph
|
|
1166
|
-
}), messages.requirementStatus(status)] }),
|
|
1167
|
-
"data-domainkit-part": "record-status",
|
|
1168
|
-
"data-status": status
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
const Header = leafPart("thead", "records-header");
|
|
1172
|
-
const Body$1 = leafPart("tbody", "records-body");
|
|
1173
|
-
const Footer = leafPart("tfoot", "records-footer");
|
|
1174
|
-
const Row = leafPart("tr", "records-row");
|
|
1175
|
-
const Head = leafPart("th", "records-head");
|
|
1176
|
-
const Cell = leafPart("td", "records-cell");
|
|
1177
|
-
const Caption = leafPart("caption", "records-caption");
|
|
1178
|
-
const Value = leafPart("span", "record-value");
|
|
1179
|
-
const Purpose = leafPart("span", "record-purpose");
|
|
1180
|
-
const CardHeader = leafPart("div", "record-card-head");
|
|
1181
|
-
const CardTitle = leafPart("strong", "record-card-title");
|
|
1182
|
-
const CardContent = leafPart("dl", "record-card-content");
|
|
1183
|
-
function Root$5({ count = 0, ...props }) {
|
|
1184
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1185
|
-
"data-domainkit-part": "records-panel",
|
|
1186
|
-
children: usePart("table", props, { count }, { "data-domainkit-part": "records-table" })
|
|
1187
|
-
});
|
|
1188
|
-
}
|
|
1189
|
-
/** A stable React key for a requirement: type, name, and data identify a record. */
|
|
1190
|
-
const identity = (record) => [
|
|
1191
|
-
rrType(record),
|
|
1192
|
-
record.name,
|
|
1193
|
-
DnsRecord.data(record)
|
|
1194
|
-
].join(":");
|
|
1195
|
-
/**
|
|
1196
|
-
* A requirement set keyed by everything it carries, for memos that decide whether to re-send it.
|
|
1197
|
-
* `identity` is deliberately narrower: it answers "is this the same record" for a React key, while
|
|
1198
|
-
* a plan and an observation are built from `ttl`, `policy`, and `purpose` as well. `policy` in
|
|
1199
|
-
* particular decides whether a name may hold anything else, so readiness turns on it.
|
|
1200
|
-
*/
|
|
1201
|
-
const requirementsKey = (records) => JSON.stringify(records);
|
|
1202
|
-
function Card$1({ readiness, record, ...props }) {
|
|
1203
|
-
const { messages } = useDomainKit();
|
|
1204
|
-
const status = statusOf(record, readiness);
|
|
1205
|
-
return usePart("section", props, { record: identity(record) }, {
|
|
1206
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(CardHeader, { children: [/* @__PURE__ */ jsx(CardTitle, { children: messages.recordType(record) }), status === null ? null : /* @__PURE__ */ jsx(Status$5, { status })] }), /* @__PURE__ */ jsxs(CardContent, { children: [
|
|
1207
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: messages.headingName }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(CopyValue, { value: record.name }) })] }),
|
|
1208
|
-
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: messages.headingValue }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(CopyValue, { value: DnsRecord.data(record) }) })] }),
|
|
1209
|
-
record.purpose === void 0 ? null : /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: messages.headingPurpose }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(Purpose, { children: record.purpose }) })] })
|
|
1210
|
-
] })] }),
|
|
1211
|
-
"data-domainkit-part": "record-card"
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
/** The default records presentation: one row per requirement, with readiness when there is any. */
|
|
1215
|
-
function Table({ caption, readiness, records, ...props }) {
|
|
1216
|
-
const { messages } = useDomainKit();
|
|
1217
|
-
const withStatus = readiness !== null && readiness !== void 0;
|
|
1218
|
-
return /* @__PURE__ */ jsxs(Root$5, {
|
|
1219
|
-
count: records.length,
|
|
1220
|
-
...props,
|
|
1221
|
-
children: [
|
|
1222
|
-
caption === void 0 ? null : /* @__PURE__ */ jsx(Caption, { children: caption }),
|
|
1223
|
-
/* @__PURE__ */ jsx(Header, { children: /* @__PURE__ */ jsxs(Row, { children: [
|
|
1224
|
-
/* @__PURE__ */ jsx(Head, {
|
|
1225
|
-
scope: "col",
|
|
1226
|
-
children: messages.headingType
|
|
1227
|
-
}),
|
|
1228
|
-
/* @__PURE__ */ jsx(Head, {
|
|
1229
|
-
scope: "col",
|
|
1230
|
-
children: messages.headingName
|
|
1231
|
-
}),
|
|
1232
|
-
/* @__PURE__ */ jsx(Head, {
|
|
1233
|
-
scope: "col",
|
|
1234
|
-
children: messages.headingValue
|
|
1235
|
-
}),
|
|
1236
|
-
withStatus ? /* @__PURE__ */ jsx(Head, {
|
|
1237
|
-
"data-column": "status",
|
|
1238
|
-
scope: "col",
|
|
1239
|
-
children: messages.headingStatus
|
|
1240
|
-
}) : null
|
|
1241
|
-
] }) }),
|
|
1242
|
-
/* @__PURE__ */ jsx(Body$1, { children: records.map((record) => {
|
|
1243
|
-
const status = statusOf(record, readiness);
|
|
1244
|
-
return /* @__PURE__ */ jsxs(Row, { children: [
|
|
1245
|
-
/* @__PURE__ */ jsx(Cell, { children: messages.recordType(record) }),
|
|
1246
|
-
/* @__PURE__ */ jsx(Cell, { children: /* @__PURE__ */ jsx(CopyValue, { value: record.name }) }),
|
|
1247
|
-
/* @__PURE__ */ jsx(Cell, { children: /* @__PURE__ */ jsxs(Value, { children: [/* @__PURE__ */ jsx(CopyValue, { value: DnsRecord.data(record) }), record.purpose === void 0 ? null : /* @__PURE__ */ jsx(Purpose, { children: record.purpose })] }) }),
|
|
1248
|
-
withStatus ? /* @__PURE__ */ jsx(Cell, {
|
|
1249
|
-
"data-column": "status",
|
|
1250
|
-
children: status === null ? null : /* @__PURE__ */ jsx(Status$5, { status })
|
|
1251
|
-
}) : null
|
|
1252
|
-
] }, identity(record));
|
|
1253
|
-
}) })
|
|
1254
|
-
]
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
//#endregion
|
|
1258
|
-
//#region src/operations.tsx
|
|
1259
|
-
var operations_exports = /* @__PURE__ */ __exportAll({
|
|
1260
|
-
Item: () => Item,
|
|
1261
|
-
Kind: () => Kind,
|
|
1262
|
-
List: () => List,
|
|
1263
|
-
Name: () => Name,
|
|
1264
|
-
Reason: () => Reason$1,
|
|
1265
|
-
Record: () => Record,
|
|
1266
|
-
Root: () => Root$4,
|
|
1267
|
-
Type: () => Type
|
|
1268
|
-
});
|
|
1269
|
-
const Kind = leafPart("span", "operation-kind");
|
|
1270
|
-
const Type = leafPart("strong", "operation-type");
|
|
1271
|
-
const Record = leafPart("span", "operation-record");
|
|
1272
|
-
const Name = leafPart("span", "operation-name");
|
|
1273
|
-
const Reason$1 = leafPart("span", "operation-reason");
|
|
1274
|
-
function Item({ operation, ...props }) {
|
|
1275
|
-
const { messages } = useDomainKit();
|
|
1276
|
-
return usePart("li", props, { operation: operation._tag }, {
|
|
1277
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1278
|
-
/* @__PURE__ */ jsx(Kind, { children: messages.operation(operation) }),
|
|
1279
|
-
" ",
|
|
1280
|
-
/* @__PURE__ */ jsx(Type, { children: messages.recordType(operation.record) }),
|
|
1281
|
-
" ",
|
|
1282
|
-
/* @__PURE__ */ jsxs(Record, { children: [/* @__PURE__ */ jsx(Name, { children: operation.record.name }), /* @__PURE__ */ jsx(CopyValue, { value: DnsRecord.data(operation.record) })] }),
|
|
1283
|
-
operation._tag === "Conflict" ? /* @__PURE__ */ jsx(Reason$1, { children: messages.conflictReason(operation.reason) }) : null
|
|
1284
|
-
] }),
|
|
1285
|
-
"data-domainkit-part": "operation-item",
|
|
1286
|
-
"data-operation": operation._tag
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
function Root$4({ count = 0, kind, ...props }) {
|
|
1290
|
-
return usePart("ul", props, {
|
|
1291
|
-
count,
|
|
1292
|
-
kind
|
|
1293
|
-
}, {
|
|
1294
|
-
"data-domainkit-part": kind === "provisioning" ? "plan-operations" : "cleanup-operations",
|
|
1295
|
-
"data-kind": kind
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
/** Every operation the plan holds, in plan order, so a customer reviews exactly what will run. */
|
|
1299
|
-
function List({ plan, ...props }) {
|
|
1300
|
-
return /* @__PURE__ */ jsx(Root$4, {
|
|
1301
|
-
count: plan.operations.length,
|
|
1302
|
-
kind: plan.kind,
|
|
1303
|
-
...props,
|
|
1304
|
-
children: plan.operations.map((operation) => /* @__PURE__ */ jsx(Item, { operation }, operation.id))
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
//#endregion
|
|
1308
|
-
//#region src/review.tsx
|
|
1309
|
-
/**
|
|
1310
|
-
* The review surface both `Provision` and `Cleanup` render: what the plan will do, the Approve
|
|
1311
|
-
* and Decline actions D35 defines, and the outcome. `kind` picks the wording; nothing else about
|
|
1312
|
-
* the two flows differs on screen.
|
|
1313
|
-
*/
|
|
1314
|
-
const busy = (status) => status === "Planning" || status === "Approving" || status === "Applying" || status === "Rejecting";
|
|
1315
|
-
/** What the flow is doing right now, or what it finished doing. */
|
|
1316
|
-
function Status$4({ controller, kind, ...props }) {
|
|
1317
|
-
const { messages } = useDomainKit();
|
|
1318
|
-
const state = controller.state;
|
|
1319
|
-
const text = () => {
|
|
1320
|
-
switch (state._tag) {
|
|
1321
|
-
case "Idle": return "";
|
|
1322
|
-
case "Planning": return kind === "provisioning" ? messages.planning : messages.planningCleanup;
|
|
1323
|
-
case "Planned": return "";
|
|
1324
|
-
case "Approving": return messages.approving;
|
|
1325
|
-
case "Applying": return kind === "provisioning" ? messages.applying : messages.cleaning;
|
|
1326
|
-
case "Rejecting": return messages.rejecting;
|
|
1327
|
-
case "Rejected": return messages.declinedBy(state.attempt.rejection?.actorId ?? "");
|
|
1328
|
-
case "Applied": return "";
|
|
1329
|
-
case "Failure": return "";
|
|
1330
|
-
}
|
|
1331
|
-
};
|
|
1332
|
-
return usePart("p", props, { status: state._tag }, {
|
|
1333
|
-
children: text(),
|
|
1334
|
-
"data-domainkit-part": kind === "provisioning" ? "provisioning-status" : "cleanup-status",
|
|
1335
|
-
"data-state": state._tag,
|
|
1336
|
-
role: busy(state._tag) ? "status" : void 0
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* How the attempt ended: a failure, a receipt that only partly landed, or one that landed whole.
|
|
1341
|
-
* Media, title, description, and the retry the flow allows. Children replace the composition and
|
|
1342
|
-
* keep the binding.
|
|
1343
|
-
*/
|
|
1344
|
-
function Outcome$4({ children, controller, kind, ...props }) {
|
|
1345
|
-
const { messages } = useDomainKit();
|
|
1346
|
-
const readOnly = useReadOnly();
|
|
1347
|
-
const state = controller.state;
|
|
1348
|
-
const retryPart = kind === "provisioning" ? "provisioning-retry" : "cleanup-retry";
|
|
1349
|
-
if (state._tag !== "Failure" && state._tag !== "Applied") return null;
|
|
1350
|
-
const partial = state._tag === "Applied" && state.receipt.status === "partial";
|
|
1351
|
-
const words = state._tag === "Failure" ? outcome(state.error, messages) : partial ? {
|
|
1352
|
-
description: kind === "provisioning" ? messages.partiallyApplied : messages.partiallyCleaned,
|
|
1353
|
-
title: kind === "provisioning" ? messages.partiallyAppliedTitle : messages.partiallyCleanedTitle
|
|
1354
|
-
} : {
|
|
1355
|
-
description: "",
|
|
1356
|
-
title: kind === "provisioning" ? messages.applied(state.receipt) : messages.cleaned(state.receipt)
|
|
1357
|
-
};
|
|
1358
|
-
return /* @__PURE__ */ jsx(Provider, {
|
|
1359
|
-
value: {
|
|
1360
|
-
description: words.description ?? "",
|
|
1361
|
-
layout: props.layout ?? "card",
|
|
1362
|
-
retry: readOnly || state._tag !== "Failure" ? null : controller.retry,
|
|
1363
|
-
retryPart,
|
|
1364
|
-
title: words.title,
|
|
1365
|
-
tone: state._tag === "Failure" ? "danger" : partial ? "warning" : "success"
|
|
1366
|
-
},
|
|
1367
|
-
children: /* @__PURE__ */ jsx(Root$6, {
|
|
1368
|
-
...props,
|
|
1369
|
-
children: children ?? /* @__PURE__ */ jsx(Composition, {})
|
|
1370
|
-
})
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
/** What a plan would actually write: an operation blocked by a conflict is not one of them. */
|
|
1374
|
-
const addable = (plan) => plan === null ? [] : plan.operations.filter((operation) => operation._tag !== "Conflict");
|
|
1375
|
-
/**
|
|
1376
|
-
* The primary action, and Decline. The primary authorizes the digest and applies it in one step,
|
|
1377
|
-
* so it says what it will do; where a conflict blocks part of a plan it approves the rest by id,
|
|
1378
|
-
* and where every operation is blocked it says what to fix instead. Neither renders until there is
|
|
1379
|
-
* a plan to act on.
|
|
1380
|
-
*/
|
|
1381
|
-
function Actions$2({ controller, kind, ...props }) {
|
|
1382
|
-
const { messages } = useDomainKit();
|
|
1383
|
-
const readOnly = useReadOnly();
|
|
1384
|
-
const state = controller.state;
|
|
1385
|
-
const plan = state._tag === "Planned" ? state.plan : null;
|
|
1386
|
-
const running = busy(state._tag);
|
|
1387
|
-
const writes = addable(plan);
|
|
1388
|
-
const blocked = plan !== null && writes.length === 0;
|
|
1389
|
-
const element = usePart("div", props, { status: state._tag }, {
|
|
1390
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1391
|
-
/* @__PURE__ */ jsx("button", {
|
|
1392
|
-
"data-domainkit-part": kind === "provisioning" ? "plan-apply" : "cleanup-apply",
|
|
1393
|
-
disabled: running || plan === null || writes.length === 0,
|
|
1394
|
-
onClick: () => controller.approve(writes.length === plan?.operations.length ? void 0 : writes.map((operation) => operation.id)),
|
|
1395
|
-
type: "button",
|
|
1396
|
-
children: kind === "provisioning" ? messages.addRecords(writes.length) : messages.approve
|
|
1397
|
-
}),
|
|
1398
|
-
blocked ? /* @__PURE__ */ jsx("p", {
|
|
1399
|
-
"data-domainkit-part": "plan-blocked",
|
|
1400
|
-
children: messages.everyRecordConflicts
|
|
1401
|
-
}) : null,
|
|
1402
|
-
/* @__PURE__ */ jsx("button", {
|
|
1403
|
-
"data-domainkit-part": "plan-decline",
|
|
1404
|
-
disabled: running || plan === null,
|
|
1405
|
-
onClick: () => controller.reject(),
|
|
1406
|
-
type: "button",
|
|
1407
|
-
children: messages.decline
|
|
1408
|
-
})
|
|
1409
|
-
] }),
|
|
1410
|
-
"data-domainkit-part": "review-actions"
|
|
1411
|
-
});
|
|
1412
|
-
if (readOnly) return null;
|
|
1413
|
-
return plan === null ? null : element;
|
|
1414
|
-
}
|
|
1415
|
-
/** The operations under review, or the reason there is nothing to review. */
|
|
1416
|
-
function Body({ controller, kind }) {
|
|
1417
|
-
const { messages } = useDomainKit();
|
|
1418
|
-
const state = controller.state;
|
|
1419
|
-
const plan = state._tag === "Planned" || state._tag === "Approving" || state._tag === "Applying" || state._tag === "Rejecting" || state._tag === "Rejected" ? state.plan : state._tag === "Applied" ? state.plan : null;
|
|
1420
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1421
|
-
"data-domainkit-part": "dialog-body",
|
|
1422
|
-
children: [
|
|
1423
|
-
/* @__PURE__ */ jsx(Status$4, {
|
|
1424
|
-
controller,
|
|
1425
|
-
kind
|
|
1426
|
-
}),
|
|
1427
|
-
plan === null ? null : plan.operations.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
1428
|
-
"data-domainkit-part": "plan-empty",
|
|
1429
|
-
children: messages.noChanges
|
|
1430
|
-
}) : /* @__PURE__ */ jsx(List, { plan }),
|
|
1431
|
-
state._tag === "Rejected" && state.attempt.rejection?.reason != null ? /* @__PURE__ */ jsx("p", {
|
|
1432
|
-
"data-domainkit-part": "plan-decline-reason",
|
|
1433
|
-
children: messages.declineReason(state.attempt.rejection.reason)
|
|
1434
|
-
}) : null,
|
|
1435
|
-
/* @__PURE__ */ jsx(Outcome$4, {
|
|
1436
|
-
controller,
|
|
1437
|
-
kind
|
|
1438
|
-
})
|
|
1439
|
-
]
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
/** The review dialog. `render` swaps the surface; the contents stay the flow's. */
|
|
1443
|
-
function Dialog$4({ children, controller, kind, onOpenChange, open, render, trigger }) {
|
|
1444
|
-
const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
|
|
1445
|
-
const readOnly = useReadOnly();
|
|
1446
|
-
const [uncontrolled, setUncontrolled] = useState(false);
|
|
1447
|
-
const isOpen = open ?? uncontrolled;
|
|
1448
|
-
const close = useRef(() => {});
|
|
1449
|
-
close.current = (next) => {
|
|
1450
|
-
if (open === void 0) setUncontrolled(next);
|
|
1451
|
-
onOpenChange?.(next);
|
|
1452
|
-
};
|
|
1453
|
-
const setOpen = (next) => close.current(next);
|
|
1454
|
-
const state = controller.state;
|
|
1455
|
-
const landed = state._tag === "Applied" && state.receipt.status === "complete";
|
|
1456
|
-
useEffect(() => {
|
|
1457
|
-
if (landed) close.current(false);
|
|
1458
|
-
}, [landed]);
|
|
1459
|
-
const running = busy(controller.state._tag);
|
|
1460
|
-
const body = children ?? /* @__PURE__ */ jsx(Body, {
|
|
1461
|
-
controller,
|
|
1462
|
-
kind
|
|
1463
|
-
});
|
|
1464
|
-
const title = kind === "provisioning" ? messages.planTitle : messages.cleanupTitle;
|
|
1465
|
-
const label = trigger ?? (kind === "provisioning" ? messages.reviewChanges : messages.cleanUp);
|
|
1466
|
-
if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
|
|
1467
|
-
if (render !== void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
1468
|
-
"data-domainkit-part": kind === "provisioning" ? "plan-trigger" : "cleanup-trigger",
|
|
1469
|
-
onClick: () => {
|
|
1470
|
-
setOpen(true);
|
|
1471
|
-
controller.plan();
|
|
1472
|
-
},
|
|
1473
|
-
type: "button",
|
|
1474
|
-
children: label
|
|
1475
|
-
}), render({
|
|
1476
|
-
children: body,
|
|
1477
|
-
open: isOpen
|
|
1478
|
-
})] });
|
|
1479
|
-
return /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
1480
|
-
onOpenChange: (next, details) => {
|
|
1481
|
-
if (!next && running) {
|
|
1482
|
-
details.cancel();
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
setOpen(next);
|
|
1486
|
-
},
|
|
1487
|
-
open: isOpen,
|
|
1488
|
-
children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
1489
|
-
"data-domainkit-part": kind === "provisioning" ? "plan-trigger" : "cleanup-trigger",
|
|
1490
|
-
onClick: controller.plan,
|
|
1491
|
-
children: label
|
|
1492
|
-
}), /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
1493
|
-
container: portalContainer,
|
|
1494
|
-
children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
1495
|
-
"data-color-scheme": colorScheme,
|
|
1496
|
-
"data-domainkit-part": "dialog-backdrop",
|
|
1497
|
-
"data-domainkit-root": "",
|
|
1498
|
-
style: themeStyle
|
|
1499
|
-
}), /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
1500
|
-
"data-color-scheme": colorScheme,
|
|
1501
|
-
"data-domainkit-part": kind === "provisioning" ? "plan-dialog" : "cleanup-dialog",
|
|
1502
|
-
"data-domainkit-root": "",
|
|
1503
|
-
style: themeStyle,
|
|
1504
|
-
children: [
|
|
1505
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1506
|
-
"data-domainkit-part": "dialog-header",
|
|
1507
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1508
|
-
"data-domainkit-part": "dialog-heading",
|
|
1509
|
-
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
1510
|
-
"data-domainkit-part": "dialog-title",
|
|
1511
|
-
children: title
|
|
1512
|
-
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
1513
|
-
"data-domainkit-part": "dialog-description",
|
|
1514
|
-
children: kind === "provisioning" ? messages.planConsent : messages.cleanupConsent
|
|
1515
|
-
})]
|
|
1516
|
-
}), running ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1517
|
-
"aria-label": messages.close,
|
|
1518
|
-
"data-domainkit-part": "dialog-close",
|
|
1519
|
-
children: "×"
|
|
1520
|
-
})]
|
|
1521
|
-
}),
|
|
1522
|
-
body,
|
|
1523
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1524
|
-
"data-domainkit-part": "dialog-footer",
|
|
1525
|
-
children: [/* @__PURE__ */ jsx(Actions$2, {
|
|
1526
|
-
controller,
|
|
1527
|
-
kind
|
|
1528
|
-
}), running || kind === "provisioning" ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1529
|
-
"data-domainkit-part": "dialog-cancel",
|
|
1530
|
-
children: messages.cancel
|
|
1531
|
-
})]
|
|
1532
|
-
})
|
|
1533
|
-
]
|
|
1534
|
-
})]
|
|
1535
|
-
})]
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
//#endregion
|
|
1539
|
-
//#region src/cleanup.tsx
|
|
758
|
+
//#region src/cleanup.ts
|
|
1540
759
|
var cleanup_exports = /* @__PURE__ */ __exportAll({
|
|
1541
|
-
Actions: () => Actions$1,
|
|
1542
|
-
Dialog: () => Dialog$3,
|
|
1543
|
-
Flow: () => Flow$3,
|
|
1544
|
-
Outcome: () => Outcome$3,
|
|
1545
|
-
Root: () => Root$3,
|
|
1546
760
|
State: () => State$2,
|
|
1547
|
-
|
|
761
|
+
pendingPlan: () => pendingPlan,
|
|
762
|
+
planOf: () => planOf,
|
|
1548
763
|
useController: () => useController$3
|
|
1549
764
|
});
|
|
1550
765
|
/**
|
|
1551
766
|
* Cleanup is bound to a receipt: it removes only what an apply proved DomainKit created. When the
|
|
1552
767
|
* host does not name one, the controller reads the domain's latest from the snapshot.
|
|
1553
768
|
*/
|
|
1554
|
-
function useController$3({ domain, onCleaned, receiptId }) {
|
|
769
|
+
function useController$3({ domain, onCleaned, readOnly, receiptId }) {
|
|
1555
770
|
const { transport } = useDomainKit();
|
|
1556
771
|
const group = transport.cleanup;
|
|
1557
772
|
const connection = transport.connection;
|
|
@@ -1564,6 +779,7 @@ function useController$3({ domain, onCleaned, receiptId }) {
|
|
|
1564
779
|
}),
|
|
1565
780
|
group,
|
|
1566
781
|
onDone: onCleaned,
|
|
782
|
+
readOnly,
|
|
1567
783
|
plan: useCallback(() => {
|
|
1568
784
|
if (group === void 0) return null;
|
|
1569
785
|
if (receiptId !== void 0) return group.plan(receiptId);
|
|
@@ -1580,95 +796,305 @@ function useController$3({ domain, onCleaned, receiptId }) {
|
|
|
1580
796
|
])
|
|
1581
797
|
});
|
|
1582
798
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
799
|
+
//#endregion
|
|
800
|
+
//#region src/domain-field.ts
|
|
801
|
+
const ZonesState = Data.taggedEnum();
|
|
802
|
+
const nothing = {
|
|
803
|
+
connections: [],
|
|
804
|
+
providers: [],
|
|
805
|
+
zones: []
|
|
806
|
+
};
|
|
807
|
+
/**
|
|
808
|
+
* Every zone this owner's connections reach. A connection whose credential the provider turned
|
|
809
|
+
* down is listed in `connections` as `reconnect` and contributes no zones, so one dead account
|
|
810
|
+
* never costs the customer the others.
|
|
811
|
+
*/
|
|
812
|
+
function useZones({ provider } = {}) {
|
|
813
|
+
const { revision, transport } = useDomainKit();
|
|
814
|
+
const connection = transport.connection;
|
|
815
|
+
const runner = useRunner();
|
|
816
|
+
const [state, setState] = useState(ZonesState.Loading());
|
|
817
|
+
const load = useCallback(() => {
|
|
818
|
+
if (connection === void 0) return;
|
|
819
|
+
setState(ZonesState.Loading());
|
|
820
|
+
runner.run(connection.zones(provider === void 0 ? {} : { provider }), {
|
|
821
|
+
onFailure: (error) => setState(ZonesState.Failure({ error })),
|
|
822
|
+
onSuccess: (listing) => setState(ZonesState.Ready({
|
|
823
|
+
connections: listing.connections,
|
|
824
|
+
providers: listing.providers,
|
|
825
|
+
zones: listing.zones
|
|
826
|
+
}))
|
|
827
|
+
});
|
|
828
|
+
}, [
|
|
829
|
+
connection,
|
|
830
|
+
provider,
|
|
831
|
+
runner
|
|
832
|
+
]);
|
|
833
|
+
useEffect(load, [load, revision]);
|
|
834
|
+
const held = state._tag === "Ready" ? state : nothing;
|
|
835
|
+
return {
|
|
836
|
+
connections: held.connections,
|
|
837
|
+
providers: held.providers,
|
|
838
|
+
refresh: load,
|
|
839
|
+
state,
|
|
840
|
+
zones: held.zones
|
|
841
|
+
};
|
|
1612
842
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
843
|
+
const AccountsState = Data.taggedEnum();
|
|
844
|
+
const currentUrl$1 = () => typeof window === "undefined" ? null : window.location.href;
|
|
845
|
+
/**
|
|
846
|
+
* The accounts this owner holds, and the one way to add another: connect a provider without naming
|
|
847
|
+
* a domain. A token method finishes in place; an interactive one redirects and comes back through
|
|
848
|
+
* the library's own callback, which lands on `returnTo`.
|
|
849
|
+
*/
|
|
850
|
+
function useAccounts({ readOnly: refused, returnTo } = {}) {
|
|
851
|
+
const { navigate, transport } = useDomainKit();
|
|
852
|
+
const inherited = useReadOnly();
|
|
853
|
+
const readOnly = refused ?? inherited;
|
|
854
|
+
const connection = transport.connection;
|
|
855
|
+
const runner = useRunner();
|
|
856
|
+
const listing = useZones();
|
|
857
|
+
const [command, setCommand] = useState(null);
|
|
858
|
+
const refresh = listing.refresh;
|
|
859
|
+
const submit = useCallback((input) => {
|
|
860
|
+
if (connection === void 0 || readOnly) return;
|
|
861
|
+
const destination = returnTo === void 0 ? currentUrl$1() : returnTo;
|
|
862
|
+
const interactive = destination === null ? {} : { returnTo: destination };
|
|
863
|
+
const method = input.method === "token" ? Transport.Method.token(input.values ?? {}) : input.method === "oauth" ? Transport.Method.oauth(interactive) : Transport.Method.integration(interactive);
|
|
864
|
+
setCommand(AccountsState.Submitting({ provider: input.provider }));
|
|
865
|
+
const granting = input.connectionId === void 0 ? connection.start({
|
|
866
|
+
method,
|
|
867
|
+
provider: input.provider
|
|
868
|
+
}) : connection.reconnect({
|
|
869
|
+
connectionId: input.connectionId,
|
|
870
|
+
method
|
|
871
|
+
});
|
|
872
|
+
runner.run(granting, {
|
|
873
|
+
onFailure: (error) => setCommand(AccountsState.Failure({ error })),
|
|
874
|
+
onSuccess: (started) => {
|
|
875
|
+
if (started._tag === "Redirect") {
|
|
876
|
+
setCommand(AccountsState.Redirecting({ url: started.authorizationUrl }));
|
|
877
|
+
navigate(started.authorizationUrl);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
setCommand(null);
|
|
881
|
+
refresh();
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}, [
|
|
885
|
+
connection,
|
|
886
|
+
navigate,
|
|
887
|
+
readOnly,
|
|
888
|
+
refresh,
|
|
889
|
+
returnTo,
|
|
890
|
+
runner
|
|
891
|
+
]);
|
|
892
|
+
const connect = useCallback((input) => submit(input), [submit]);
|
|
893
|
+
const reconnect = useCallback((input) => submit(input), [submit]);
|
|
894
|
+
const state = command ?? (listing.state._tag === "Loading" ? AccountsState.Loading() : listing.state._tag === "Failure" ? AccountsState.Failure({ error: listing.state.error }) : AccountsState.Ready());
|
|
895
|
+
return {
|
|
896
|
+
connect,
|
|
897
|
+
reconnect,
|
|
898
|
+
connections: listing.connections,
|
|
899
|
+
providers: listing.providers,
|
|
900
|
+
refresh,
|
|
901
|
+
state,
|
|
902
|
+
zones: listing.zones
|
|
903
|
+
};
|
|
1631
904
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
905
|
+
const normalize = (value) => value.trim().toLowerCase().replace(/\.$/, "");
|
|
906
|
+
/**
|
|
907
|
+
* The zone a value already sits in: the longest listed zone the value is at or under. Two accounts
|
|
908
|
+
* can serve the same zone, so `preferred` names the one the customer picked and wins among equals;
|
|
909
|
+
* without it the first listed account answers.
|
|
910
|
+
*/
|
|
911
|
+
const placementOf = (value, zones, preferred) => {
|
|
912
|
+
const domain = normalize(value);
|
|
913
|
+
if (domain === "") return null;
|
|
914
|
+
const matching = zones.filter((candidate) => {
|
|
915
|
+
const zone = normalize(candidate.zone);
|
|
916
|
+
return domain === zone || domain.endsWith(`.${zone}`);
|
|
917
|
+
});
|
|
918
|
+
const longest = matching.reduce((best, candidate) => best === null || normalize(candidate.zone).length > normalize(best.zone).length ? candidate : best, null);
|
|
919
|
+
if (longest === null) return null;
|
|
920
|
+
const closest = matching.filter((candidate) => normalize(candidate.zone) === normalize(longest.zone));
|
|
921
|
+
const best = closest.find((candidate) => preferred != null && candidate.connectionId === preferred.connectionId && normalize(candidate.zone) === normalize(preferred.zone)) ?? closest[0] ?? longest;
|
|
922
|
+
return {
|
|
923
|
+
placement: {
|
|
924
|
+
connectionId: best.connectionId,
|
|
925
|
+
zone: best.zone
|
|
926
|
+
},
|
|
927
|
+
zone: best
|
|
928
|
+
};
|
|
929
|
+
};
|
|
1638
930
|
/**
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
931
|
+
* What the customer typed in front of the zone they are reaching for. "mail.ex" against
|
|
932
|
+
* `example.com` is the subdomain "mail"; "ex" on its own is none.
|
|
1641
933
|
*/
|
|
1642
|
-
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
934
|
+
const subdomainOf = (value, zone) => {
|
|
935
|
+
const typed = normalize(value);
|
|
936
|
+
const target = normalize(zone);
|
|
937
|
+
if (typed === "" || target.startsWith(typed)) return "";
|
|
938
|
+
const cut = typed.lastIndexOf(".");
|
|
939
|
+
if (cut < 0) return "";
|
|
940
|
+
const head = typed.slice(0, cut);
|
|
941
|
+
const tail = typed.slice(cut + 1);
|
|
942
|
+
return target.startsWith(tail) ? head : "";
|
|
943
|
+
};
|
|
944
|
+
/** Zones the typed value is reaching for: a prefix of the zone, or a subdomain of one. */
|
|
945
|
+
const suggestionsFor = (value, zones) => {
|
|
946
|
+
const typed = normalize(value);
|
|
947
|
+
if (typed === "") return zones;
|
|
948
|
+
return zones.filter((candidate) => {
|
|
949
|
+
const zone = normalize(candidate.zone);
|
|
950
|
+
if (zone.startsWith(typed)) return true;
|
|
951
|
+
const cut = typed.lastIndexOf(".");
|
|
952
|
+
if (cut < 0) return false;
|
|
953
|
+
const tail = typed.slice(cut + 1);
|
|
954
|
+
return tail !== "" && zone.startsWith(tail);
|
|
1652
955
|
});
|
|
956
|
+
};
|
|
957
|
+
/** The value a suggestion completes to: whatever subdomain was typed, then the zone. */
|
|
958
|
+
const completionOf = (value, zone) => {
|
|
959
|
+
const subdomain = subdomainOf(value, zone.zone);
|
|
960
|
+
return subdomain === "" ? zone.zone : `${subdomain}.${zone.zone}`;
|
|
961
|
+
};
|
|
962
|
+
const optionId = (listId, zone) => `${listId}-${zone.connectionId}-${zone.zone}`;
|
|
963
|
+
/**
|
|
964
|
+
* One input over every zone the workspace's accounts reach, as props rather than markup. Arrow
|
|
965
|
+
* keys move the highlight, Tab or Enter completes it, Escape closes the list, and `found` says
|
|
966
|
+
* where the records will go.
|
|
967
|
+
*/
|
|
968
|
+
function useDomainField({ id, onChange, onResolve, value, zones }) {
|
|
969
|
+
const messages = useMessages();
|
|
970
|
+
const generated = useId();
|
|
971
|
+
const inputId = id ?? generated;
|
|
972
|
+
const listId = `${inputId}-suggestions`;
|
|
973
|
+
const [open, setOpen] = useState(false);
|
|
974
|
+
const [active, setActive] = useState(0);
|
|
975
|
+
const [chosen, setChosen] = useState(null);
|
|
976
|
+
const suggestions = suggestionsFor(value, zones);
|
|
977
|
+
const highlighted = suggestions[Math.min(active, Math.max(suggestions.length - 1, 0))] ?? null;
|
|
978
|
+
const found = placementOf(value, zones, chosen);
|
|
979
|
+
const showing = open && suggestions.length > 0;
|
|
980
|
+
const placement = found?.placement ?? null;
|
|
981
|
+
const connectionId = placement?.connectionId ?? null;
|
|
982
|
+
const placedIn = placement?.zone ?? null;
|
|
983
|
+
const announced = useRef(null);
|
|
984
|
+
useEffect(() => {
|
|
985
|
+
const signature = `${value}|${connectionId ?? ""}|${placedIn ?? ""}`;
|
|
986
|
+
if (announced.current === signature) return;
|
|
987
|
+
announced.current = signature;
|
|
988
|
+
onResolve?.({
|
|
989
|
+
connection: connectionId === null || placedIn === null ? null : {
|
|
990
|
+
connectionId,
|
|
991
|
+
zone: placedIn
|
|
992
|
+
},
|
|
993
|
+
domain: value
|
|
994
|
+
});
|
|
995
|
+
}, [
|
|
996
|
+
connectionId,
|
|
997
|
+
onResolve,
|
|
998
|
+
placedIn,
|
|
999
|
+
value
|
|
1000
|
+
]);
|
|
1001
|
+
const complete = (zone) => {
|
|
1002
|
+
onChange(completionOf(value, zone));
|
|
1003
|
+
setChosen({
|
|
1004
|
+
connectionId: zone.connectionId,
|
|
1005
|
+
zone: zone.zone
|
|
1006
|
+
});
|
|
1007
|
+
setOpen(false);
|
|
1008
|
+
setActive(0);
|
|
1009
|
+
};
|
|
1010
|
+
return {
|
|
1011
|
+
close: () => setOpen(false),
|
|
1012
|
+
complete,
|
|
1013
|
+
found,
|
|
1014
|
+
highlighted,
|
|
1015
|
+
inputProps: {
|
|
1016
|
+
"aria-activedescendant": showing && highlighted !== null ? optionId(listId, highlighted) : void 0,
|
|
1017
|
+
"aria-autocomplete": "list",
|
|
1018
|
+
"aria-controls": listId,
|
|
1019
|
+
"aria-expanded": showing,
|
|
1020
|
+
autoComplete: "off",
|
|
1021
|
+
id: inputId,
|
|
1022
|
+
onChange: (event) => {
|
|
1023
|
+
onChange(event.target.value);
|
|
1024
|
+
setActive(0);
|
|
1025
|
+
setOpen(true);
|
|
1026
|
+
},
|
|
1027
|
+
onFocus: () => setOpen(true),
|
|
1028
|
+
onKeyDown: (event) => {
|
|
1029
|
+
if (event.key === "Escape") {
|
|
1030
|
+
setOpen(false);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (suggestions.length === 0) return;
|
|
1034
|
+
if (event.key === "ArrowDown") {
|
|
1035
|
+
event.preventDefault();
|
|
1036
|
+
setOpen(true);
|
|
1037
|
+
setActive((index) => (index + 1) % suggestions.length);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (event.key === "ArrowUp") {
|
|
1041
|
+
event.preventDefault();
|
|
1042
|
+
setOpen(true);
|
|
1043
|
+
setActive((index) => (index + suggestions.length - 1) % suggestions.length);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if ((event.key === "Tab" || event.key === "Enter") && showing && highlighted !== null) {
|
|
1047
|
+
event.preventDefault();
|
|
1048
|
+
complete(highlighted);
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
role: "combobox",
|
|
1052
|
+
type: "text",
|
|
1053
|
+
value
|
|
1054
|
+
},
|
|
1055
|
+
listboxProps: {
|
|
1056
|
+
"aria-label": messages.zoneSuggestions,
|
|
1057
|
+
hidden: !showing,
|
|
1058
|
+
id: listId,
|
|
1059
|
+
role: "listbox"
|
|
1060
|
+
},
|
|
1061
|
+
open: showing,
|
|
1062
|
+
optionProps: (zone) => ({
|
|
1063
|
+
"aria-selected": zone === highlighted,
|
|
1064
|
+
id: optionId(listId, zone),
|
|
1065
|
+
onMouseDown: (event) => {
|
|
1066
|
+
event.preventDefault();
|
|
1067
|
+
complete(zone);
|
|
1068
|
+
},
|
|
1069
|
+
role: "option"
|
|
1070
|
+
}),
|
|
1071
|
+
suggestions
|
|
1072
|
+
};
|
|
1653
1073
|
}
|
|
1654
1074
|
//#endregion
|
|
1655
|
-
//#region src/connect.
|
|
1075
|
+
//#region src/connect.ts
|
|
1656
1076
|
var connect_exports = /* @__PURE__ */ __exportAll({
|
|
1657
|
-
|
|
1658
|
-
Dialog: () => Dialog$2,
|
|
1659
|
-
DisconnectDialog: () => DisconnectDialog,
|
|
1660
|
-
Flow: () => Flow$2,
|
|
1661
|
-
Form: () => Form,
|
|
1662
|
-
Outcome: () => Outcome$2,
|
|
1663
|
-
Prompt: () => Prompt,
|
|
1664
|
-
Root: () => Root$2,
|
|
1077
|
+
AccountsState: () => AccountsState,
|
|
1665
1078
|
State: () => State$1,
|
|
1666
|
-
|
|
1079
|
+
ZonesState: () => ZonesState,
|
|
1667
1080
|
answeredInPlace: () => answeredInPlace,
|
|
1081
|
+
attempted: () => attempted,
|
|
1082
|
+
completionOf: () => completionOf,
|
|
1083
|
+
describeMethods: () => describeMethods,
|
|
1084
|
+
displayName: () => displayName,
|
|
1668
1085
|
holdsConnection: () => holdsConnection,
|
|
1669
1086
|
hostProvider: () => hostProvider,
|
|
1670
1087
|
offering: () => offering,
|
|
1671
|
-
|
|
1088
|
+
placementOf: () => placementOf,
|
|
1089
|
+
providerOf: () => providerOf,
|
|
1090
|
+
reconnect: () => reconnect,
|
|
1091
|
+
rejectedField: () => rejectedField,
|
|
1092
|
+
reusableConnections: () => reusableConnections,
|
|
1093
|
+
suggestionsFor: () => suggestionsFor,
|
|
1094
|
+
useAccounts: () => useAccounts,
|
|
1095
|
+
useController: () => useController$2,
|
|
1096
|
+
useDomainField: () => useDomainField,
|
|
1097
|
+
useZones: () => useZones
|
|
1672
1098
|
});
|
|
1673
1099
|
const State$1 = Data.taggedEnum();
|
|
1674
1100
|
/**
|
|
@@ -1720,10 +1146,12 @@ const settled = (snapshot, discovery) => {
|
|
|
1720
1146
|
* transport declares it and the domain has no connection yet; a discovery failure is not the
|
|
1721
1147
|
* customer's problem, so the provider list still renders.
|
|
1722
1148
|
*/
|
|
1723
|
-
function useController$2({ domain, returnTo }) {
|
|
1149
|
+
function useController$2({ domain, readOnly: refused, returnTo }) {
|
|
1724
1150
|
const { emit, navigate, revision, transport } = useDomainKit();
|
|
1725
|
-
const
|
|
1151
|
+
const inherited = useReadOnly();
|
|
1152
|
+
const readOnly = refused ?? inherited;
|
|
1726
1153
|
const connection = transport.connection;
|
|
1154
|
+
const provisioning = transport.provisioning;
|
|
1727
1155
|
const runner = useRunner();
|
|
1728
1156
|
const [state, setState] = useState(State$1.Loading({ snapshot: null }));
|
|
1729
1157
|
const held = useRef({
|
|
@@ -1797,20 +1225,26 @@ function useController$2({ domain, returnTo }) {
|
|
|
1797
1225
|
const started = useCallback((result) => {
|
|
1798
1226
|
if (held.current.domain !== domain) return;
|
|
1799
1227
|
switch (result._tag) {
|
|
1800
|
-
case "Connected":
|
|
1228
|
+
case "Connected": {
|
|
1229
|
+
const snapshot = result.snapshot;
|
|
1230
|
+
setEstablished((count) => count + 1);
|
|
1231
|
+
if (snapshot === null) {
|
|
1232
|
+
load();
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1801
1235
|
held.current = {
|
|
1802
1236
|
discovery: null,
|
|
1803
1237
|
domain,
|
|
1804
|
-
snapshot
|
|
1238
|
+
snapshot
|
|
1805
1239
|
};
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
connectionId: result.snapshot.connectionId,
|
|
1240
|
+
setState(settled(snapshot, null));
|
|
1241
|
+
if (snapshot.connectionId !== null) emit(Event.Connected({
|
|
1242
|
+
connectionId: snapshot.connectionId,
|
|
1810
1243
|
domain,
|
|
1811
|
-
snapshot
|
|
1244
|
+
snapshot
|
|
1812
1245
|
}));
|
|
1813
1246
|
return;
|
|
1247
|
+
}
|
|
1814
1248
|
case "Redirect":
|
|
1815
1249
|
markReturn(domain);
|
|
1816
1250
|
setState(State$1.Redirecting({ url: result.authorizationUrl }));
|
|
@@ -1827,6 +1261,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1827
1261
|
}, [
|
|
1828
1262
|
domain,
|
|
1829
1263
|
emit,
|
|
1264
|
+
load,
|
|
1830
1265
|
navigate
|
|
1831
1266
|
]);
|
|
1832
1267
|
const submit = useCallback((effect, attempt) => {
|
|
@@ -1854,11 +1289,16 @@ function useController$2({ domain, returnTo }) {
|
|
|
1854
1289
|
started
|
|
1855
1290
|
]);
|
|
1856
1291
|
const connect = useCallback((input) => {
|
|
1857
|
-
if (connection === void 0) return;
|
|
1292
|
+
if (connection === void 0 || readOnly) return;
|
|
1858
1293
|
const destination = input.returnTo ?? (returnTo === void 0 ? currentUrl() : returnTo);
|
|
1859
1294
|
const interactive = destination === null ? {} : { returnTo: destination };
|
|
1860
1295
|
const method = input.method === "token" ? Transport.Method.token(input.values ?? {}) : input.method === "oauth" ? Transport.Method.oauth(interactive) : Transport.Method.integration(interactive);
|
|
1861
|
-
|
|
1296
|
+
const snapshot = held.current.snapshot;
|
|
1297
|
+
const stale = snapshot?.status === "reconnect" && snapshot.provider === input.provider && snapshot.connectionId !== null;
|
|
1298
|
+
submit(stale && snapshot?.connectionId != null ? connection.reconnect({
|
|
1299
|
+
connectionId: snapshot.connectionId,
|
|
1300
|
+
method
|
|
1301
|
+
}) : connection.start({
|
|
1862
1302
|
domain,
|
|
1863
1303
|
method,
|
|
1864
1304
|
provider: input.provider
|
|
@@ -1869,11 +1309,12 @@ function useController$2({ domain, returnTo }) {
|
|
|
1869
1309
|
}, [
|
|
1870
1310
|
connection,
|
|
1871
1311
|
domain,
|
|
1312
|
+
readOnly,
|
|
1872
1313
|
returnTo,
|
|
1873
1314
|
submit
|
|
1874
1315
|
]);
|
|
1875
1316
|
const reuse = useCallback((input) => {
|
|
1876
|
-
if (connection === void 0) return;
|
|
1317
|
+
if (connection === void 0 || readOnly) return;
|
|
1877
1318
|
submit(connection.attach({
|
|
1878
1319
|
connectionId: input.connectionId,
|
|
1879
1320
|
domain,
|
|
@@ -1882,6 +1323,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1882
1323
|
}, [
|
|
1883
1324
|
connection,
|
|
1884
1325
|
domain,
|
|
1326
|
+
readOnly,
|
|
1885
1327
|
submit
|
|
1886
1328
|
]);
|
|
1887
1329
|
const select = useCallback((zone) => {
|
|
@@ -1920,17 +1362,20 @@ function useController$2({ domain, returnTo }) {
|
|
|
1920
1362
|
runner
|
|
1921
1363
|
]);
|
|
1922
1364
|
const detach = useCallback(() => {
|
|
1923
|
-
const attachmentId = held.current.snapshot?.
|
|
1924
|
-
if (connection === void 0 || attachmentId == null
|
|
1365
|
+
const attachmentId = held.current.snapshot?.attachment?.id;
|
|
1366
|
+
if (readOnly || connection === void 0 || attachmentId == null) return;
|
|
1367
|
+
if (held.current.domain !== domain) return;
|
|
1925
1368
|
release(connection.detach(attachmentId), Event.Detached({ domain }));
|
|
1926
1369
|
}, [
|
|
1927
1370
|
connection,
|
|
1928
1371
|
domain,
|
|
1372
|
+
readOnly,
|
|
1929
1373
|
release
|
|
1930
1374
|
]);
|
|
1931
1375
|
const disconnect = useCallback(() => {
|
|
1932
1376
|
const connectionId = held.current.snapshot?.connectionId;
|
|
1933
|
-
if (connection === void 0 || connectionId == null
|
|
1377
|
+
if (readOnly || connection === void 0 || connectionId == null) return;
|
|
1378
|
+
if (held.current.domain !== domain) return;
|
|
1934
1379
|
release(connection.disconnect(connectionId), Event.Disconnected({
|
|
1935
1380
|
connectionId,
|
|
1936
1381
|
domain
|
|
@@ -1938,6 +1383,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1938
1383
|
}, [
|
|
1939
1384
|
connection,
|
|
1940
1385
|
domain,
|
|
1386
|
+
readOnly,
|
|
1941
1387
|
release
|
|
1942
1388
|
]);
|
|
1943
1389
|
const retry = useCallback(() => {
|
|
@@ -1949,6 +1395,44 @@ function useController$2({ domain, returnTo }) {
|
|
|
1949
1395
|
load,
|
|
1950
1396
|
readOnly
|
|
1951
1397
|
]);
|
|
1398
|
+
const sighted = useRef(null);
|
|
1399
|
+
useEffect(() => {
|
|
1400
|
+
if (readOnly || state._tag !== "Disconnected") return;
|
|
1401
|
+
const discovery = state.discovery;
|
|
1402
|
+
if (discovery === null || discovery._tag !== "Resolved") return;
|
|
1403
|
+
const sight = `${domain}|${discovery.connectionId}|${discovery.zone}`;
|
|
1404
|
+
if (sighted.current === sight) return;
|
|
1405
|
+
sighted.current = sight;
|
|
1406
|
+
reuse({
|
|
1407
|
+
connectionId: discovery.connectionId,
|
|
1408
|
+
zone: discovery.zone
|
|
1409
|
+
});
|
|
1410
|
+
}, [
|
|
1411
|
+
domain,
|
|
1412
|
+
readOnly,
|
|
1413
|
+
reuse,
|
|
1414
|
+
state
|
|
1415
|
+
]);
|
|
1416
|
+
const [receipt, setReceipt] = useState(null);
|
|
1417
|
+
const receiptId = snapshotOf(state)?.lastReceiptId ?? null;
|
|
1418
|
+
const readReceipt = useRef(null);
|
|
1419
|
+
useEffect(() => {
|
|
1420
|
+
if (receiptId === null) {
|
|
1421
|
+
readReceipt.current = null;
|
|
1422
|
+
setReceipt(null);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (provisioning === void 0 || readReceipt.current === receiptId) return;
|
|
1426
|
+
readReceipt.current = receiptId;
|
|
1427
|
+
runner.run(provisioning.receipt(Receipt.ReceiptId.make(receiptId)), {
|
|
1428
|
+
onFailure: () => setReceipt(null),
|
|
1429
|
+
onSuccess: (value) => setReceipt(value.id === receiptId ? value : null)
|
|
1430
|
+
});
|
|
1431
|
+
}, [
|
|
1432
|
+
provisioning,
|
|
1433
|
+
receiptId,
|
|
1434
|
+
runner
|
|
1435
|
+
]);
|
|
1952
1436
|
return {
|
|
1953
1437
|
connect,
|
|
1954
1438
|
detach,
|
|
@@ -1957,6 +1441,7 @@ function useController$2({ domain, returnTo }) {
|
|
|
1957
1441
|
established,
|
|
1958
1442
|
discovery: state._tag === "Disconnected" || state._tag === "Failure" || state._tag === "Submitting" ? state.discovery : null,
|
|
1959
1443
|
providers: snapshotOf(state)?.providers ?? [],
|
|
1444
|
+
receipt: receipt?.id === receiptId ? receipt : null,
|
|
1960
1445
|
refresh: load,
|
|
1961
1446
|
retry,
|
|
1962
1447
|
reuse,
|
|
@@ -1965,24 +1450,24 @@ function useController$2({ domain, returnTo }) {
|
|
|
1965
1450
|
state
|
|
1966
1451
|
};
|
|
1967
1452
|
}
|
|
1968
|
-
function Root$2({ controller, ...props }) {
|
|
1969
|
-
return usePart("div", props, { status: controller.state._tag }, {
|
|
1970
|
-
"data-domainkit-part": "connection-root",
|
|
1971
|
-
"data-state": controller.state._tag
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
1453
|
/**
|
|
1975
1454
|
* Whether a failure is already answered where it was raised: beside the method the customer used.
|
|
1976
|
-
* A
|
|
1455
|
+
* A surface rendering one outcome for the whole flow skips it, so the failure announces once.
|
|
1977
1456
|
*/
|
|
1978
1457
|
const answeredInPlace = (controller) => controller.state._tag === "Failure" && controller.state.attempt !== null;
|
|
1979
1458
|
/**
|
|
1980
1459
|
* Whether the surface should read as connected: it is, or a command it started is in flight over a
|
|
1981
|
-
* connection that was. The
|
|
1982
|
-
* command rather than vanishing under the customer half way through it.
|
|
1460
|
+
* connection that was. The provider row and the dialog it opened stay on screen for the length of
|
|
1461
|
+
* the command rather than vanishing under the customer half way through it.
|
|
1983
1462
|
*/
|
|
1984
1463
|
const holdsConnection = (controller) => controller.state._tag === "Connected" || controller.state._tag === "Submitting" && controller.snapshot?.status === "connected";
|
|
1985
1464
|
/**
|
|
1465
|
+
* Whether the provider turned the credential down, so the customer must prove the account again.
|
|
1466
|
+
* Proving it re-credits the connection the workspace already holds and leaves its domains where
|
|
1467
|
+
* they are, which is why the surface offers a reconnect rather than a second account.
|
|
1468
|
+
*/
|
|
1469
|
+
const reconnect = (controller) => controller.state._tag === "Reconnect" || controller.state._tag === "Submitting" && controller.snapshot?.status === "reconnect";
|
|
1470
|
+
/**
|
|
1986
1471
|
* The provider whose nameservers serve this domain, as the snapshot describes it. Discovery names
|
|
1987
1472
|
* it by id; without a descriptor there is nothing to draw, so there is no host.
|
|
1988
1473
|
*/
|
|
@@ -1992,816 +1477,227 @@ const hostProvider = (controller) => {
|
|
|
1992
1477
|
const named = discovery.host.provider;
|
|
1993
1478
|
return controller.providers.find((entry) => entry.id === named) ?? null;
|
|
1994
1479
|
};
|
|
1480
|
+
/** The descriptor for one provider id, or `null` when the snapshot describes no such provider. */
|
|
1481
|
+
const providerOf = (controller, providerId) => providerId == null ? null : controller.providers.find((entry) => entry.id === providerId) ?? null;
|
|
1995
1482
|
/** The provider as the customer knows it, falling back to the id when the snapshot has no name. */
|
|
1996
|
-
const displayName = (controller,
|
|
1483
|
+
const displayName = (controller, providerId) => providerOf(controller, providerId)?.name ?? providerId;
|
|
1997
1484
|
/**
|
|
1998
|
-
*
|
|
1999
|
-
*
|
|
1485
|
+
* Whether the connect surface has anything to offer for this domain: a provider that serves the
|
|
1486
|
+
* zone, a connection discovery already found, one the owner already holds, or a command already
|
|
1487
|
+
* running. Never while a connection is held, which is what `holdsConnection` answers. A host reads
|
|
1488
|
+
* it to order its own offers beside DomainKit's rather than competing with them.
|
|
2000
1489
|
*/
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
const readOnly = useReadOnly();
|
|
2004
|
-
const state = controller.state;
|
|
2005
|
-
if (state._tag !== "Failure") return null;
|
|
2006
|
-
const acted = state.attempt === null ? void 0 : displayName(controller, state.attempt.provider);
|
|
2007
|
-
const words = outcome(state.error, messages, {
|
|
2008
|
-
...controller.snapshot === null ? {} : { domain: controller.snapshot.domain },
|
|
2009
|
-
...acted === void 0 ? {} : { provider: acted }
|
|
2010
|
-
});
|
|
2011
|
-
return /* @__PURE__ */ jsx(Provider, {
|
|
2012
|
-
value: {
|
|
2013
|
-
description: words.description ?? "",
|
|
2014
|
-
layout: props.layout ?? "card",
|
|
2015
|
-
retry: readOnly ? null : controller.retry,
|
|
2016
|
-
retryPart: "connection-retry",
|
|
2017
|
-
title: words.title,
|
|
2018
|
-
tone: "danger"
|
|
2019
|
-
},
|
|
2020
|
-
children: /* @__PURE__ */ jsx(Root$6, {
|
|
2021
|
-
...props,
|
|
2022
|
-
children: children ?? /* @__PURE__ */ jsx(Composition, {})
|
|
2023
|
-
})
|
|
2024
|
-
});
|
|
2025
|
-
}
|
|
2026
|
-
function Status$2({ controller, ...props }) {
|
|
2027
|
-
const { messages } = useDomainKit();
|
|
1490
|
+
const offering = (controller, connect = "detected") => {
|
|
1491
|
+
if (holdsConnection(controller)) return false;
|
|
2028
1492
|
const state = controller.state;
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
const
|
|
2032
|
-
|
|
2033
|
-
case "Loading": return messages.loading;
|
|
2034
|
-
case "Submitting": return messages.connecting;
|
|
2035
|
-
case "Redirecting": return messages.redirecting;
|
|
2036
|
-
case "Connected": return messages.connectedTo(provider);
|
|
2037
|
-
case "Reconnect": return messages.reconnectRequired(provider);
|
|
2038
|
-
case "SelectionRequired": return messages.chooseZone;
|
|
2039
|
-
case "Disconnected": return messages.notConnected;
|
|
2040
|
-
case "Failure": return "";
|
|
2041
|
-
}
|
|
2042
|
-
};
|
|
2043
|
-
return usePart("p", props, { status: state._tag }, {
|
|
2044
|
-
children: text(),
|
|
2045
|
-
"data-domainkit-part": "connection-status",
|
|
2046
|
-
"data-state": state._tag,
|
|
2047
|
-
role: state._tag === "Submitting" || state._tag === "Loading" ? "status" : void 0
|
|
2048
|
-
});
|
|
2049
|
-
}
|
|
2050
|
-
/**
|
|
2051
|
-
* The one field a rejected command was about, if any. A provider that turns down credentials is
|
|
2052
|
-
* answering the secret it was given, and it names no field, so the first secret carries the
|
|
2053
|
-
* answer; anything else is about the request rather than one input.
|
|
2054
|
-
*/
|
|
2055
|
-
const rejected = (reason, fields) => {
|
|
2056
|
-
if (reason === null) return null;
|
|
2057
|
-
if (reason._tag === "InvalidInput") return fields.find((field) => field.name === reason.field)?.name ?? null;
|
|
2058
|
-
if (reason._tag !== "Unauthenticated" && reason._tag !== "Forbidden") return null;
|
|
2059
|
-
return fields.find((field) => field.secret)?.name ?? null;
|
|
1493
|
+
if (state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired" || state._tag === "Reconnect") return true;
|
|
1494
|
+
if (connect !== "detected") return connect === "always";
|
|
1495
|
+
const discovery = controller.discovery;
|
|
1496
|
+
return hostProvider(controller) !== null || discovery !== null && discovery._tag !== "NotFound" || (controller.snapshot?.reusable.length ?? 0) > 0;
|
|
2060
1497
|
};
|
|
2061
1498
|
/**
|
|
2062
|
-
*
|
|
2063
|
-
* the
|
|
2064
|
-
* the form's, not the input's, so a rejection answers beside what the customer typed.
|
|
2065
|
-
*/
|
|
2066
|
-
function TokenField({ controller, docsUrl, field, invalid, onEnter, value }) {
|
|
2067
|
-
const { messages } = useDomainKit();
|
|
2068
|
-
const icons = useIcons();
|
|
2069
|
-
const id = useId();
|
|
2070
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2071
|
-
"data-domainkit-part": "field",
|
|
2072
|
-
"data-invalid": invalid ? "" : void 0,
|
|
2073
|
-
"data-orientation": "vertical",
|
|
2074
|
-
children: [
|
|
2075
|
-
/* @__PURE__ */ jsxs("label", {
|
|
2076
|
-
"data-domainkit-part": "field-label",
|
|
2077
|
-
htmlFor: id,
|
|
2078
|
-
children: [messages.fieldLabel(field.name), field.required ? null : /* @__PURE__ */ jsx("span", {
|
|
2079
|
-
"data-domainkit-part": "field-optional",
|
|
2080
|
-
children: messages.optionalField
|
|
2081
|
-
})]
|
|
2082
|
-
}),
|
|
2083
|
-
/* @__PURE__ */ jsx("input", {
|
|
2084
|
-
"aria-invalid": invalid ? true : void 0,
|
|
2085
|
-
autoComplete: "off",
|
|
2086
|
-
"data-domainkit-part": "field-input",
|
|
2087
|
-
id,
|
|
2088
|
-
name: field.name,
|
|
2089
|
-
onChange: (event) => onEnter(event.target.value),
|
|
2090
|
-
required: field.required,
|
|
2091
|
-
type: field.secret ? "password" : "text",
|
|
2092
|
-
value
|
|
2093
|
-
}),
|
|
2094
|
-
docsUrl === null ? null : /* @__PURE__ */ jsxs("a", {
|
|
2095
|
-
"data-domainkit-part": "field-description",
|
|
2096
|
-
href: docsUrl,
|
|
2097
|
-
rel: "noreferrer",
|
|
2098
|
-
target: "_blank",
|
|
2099
|
-
children: [messages.getToken, /* @__PURE__ */ jsx("span", {
|
|
2100
|
-
"aria-hidden": "true",
|
|
2101
|
-
"data-icon": "inline-end",
|
|
2102
|
-
children: icons.external
|
|
2103
|
-
})]
|
|
2104
|
-
}),
|
|
2105
|
-
invalid ? /* @__PURE__ */ jsx("div", {
|
|
2106
|
-
"data-domainkit-part": "field-error",
|
|
2107
|
-
children: /* @__PURE__ */ jsx(Outcome$2, {
|
|
2108
|
-
controller,
|
|
2109
|
-
layout: "inline"
|
|
2110
|
-
})
|
|
2111
|
-
}) : null
|
|
2112
|
-
]
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
/**
|
|
2116
|
-
* One auth method, rendered from its descriptor. A token method draws an input per declared
|
|
2117
|
-
* field, so a provider that needs an account id alongside a token needs no code here. The fields
|
|
2118
|
-
* a provider does not need sit behind a disclosure, so the common form is one field.
|
|
2119
|
-
*/
|
|
2120
|
-
function Method({ controller, method, onEnter, provider, values }) {
|
|
2121
|
-
const { messages } = useDomainKit();
|
|
2122
|
-
const icons = useIcons();
|
|
2123
|
-
const [more, setMore] = useState(false);
|
|
2124
|
-
const state = controller.state;
|
|
2125
|
-
const busy = state._tag === "Submitting";
|
|
2126
|
-
const failed = state._tag === "Failure" && state.attempt !== null && state.attempt.provider === provider.id && state.attempt.method === method.kind;
|
|
2127
|
-
if (method.fields === null) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
2128
|
-
"data-domainkit-part": method.kind === "oauth" ? "oauth-connect" : "integration-connect",
|
|
2129
|
-
"data-provider": provider.id,
|
|
2130
|
-
disabled: busy,
|
|
2131
|
-
onClick: () => controller.connect({
|
|
2132
|
-
method: method.kind,
|
|
2133
|
-
provider: provider.id
|
|
2134
|
-
}),
|
|
2135
|
-
type: "button",
|
|
2136
|
-
children: method.kind === "oauth" ? messages.methodOAuth(provider.name) : messages.methodIntegration(provider.name)
|
|
2137
|
-
}), failed ? /* @__PURE__ */ jsx(Outcome$2, {
|
|
2138
|
-
controller,
|
|
2139
|
-
layout: "inline"
|
|
2140
|
-
}) : null] });
|
|
2141
|
-
const fields = method.fields;
|
|
2142
|
-
const answered = rejected(failed && state._tag === "Failure" ? state.error.reason : null, fields);
|
|
2143
|
-
const required = fields.filter((field) => field.required);
|
|
2144
|
-
const optional = fields.filter((field) => !field.required);
|
|
2145
|
-
const showMore = more || optional.some((field) => field.name === answered);
|
|
2146
|
-
const explains = (required.find((field) => field.secret) ?? required[0] ?? optional[0])?.name;
|
|
2147
|
-
const render = (field) => /* @__PURE__ */ jsx(TokenField, {
|
|
2148
|
-
controller,
|
|
2149
|
-
docsUrl: field.name === explains ? method.docsUrl : null,
|
|
2150
|
-
field,
|
|
2151
|
-
invalid: field.name === answered,
|
|
2152
|
-
onEnter: (value) => onEnter(field.name, value),
|
|
2153
|
-
value: values[field.name] ?? ""
|
|
2154
|
-
}, field.name);
|
|
2155
|
-
return /* @__PURE__ */ jsxs("form", {
|
|
2156
|
-
"data-domainkit-part": "token-connect",
|
|
2157
|
-
"data-provider": provider.id,
|
|
2158
|
-
onSubmit: (event) => {
|
|
2159
|
-
event.preventDefault();
|
|
2160
|
-
const submitted = {};
|
|
2161
|
-
for (const field of fields) {
|
|
2162
|
-
const value = values[field.name] ?? "";
|
|
2163
|
-
if (value !== "" || field.required) submitted[field.name] = value;
|
|
2164
|
-
}
|
|
2165
|
-
controller.connect({
|
|
2166
|
-
method: method.kind,
|
|
2167
|
-
provider: provider.id,
|
|
2168
|
-
values: submitted
|
|
2169
|
-
});
|
|
2170
|
-
},
|
|
2171
|
-
children: [
|
|
2172
|
-
required.map(render),
|
|
2173
|
-
optional.length === 0 ? null : /* @__PURE__ */ jsxs(Collapsible.Root, {
|
|
2174
|
-
"data-domainkit-part": "more-options",
|
|
2175
|
-
"data-state": showMore ? "open" : "closed",
|
|
2176
|
-
onOpenChange: setMore,
|
|
2177
|
-
open: showMore,
|
|
2178
|
-
children: [/* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
2179
|
-
"data-domainkit-part": "more-options-trigger",
|
|
2180
|
-
children: [messages.moreOptions, /* @__PURE__ */ jsx("span", {
|
|
2181
|
-
"aria-hidden": "true",
|
|
2182
|
-
"data-icon": "inline-end",
|
|
2183
|
-
children: icons.chevron
|
|
2184
|
-
})]
|
|
2185
|
-
}), /* @__PURE__ */ jsx(Collapsible.Panel, {
|
|
2186
|
-
"data-domainkit-part": "more-options-panel",
|
|
2187
|
-
children: optional.map(render)
|
|
2188
|
-
})]
|
|
2189
|
-
}),
|
|
2190
|
-
failed && answered === null ? /* @__PURE__ */ jsx(Outcome$2, {
|
|
2191
|
-
controller,
|
|
2192
|
-
layout: "inline"
|
|
2193
|
-
}) : null,
|
|
2194
|
-
/* @__PURE__ */ jsx("button", {
|
|
2195
|
-
"data-domainkit-part": "token-submit",
|
|
2196
|
-
disabled: busy,
|
|
2197
|
-
type: "submit",
|
|
2198
|
-
children: messages.methodToken
|
|
2199
|
-
})
|
|
2200
|
-
]
|
|
2201
|
-
});
|
|
2202
|
-
}
|
|
2203
|
-
/**
|
|
2204
|
-
* One provider's methods, in the order the descriptor declares them: the interactive ones a
|
|
2205
|
-
* customer clicks through, then the token. Where a provider offers both, the interactive method is
|
|
2206
|
-
* the offer and a plain button opens the token form in its place, so the dialog asks for one
|
|
2207
|
-
* decision at a time. With a heading the whole provider is a disclosure the customer opens; the
|
|
2208
|
-
* provider a narrowed dialog is about carries no heading, because the dialog title already names
|
|
2209
|
-
* it.
|
|
2210
|
-
*/
|
|
2211
|
-
function Authentication({ controller, onEnter, onOpen, onType, open, provider, typing, values }) {
|
|
2212
|
-
const { messages } = useDomainKit();
|
|
2213
|
-
const state = controller.state;
|
|
2214
|
-
const render = (method) => /* @__PURE__ */ jsx(Method, {
|
|
2215
|
-
controller,
|
|
2216
|
-
method,
|
|
2217
|
-
onEnter,
|
|
2218
|
-
provider,
|
|
2219
|
-
values
|
|
2220
|
-
}, `${provider.id}:${method.kind}`);
|
|
2221
|
-
const interactive = provider.methods.filter((method) => method.fields === null);
|
|
2222
|
-
const typed = provider.methods.filter((method) => method.fields !== null);
|
|
2223
|
-
const methods = !(interactive.length > 0 && typed.length > 0) ? provider.methods.map(render) : typing ? typed.map(render) : /* @__PURE__ */ jsxs(Fragment, { children: [interactive.map(render), /* @__PURE__ */ jsx("button", {
|
|
2224
|
-
"data-domainkit-part": "method-alternate",
|
|
2225
|
-
"data-provider": provider.id,
|
|
2226
|
-
disabled: state._tag === "Submitting",
|
|
2227
|
-
onClick: onType,
|
|
2228
|
-
type: "button",
|
|
2229
|
-
children: messages.useTokenInstead
|
|
2230
|
-
})] });
|
|
2231
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
2232
|
-
"data-domainkit-part": "provider-authentication",
|
|
2233
|
-
"data-provider": provider.id,
|
|
2234
|
-
"data-state": open ? "open" : "closed",
|
|
2235
|
-
children: [onOpen === void 0 ? null : /* @__PURE__ */ jsxs("button", {
|
|
2236
|
-
"aria-expanded": open,
|
|
2237
|
-
"data-domainkit-part": "provider-heading",
|
|
2238
|
-
onClick: onOpen,
|
|
2239
|
-
type: "button",
|
|
2240
|
-
children: [/* @__PURE__ */ jsx(Mark, { provider }), /* @__PURE__ */ jsx("strong", { children: provider.name })]
|
|
2241
|
-
}), open ? methods : null]
|
|
2242
|
-
});
|
|
2243
|
-
}
|
|
2244
|
-
/**
|
|
2245
|
-
* Everything a disconnected domain can do: a connection discovery already found, the connections
|
|
2246
|
-
* this owner already has, then each provider's declared methods.
|
|
1499
|
+
* Every connection the customer can attach without proving anything: the one discovery resolved,
|
|
1500
|
+
* the candidates a zone choice is between, and the connections the snapshot lists as reusable.
|
|
2247
1501
|
*/
|
|
2248
|
-
|
|
2249
|
-
const { messages } = useDomainKit();
|
|
2250
|
-
const [opened, setOpened] = useState(null);
|
|
2251
|
-
const [entered, setEntered] = useState(null);
|
|
1502
|
+
const reusableConnections = (controller) => {
|
|
2252
1503
|
const state = controller.state;
|
|
2253
|
-
const owner = controller.domain;
|
|
2254
|
-
if (entered !== null && (entered.domain !== owner || state._tag === "Connected")) setEntered(null);
|
|
2255
|
-
const kept = entered?.domain === owner ? entered : null;
|
|
2256
|
-
const enter = (provider, name, value) => setEntered((previous) => {
|
|
2257
|
-
const held = previous?.domain === owner && previous.provider === provider ? previous : null;
|
|
2258
|
-
return {
|
|
2259
|
-
domain: owner,
|
|
2260
|
-
provider,
|
|
2261
|
-
values: held === null ? { [name]: value } : {
|
|
2262
|
-
...held.values,
|
|
2263
|
-
[name]: value
|
|
2264
|
-
}
|
|
2265
|
-
};
|
|
2266
|
-
});
|
|
2267
|
-
const valuesFor = (provider) => kept?.provider === provider ? kept.values : {};
|
|
2268
|
-
const snapshot = controller.snapshot;
|
|
2269
1504
|
const discovery = controller.discovery;
|
|
2270
1505
|
const candidates = state._tag === "SelectionRequired" ? state.candidates.map((candidate) => ({
|
|
2271
1506
|
...candidate,
|
|
2272
1507
|
connectionId: state.connectionId
|
|
2273
1508
|
})) : discovery !== null && discovery._tag === "SelectionRequired" ? discovery.candidates : [];
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
}),
|
|
2319
|
-
reusable.length === 0 ? null : /* @__PURE__ */ jsxs("section", {
|
|
2320
|
-
"data-domainkit-part": "reusable-connection",
|
|
2321
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
2322
|
-
"data-domainkit-part": "reusable-connection-heading",
|
|
2323
|
-
children: /* @__PURE__ */ jsx("strong", { children: messages.reusableConnections })
|
|
2324
|
-
}), reusable.map((connection) => /* @__PURE__ */ jsx("button", {
|
|
2325
|
-
"data-connection-id": connection.connectionId,
|
|
2326
|
-
"data-domainkit-part": "attach-target",
|
|
2327
|
-
onClick: () => controller.reuse({ connectionId: connection.connectionId }),
|
|
2328
|
-
type: "button",
|
|
2329
|
-
children: messages.useConnection(connection.provider)
|
|
2330
|
-
}, connection.connectionId))]
|
|
2331
|
-
}),
|
|
2332
|
-
providers.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
2333
|
-
"data-domainkit-part": "target-unavailable",
|
|
2334
|
-
children: messages.noProviders
|
|
2335
|
-
}) : host === null ? providers.map((provider) => authentication(provider, opened === null ? provider.id === providers[0]?.id : opened === provider.id)) : /* @__PURE__ */ jsxs(Fragment, { children: [typing === host.id ? /* @__PURE__ */ jsx("button", {
|
|
2336
|
-
"data-domainkit-part": "dialog-back",
|
|
2337
|
-
onClick: () => setTyping(null),
|
|
2338
|
-
type: "button",
|
|
2339
|
-
children: messages.back
|
|
2340
|
-
}) : null, /* @__PURE__ */ jsx(Authentication, {
|
|
2341
|
-
controller,
|
|
2342
|
-
onEnter: (name, value) => enter(host.id, name, value),
|
|
2343
|
-
onType: () => setTyping(host.id),
|
|
2344
|
-
open: true,
|
|
2345
|
-
provider: host,
|
|
2346
|
-
typing: typing === host.id,
|
|
2347
|
-
values: valuesFor(host.id)
|
|
2348
|
-
})] })
|
|
2349
|
-
] }),
|
|
2350
|
-
"data-domainkit-part": "connection-form"
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
/** The connect surface. Without `render` it is a Base UI dialog behind `trigger`. */
|
|
2354
|
-
function Dialog$2({ children, controller, render, title, trigger }) {
|
|
2355
|
-
const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
|
|
2356
|
-
const icons = useIcons();
|
|
2357
|
-
const readOnly = useReadOnly();
|
|
2358
|
-
const [open, setOpen] = useState(false);
|
|
2359
|
-
const state = controller.state;
|
|
2360
|
-
const busy = state._tag === "Submitting";
|
|
2361
|
-
const unattributed = state._tag === "Failure" && !answeredInPlace(controller);
|
|
2362
|
-
const snapshot = controller.snapshot;
|
|
2363
|
-
const [chosen, setChosen] = useState(null);
|
|
2364
|
-
const attached = snapshot?.provider ?? null;
|
|
2365
|
-
const host = hostProvider(controller);
|
|
2366
|
-
const narrowed = controller.providers.find((entry) => entry.id === (chosen ?? attached)) ?? host;
|
|
2367
|
-
const named = narrowed?.name ?? null;
|
|
2368
|
-
const heading = title ?? (named === null ? messages.connectAnyTitle : messages.connectTitle(named));
|
|
2369
|
-
const alternatives = narrowed === null ? [] : controller.providers.filter((entry) => entry.id !== narrowed.id);
|
|
2370
|
-
const running = state._tag === "Loading" || state._tag === "Submitting" || state._tag === "Redirecting" || state._tag === "SelectionRequired";
|
|
2371
|
-
const label = named === null ? messages.connectAnyTitle : messages.connect;
|
|
2372
|
-
const body = children ?? /* @__PURE__ */ jsx(Form, {
|
|
2373
|
-
controller,
|
|
2374
|
-
...chosen === null ? {} : { provider: chosen }
|
|
2375
|
-
});
|
|
2376
|
-
if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
|
|
2377
|
-
if (render !== void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
2378
|
-
"data-domainkit-part": "connection-trigger",
|
|
2379
|
-
onClick: () => setOpen(!open),
|
|
2380
|
-
type: "button",
|
|
2381
|
-
children: trigger ?? label
|
|
2382
|
-
}), render({
|
|
2383
|
-
children: body,
|
|
2384
|
-
open
|
|
2385
|
-
})] });
|
|
2386
|
-
return /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
2387
|
-
onOpenChange: (next, details) => {
|
|
2388
|
-
if (!next && busy) {
|
|
2389
|
-
details.cancel();
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2392
|
-
setOpen(next);
|
|
2393
|
-
},
|
|
2394
|
-
open,
|
|
2395
|
-
children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
2396
|
-
"data-domainkit-part": "connection-trigger",
|
|
2397
|
-
children: trigger ?? label
|
|
2398
|
-
}), /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
2399
|
-
container: portalContainer,
|
|
2400
|
-
children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
2401
|
-
"data-color-scheme": colorScheme,
|
|
2402
|
-
"data-domainkit-part": "dialog-backdrop",
|
|
2403
|
-
"data-domainkit-root": "",
|
|
2404
|
-
style: themeStyle
|
|
2405
|
-
}), /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
2406
|
-
"data-color-scheme": colorScheme,
|
|
2407
|
-
"data-domainkit-part": "connection-dialog",
|
|
2408
|
-
"data-domainkit-root": "",
|
|
2409
|
-
style: themeStyle,
|
|
2410
|
-
children: [
|
|
2411
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2412
|
-
"data-domainkit-part": "dialog-header",
|
|
2413
|
-
children: [
|
|
2414
|
-
narrowed === null ? null : /* @__PURE__ */ jsx("div", {
|
|
2415
|
-
"data-domainkit-part": "dialog-media",
|
|
2416
|
-
children: /* @__PURE__ */ jsx(Mark, { provider: narrowed })
|
|
2417
|
-
}),
|
|
2418
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2419
|
-
"data-domainkit-part": "dialog-heading",
|
|
2420
|
-
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
2421
|
-
"data-domainkit-part": "dialog-title",
|
|
2422
|
-
children: alternatives.length === 0 || title !== void 0 ? heading : /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsxs(Menu.Trigger, {
|
|
2423
|
-
"data-domainkit-part": "dialog-provider-menu",
|
|
2424
|
-
disabled: busy,
|
|
2425
|
-
children: [heading, /* @__PURE__ */ jsx("span", {
|
|
2426
|
-
"aria-hidden": "true",
|
|
2427
|
-
"data-icon": "inline-end",
|
|
2428
|
-
children: icons.chevron
|
|
2429
|
-
})]
|
|
2430
|
-
}), /* @__PURE__ */ jsx(Menu.Portal, {
|
|
2431
|
-
container: portalContainer,
|
|
2432
|
-
children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
2433
|
-
align: "start",
|
|
2434
|
-
"data-domainkit-part": "provider-menu-positioner",
|
|
2435
|
-
sideOffset: 6,
|
|
2436
|
-
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
2437
|
-
"aria-label": messages.useAnotherProvider,
|
|
2438
|
-
"data-color-scheme": colorScheme,
|
|
2439
|
-
"data-domainkit-part": "provider-menu",
|
|
2440
|
-
"data-domainkit-root": "",
|
|
2441
|
-
style: themeStyle,
|
|
2442
|
-
children: alternatives.map((entry) => /* @__PURE__ */ jsxs(Menu.Item, {
|
|
2443
|
-
"data-domainkit-part": "provider-menu-item",
|
|
2444
|
-
"data-provider": entry.id,
|
|
2445
|
-
onClick: () => setChosen(entry.id),
|
|
2446
|
-
children: [/* @__PURE__ */ jsx(Mark, { provider: entry }), entry.name]
|
|
2447
|
-
}, entry.id))
|
|
2448
|
-
})
|
|
2449
|
-
})
|
|
2450
|
-
})] })
|
|
2451
|
-
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
2452
|
-
"data-domainkit-part": "dialog-description",
|
|
2453
|
-
children: messages.connectDescription(snapshot?.domain ?? "")
|
|
2454
|
-
})]
|
|
2455
|
-
}),
|
|
2456
|
-
busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2457
|
-
"aria-label": messages.close,
|
|
2458
|
-
"data-domainkit-part": "dialog-close",
|
|
2459
|
-
children: "×"
|
|
2460
|
-
})
|
|
2461
|
-
]
|
|
2462
|
-
}),
|
|
2463
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2464
|
-
"data-domainkit-part": "dialog-body",
|
|
2465
|
-
children: [
|
|
2466
|
-
running ? /* @__PURE__ */ jsx(Status$2, { controller }) : null,
|
|
2467
|
-
body,
|
|
2468
|
-
unattributed ? /* @__PURE__ */ jsx(Outcome$2, { controller }) : null
|
|
2469
|
-
]
|
|
2470
|
-
}),
|
|
2471
|
-
busy ? null : /* @__PURE__ */ jsx("div", {
|
|
2472
|
-
"data-domainkit-part": "dialog-footer",
|
|
2473
|
-
children: /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2474
|
-
"data-domainkit-part": "dialog-cancel",
|
|
2475
|
-
children: messages.cancel
|
|
2476
|
-
})
|
|
2477
|
-
})
|
|
2478
|
-
]
|
|
2479
|
-
})]
|
|
2480
|
-
})]
|
|
2481
|
-
});
|
|
2482
|
-
}
|
|
1509
|
+
return [
|
|
1510
|
+
...(discovery !== null && discovery._tag === "Resolved" ? [discovery] : []).map((entry) => ({
|
|
1511
|
+
connectionId: entry.connectionId,
|
|
1512
|
+
label: entry.label,
|
|
1513
|
+
zone: entry.zone
|
|
1514
|
+
})),
|
|
1515
|
+
...candidates.map((entry) => ({
|
|
1516
|
+
connectionId: entry.connectionId,
|
|
1517
|
+
label: entry.label,
|
|
1518
|
+
zone: entry.zone
|
|
1519
|
+
})),
|
|
1520
|
+
...(controller.snapshot?.reusable ?? []).map((entry) => ({
|
|
1521
|
+
connectionId: entry.connectionId,
|
|
1522
|
+
label: entry.provider,
|
|
1523
|
+
zone: void 0
|
|
1524
|
+
}))
|
|
1525
|
+
];
|
|
1526
|
+
};
|
|
1527
|
+
const fieldsOf = (method) => {
|
|
1528
|
+
const fields = method.fields;
|
|
1529
|
+
if (fields === null) return null;
|
|
1530
|
+
const required = fields.filter((field) => field.required);
|
|
1531
|
+
const optional = fields.filter((field) => !field.required);
|
|
1532
|
+
return {
|
|
1533
|
+
explains: (required.find((field) => field.secret) ?? required[0] ?? optional[0])?.name ?? null,
|
|
1534
|
+
optional,
|
|
1535
|
+
required
|
|
1536
|
+
};
|
|
1537
|
+
};
|
|
1538
|
+
/** One provider's auth methods, arranged for a surface that asks for one decision at a time. */
|
|
1539
|
+
const describeMethods = (provider) => {
|
|
1540
|
+
const described = provider.methods.map((method) => ({
|
|
1541
|
+
descriptor: method,
|
|
1542
|
+
fields: fieldsOf(method),
|
|
1543
|
+
kind: method.kind
|
|
1544
|
+
}));
|
|
1545
|
+
const interactive = described.filter((method) => method.fields === null);
|
|
1546
|
+
const typed = described.filter((method) => method.fields !== null);
|
|
1547
|
+
return {
|
|
1548
|
+
alternate: interactive.length > 0 && typed.length > 0,
|
|
1549
|
+
interactive,
|
|
1550
|
+
typed
|
|
1551
|
+
};
|
|
1552
|
+
};
|
|
2483
1553
|
/**
|
|
2484
|
-
*
|
|
2485
|
-
*
|
|
2486
|
-
*
|
|
2487
|
-
* renders on this, and `Domain.Flow` reports it so a host can order its own offers beside
|
|
2488
|
-
* DomainKit's rather than competing with them.
|
|
1554
|
+
* The one field a rejected command was about, if any. A provider that turns down credentials is
|
|
1555
|
+
* answering the secret it was given, and it names no field, so the first secret carries the
|
|
1556
|
+
* answer; anything else is about the request rather than one input.
|
|
2489
1557
|
*/
|
|
2490
|
-
const
|
|
2491
|
-
if (
|
|
1558
|
+
const rejectedField = (error, fields) => {
|
|
1559
|
+
if (error === null) return null;
|
|
1560
|
+
const reason = error.reason;
|
|
1561
|
+
if (reason._tag === "InvalidInput") return fields.find((field) => field.name === reason.field)?.name ?? null;
|
|
1562
|
+
if (reason._tag !== "Unauthenticated" && reason._tag !== "Forbidden") return null;
|
|
1563
|
+
return fields.find((field) => field.secret)?.name ?? null;
|
|
1564
|
+
};
|
|
1565
|
+
/** Whether a failure was raised by this provider's method, so the form answers beside it. */
|
|
1566
|
+
const attempted = (controller, provider, method) => {
|
|
2492
1567
|
const state = controller.state;
|
|
2493
|
-
if (state._tag
|
|
2494
|
-
|
|
2495
|
-
const discovery = controller.discovery;
|
|
2496
|
-
return hostProvider(controller) !== null || discovery !== null && discovery._tag !== "NotFound" || (controller.snapshot?.reusable.length ?? 0) > 0;
|
|
1568
|
+
if (state._tag !== "Failure" || state.attempt === null) return null;
|
|
1569
|
+
return state.attempt.provider === provider && state.attempt.method === method ? state.error : null;
|
|
2497
1570
|
};
|
|
1571
|
+
//#endregion
|
|
1572
|
+
//#region src/records.ts
|
|
2498
1573
|
/**
|
|
2499
|
-
*
|
|
2500
|
-
* the
|
|
2501
|
-
* renders unless the host application asks for the dialog anyway.
|
|
1574
|
+
* What a domain's requirements are, as data: the zone file they spell, the clipboard control a
|
|
1575
|
+
* value needs, and the standing one row reports. The table itself is the host's.
|
|
2502
1576
|
*/
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
case "Approving":
|
|
2535
|
-
case "Applying":
|
|
2536
|
-
case "Rejecting":
|
|
2537
|
-
case "Rejected": return state.plan;
|
|
2538
|
-
case "Applied": return state.plan;
|
|
2539
|
-
case "Idle":
|
|
2540
|
-
case "Planning":
|
|
2541
|
-
case "Failure": return null;
|
|
1577
|
+
var records_exports = /* @__PURE__ */ __exportAll({
|
|
1578
|
+
copyText: () => copyText,
|
|
1579
|
+
downloadZoneFile: () => downloadZoneFile,
|
|
1580
|
+
identity: () => identity,
|
|
1581
|
+
requirementsKey: () => requirementsKey,
|
|
1582
|
+
statusOf: () => statusOf,
|
|
1583
|
+
toZoneFile: () => toZoneFile,
|
|
1584
|
+
useCopy: () => useCopy
|
|
1585
|
+
});
|
|
1586
|
+
const absolute = (value) => value.endsWith(".") ? value : `${value}.`;
|
|
1587
|
+
const textEncoder = new TextEncoder();
|
|
1588
|
+
const escapeTxtCharacter = (character) => {
|
|
1589
|
+
if (character === "\"" || character === "\\") return `\\${character}`;
|
|
1590
|
+
const codePoint = character.codePointAt(0);
|
|
1591
|
+
if (codePoint !== void 0 && (codePoint < 32 || codePoint === 127)) return `\\${codePoint.toString().padStart(3, "0")}`;
|
|
1592
|
+
return character;
|
|
1593
|
+
};
|
|
1594
|
+
/** TXT strings are capped at 255 bytes each, so long values are split and quoted per chunk. */
|
|
1595
|
+
const txtValue = (value) => {
|
|
1596
|
+
const chunks = [];
|
|
1597
|
+
let chunk = "";
|
|
1598
|
+
let byteLength = 0;
|
|
1599
|
+
for (const character of value) {
|
|
1600
|
+
const characterByteLength = textEncoder.encode(character).byteLength;
|
|
1601
|
+
if (byteLength + characterByteLength > 255) {
|
|
1602
|
+
chunks.push(chunk);
|
|
1603
|
+
chunk = "";
|
|
1604
|
+
byteLength = 0;
|
|
1605
|
+
}
|
|
1606
|
+
chunk += escapeTxtCharacter(character);
|
|
1607
|
+
byteLength += characterByteLength;
|
|
2542
1608
|
}
|
|
1609
|
+
chunks.push(chunk);
|
|
1610
|
+
return chunks.map((part) => `"${part}"`).join(" ");
|
|
2543
1611
|
};
|
|
2544
|
-
/**
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
const
|
|
2562
|
-
const
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
]);
|
|
2578
|
-
const
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
if (cleaning._tag === "Applied") {
|
|
2590
|
-
setRemoving(false);
|
|
2591
|
-
disconnect();
|
|
2592
|
-
return;
|
|
1612
|
+
/** The RR type as a zone file spells it. The tag and the type name are the same string. */
|
|
1613
|
+
const rrType = (record) => record._tag;
|
|
1614
|
+
const zoneData = (record) => {
|
|
1615
|
+
switch (record._tag) {
|
|
1616
|
+
case "A":
|
|
1617
|
+
case "AAAA": return record.address;
|
|
1618
|
+
case "CNAME": return absolute(record.target);
|
|
1619
|
+
case "NS": return absolute(record.nameserver);
|
|
1620
|
+
case "TXT": return txtValue(record.value);
|
|
1621
|
+
case "MX": return `${record.priority} ${absolute(record.exchange)}`;
|
|
1622
|
+
case "CAA": return `${record.flags} ${record.tag} "${record.value}"`;
|
|
1623
|
+
case "SRV": return `${record.priority} ${record.weight} ${record.port} ${absolute(record.target)}`;
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
const toZoneFile = (records) => `${records.map((record) => `${absolute(record.name)}${record.ttl === null ? "" : ` ${record.ttl}`} IN ${rrType(record)} ${zoneData(record)}`).join("\n")}\n`;
|
|
1627
|
+
const downloadText = (filename, contents) => {
|
|
1628
|
+
if (typeof document === "undefined") return;
|
|
1629
|
+
const blob = new Blob([contents], { type: "text/plain;charset=utf-8" });
|
|
1630
|
+
const url = URL.createObjectURL(blob);
|
|
1631
|
+
const anchor = document.createElement("a");
|
|
1632
|
+
anchor.download = filename;
|
|
1633
|
+
anchor.href = url;
|
|
1634
|
+
document.body.append(anchor);
|
|
1635
|
+
anchor.click();
|
|
1636
|
+
anchor.remove();
|
|
1637
|
+
URL.revokeObjectURL(url);
|
|
1638
|
+
};
|
|
1639
|
+
const downloadZoneFile = (domain, records) => downloadText(`${domain}.txt`, toZoneFile(records));
|
|
1640
|
+
const copyText = (value) => {
|
|
1641
|
+
if (typeof navigator === "undefined" || navigator.clipboard === void 0) return Promise.resolve(false);
|
|
1642
|
+
return navigator.clipboard.writeText(value).then(() => true, () => false);
|
|
1643
|
+
};
|
|
1644
|
+
function useCopy(value, resetAfter = 2e3) {
|
|
1645
|
+
const [copied, setCopied] = useState(false);
|
|
1646
|
+
const reset = useRef(void 0);
|
|
1647
|
+
useEffect(() => () => clearTimeout(reset.current), []);
|
|
1648
|
+
return {
|
|
1649
|
+
copied,
|
|
1650
|
+
copy: () => {
|
|
1651
|
+
copyText(value).then((ok) => {
|
|
1652
|
+
if (!ok) return;
|
|
1653
|
+
setCopied(true);
|
|
1654
|
+
clearTimeout(reset.current);
|
|
1655
|
+
reset.current = setTimeout(() => setCopied(false), resetAfter);
|
|
1656
|
+
});
|
|
2593
1657
|
}
|
|
2594
|
-
|
|
2595
|
-
}, [
|
|
2596
|
-
cleaning,
|
|
2597
|
-
disconnect,
|
|
2598
|
-
removing
|
|
2599
|
-
]);
|
|
2600
|
-
const busy = removing || releasing;
|
|
2601
|
-
const settling = removable && removals === null;
|
|
2602
|
-
const provider = snapshot?.provider ?? null;
|
|
2603
|
-
const named = provider === null ? "" : displayName(controller, provider);
|
|
2604
|
-
const heading = provider === null ? messages.disconnect : messages.disconnectTitle(named);
|
|
2605
|
-
const count = removals?.operations.length ?? 0;
|
|
2606
|
-
const choosable = removable && count > 0;
|
|
2607
|
-
if (readOnly) return /* @__PURE__ */ jsx(Fragment, {});
|
|
2608
|
-
return /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
2609
|
-
onOpenChange: (next, details) => {
|
|
2610
|
-
if (!next && busy) {
|
|
2611
|
-
details.cancel();
|
|
2612
|
-
return;
|
|
2613
|
-
}
|
|
2614
|
-
setOpen(next);
|
|
2615
|
-
},
|
|
2616
|
-
open,
|
|
2617
|
-
children: [/* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
2618
|
-
"data-domainkit-part": "disconnect-action",
|
|
2619
|
-
children: trigger ?? messages.disconnect
|
|
2620
|
-
}), /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
2621
|
-
container: portalContainer,
|
|
2622
|
-
children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
2623
|
-
"data-color-scheme": colorScheme,
|
|
2624
|
-
"data-domainkit-part": "dialog-backdrop",
|
|
2625
|
-
"data-domainkit-root": "",
|
|
2626
|
-
style: themeStyle
|
|
2627
|
-
}), /* @__PURE__ */ jsxs(Dialog.Popup, {
|
|
2628
|
-
"data-color-scheme": colorScheme,
|
|
2629
|
-
"data-cleanup": choosable ? "offered" : "none",
|
|
2630
|
-
"data-domainkit-part": "disconnect-dialog",
|
|
2631
|
-
"data-domainkit-root": "",
|
|
2632
|
-
style: themeStyle,
|
|
2633
|
-
children: [
|
|
2634
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2635
|
-
"data-domainkit-part": "dialog-header",
|
|
2636
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
2637
|
-
"data-domainkit-part": "dialog-heading",
|
|
2638
|
-
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
2639
|
-
"data-domainkit-part": "dialog-title",
|
|
2640
|
-
children: heading
|
|
2641
|
-
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
2642
|
-
"data-domainkit-part": "dialog-description",
|
|
2643
|
-
children: messages.disconnectConsent(snapshot?.domain ?? "", named)
|
|
2644
|
-
})]
|
|
2645
|
-
}), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2646
|
-
"aria-label": messages.close,
|
|
2647
|
-
"data-domainkit-part": "dialog-close",
|
|
2648
|
-
children: "×"
|
|
2649
|
-
})]
|
|
2650
|
-
}),
|
|
2651
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2652
|
-
"data-domainkit-part": "dialog-body",
|
|
2653
|
-
children: [
|
|
2654
|
-
shared ? /* @__PURE__ */ jsxs("fieldset", {
|
|
2655
|
-
"data-domainkit-part": "disconnect-scope",
|
|
2656
|
-
children: [
|
|
2657
|
-
/* @__PURE__ */ jsx("legend", { children: messages.disconnectScope }),
|
|
2658
|
-
/* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
|
|
2659
|
-
checked: !everyDomain,
|
|
2660
|
-
disabled: busy,
|
|
2661
|
-
name: "disconnect-scope",
|
|
2662
|
-
onChange: () => setEveryDomain(false),
|
|
2663
|
-
type: "radio"
|
|
2664
|
-
}), messages.disconnectThisDomain] }),
|
|
2665
|
-
/* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
|
|
2666
|
-
checked: everyDomain,
|
|
2667
|
-
disabled: busy,
|
|
2668
|
-
name: "disconnect-scope",
|
|
2669
|
-
onChange: () => setEveryDomain(true),
|
|
2670
|
-
type: "radio"
|
|
2671
|
-
}), messages.disconnectEveryDomain(snapshot?.connectionDomains ?? 0)] })
|
|
2672
|
-
]
|
|
2673
|
-
}) : null,
|
|
2674
|
-
choosable ? /* @__PURE__ */ jsxs("div", {
|
|
2675
|
-
"data-domainkit-part": "disconnect-cleanup",
|
|
2676
|
-
"data-state": alsoRemove ? "on" : "off",
|
|
2677
|
-
children: [/* @__PURE__ */ jsxs("label", {
|
|
2678
|
-
"data-domainkit-part": "disconnect-cleanup-label",
|
|
2679
|
-
children: [/* @__PURE__ */ jsx(Switch.Root, {
|
|
2680
|
-
checked: alsoRemove,
|
|
2681
|
-
"data-domainkit-part": "disconnect-cleanup-switch",
|
|
2682
|
-
disabled: busy,
|
|
2683
|
-
onCheckedChange: setAlsoRemove,
|
|
2684
|
-
children: /* @__PURE__ */ jsx(Switch.Thumb, { "data-domainkit-part": "disconnect-cleanup-thumb" })
|
|
2685
|
-
}), messages.disconnectWithCleanup(count)]
|
|
2686
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
2687
|
-
"data-domainkit-part": "disconnect-cleanup-panel",
|
|
2688
|
-
children: [removals === null ? null : /* @__PURE__ */ jsx(List, { plan: removals }), alsoRemove ? null : /* @__PURE__ */ jsx("p", {
|
|
2689
|
-
"data-domainkit-part": "disconnect-cleanup-note",
|
|
2690
|
-
children: messages.disconnectKeepsRecords(named)
|
|
2691
|
-
})]
|
|
2692
|
-
})]
|
|
2693
|
-
}) : null,
|
|
2694
|
-
/* @__PURE__ */ jsx(Status$3, { controller: cleanup }),
|
|
2695
|
-
releasing ? /* @__PURE__ */ jsx("p", {
|
|
2696
|
-
"data-domainkit-part": "disconnect-status",
|
|
2697
|
-
role: "status",
|
|
2698
|
-
children: messages.disconnecting
|
|
2699
|
-
}) : null,
|
|
2700
|
-
/* @__PURE__ */ jsx(Outcome$3, { controller: cleanup }),
|
|
2701
|
-
/* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
2702
|
-
]
|
|
2703
|
-
}),
|
|
2704
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2705
|
-
"data-domainkit-part": "dialog-footer",
|
|
2706
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
2707
|
-
"data-domainkit-part": "disconnect-confirm",
|
|
2708
|
-
disabled: busy || settling,
|
|
2709
|
-
onClick: () => {
|
|
2710
|
-
if (choosable && alsoRemove) {
|
|
2711
|
-
setRemoving(true);
|
|
2712
|
-
approve();
|
|
2713
|
-
return;
|
|
2714
|
-
}
|
|
2715
|
-
disconnect();
|
|
2716
|
-
},
|
|
2717
|
-
type: "button",
|
|
2718
|
-
children: messages.disconnect
|
|
2719
|
-
}), busy ? null : /* @__PURE__ */ jsx(Dialog.Close, {
|
|
2720
|
-
"data-domainkit-part": "dialog-cancel",
|
|
2721
|
-
children: messages.cancel
|
|
2722
|
-
})]
|
|
2723
|
-
})
|
|
2724
|
-
]
|
|
2725
|
-
})]
|
|
2726
|
-
})]
|
|
2727
|
-
});
|
|
1658
|
+
};
|
|
2728
1659
|
}
|
|
1660
|
+
/** The row's standing, or `null` when neither a plan nor an observation covers the record. */
|
|
1661
|
+
const statusOf = (record, sources) => {
|
|
1662
|
+
const operation = sources.plan?.operations.find((entry) => DnsRecord.equals(entry.record, record));
|
|
1663
|
+
if (operation !== void 0) return {
|
|
1664
|
+
_tag: "Operation",
|
|
1665
|
+
operation
|
|
1666
|
+
};
|
|
1667
|
+
const status = sources.readiness?.requirements.find((requirement) => DnsRecord.equals(requirement.record, record))?.status;
|
|
1668
|
+
return status === void 0 ? null : {
|
|
1669
|
+
_tag: "Readiness",
|
|
1670
|
+
status
|
|
1671
|
+
};
|
|
1672
|
+
};
|
|
1673
|
+
/** A stable React key for a requirement: type, name, and data identify a record. */
|
|
1674
|
+
const identity = (record) => [
|
|
1675
|
+
rrType(record),
|
|
1676
|
+
record.name,
|
|
1677
|
+
DnsRecord.data(record)
|
|
1678
|
+
].join(":");
|
|
2729
1679
|
/**
|
|
2730
|
-
* A
|
|
2731
|
-
*
|
|
1680
|
+
* A requirement set keyed by everything it carries, for memos that decide whether to re-send it.
|
|
1681
|
+
* `identity` is deliberately narrower: it answers "is this the same record" for a React key, while
|
|
1682
|
+
* a plan and an observation are built from `ttl`, `policy`, and `purpose` as well. `policy` in
|
|
1683
|
+
* particular decides whether a name may hold anything else, so readiness turns on it.
|
|
2732
1684
|
*/
|
|
2733
|
-
|
|
2734
|
-
const { messages } = useDomainKit();
|
|
2735
|
-
const readOnly = useReadOnly();
|
|
2736
|
-
const state = controller.state;
|
|
2737
|
-
const snapshot = controller.snapshot;
|
|
2738
|
-
const provider = controller.providers.find((entry) => entry.id === snapshot?.provider);
|
|
2739
|
-
const standing = state._tag === "Reconnect" ? messages.needsReconnect : messages.connected;
|
|
2740
|
-
return usePart("div", props, { status: state._tag }, {
|
|
2741
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2742
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2743
|
-
"data-domainkit-part": "connected-identity",
|
|
2744
|
-
children: [
|
|
2745
|
-
provider === void 0 ? null : /* @__PURE__ */ jsx(Mark, { provider }),
|
|
2746
|
-
/* @__PURE__ */ jsx("span", {
|
|
2747
|
-
"data-domainkit-part": "host-name",
|
|
2748
|
-
children: provider?.name ?? snapshot?.provider ?? ""
|
|
2749
|
-
}),
|
|
2750
|
-
/* @__PURE__ */ jsx("span", {
|
|
2751
|
-
"data-domainkit-part": "host-statement",
|
|
2752
|
-
children: standing
|
|
2753
|
-
})
|
|
2754
|
-
]
|
|
2755
|
-
}),
|
|
2756
|
-
readOnly ? null : /* @__PURE__ */ jsx("div", {
|
|
2757
|
-
"data-domainkit-part": "connected-actions",
|
|
2758
|
-
children: /* @__PURE__ */ jsx(DisconnectDialog, {
|
|
2759
|
-
controller,
|
|
2760
|
-
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2761
|
-
})
|
|
2762
|
-
}),
|
|
2763
|
-
/* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
2764
|
-
] }),
|
|
2765
|
-
"data-domainkit-part": "connected-card"
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
/** Connection on its own: the card once connected, the prompt until then. */
|
|
2769
|
-
function Flow$2({ connect, domain, onCleaned, returnTo, ...props }) {
|
|
2770
|
-
const controller = useController$2({
|
|
2771
|
-
domain,
|
|
2772
|
-
...returnTo === void 0 ? {} : { returnTo }
|
|
2773
|
-
});
|
|
2774
|
-
return /* @__PURE__ */ jsx(Root$2, {
|
|
2775
|
-
controller,
|
|
2776
|
-
...props,
|
|
2777
|
-
children: holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
|
|
2778
|
-
controller,
|
|
2779
|
-
...onCleaned === void 0 ? {} : { onCleaned }
|
|
2780
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Prompt, {
|
|
2781
|
-
controller,
|
|
2782
|
-
...connect === void 0 ? {} : { connect }
|
|
2783
|
-
}), answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })] })
|
|
2784
|
-
});
|
|
2785
|
-
}
|
|
1685
|
+
const requirementsKey = (records) => JSON.stringify(records);
|
|
2786
1686
|
//#endregion
|
|
2787
|
-
//#region src/provision.
|
|
1687
|
+
//#region src/provision.ts
|
|
2788
1688
|
var provision_exports = /* @__PURE__ */ __exportAll({
|
|
2789
|
-
Actions: () => Actions,
|
|
2790
|
-
Dialog: () => Dialog$1,
|
|
2791
|
-
Flow: () => Flow$1,
|
|
2792
|
-
Outcome: () => Outcome$1,
|
|
2793
|
-
Root: () => Root$1,
|
|
2794
1689
|
State: () => State$2,
|
|
2795
|
-
|
|
1690
|
+
pendingPlan: () => pendingPlan,
|
|
1691
|
+
planOf: () => planOf,
|
|
2796
1692
|
useController: () => useController$1
|
|
2797
1693
|
});
|
|
2798
|
-
/** Plan, approve, apply. `approve` authorizes the digest and applies it in the same action. */
|
|
2799
1694
|
/**
|
|
1695
|
+
* Plan, approve, apply; `approve` authorizes the digest and applies it in the same action.
|
|
2800
1696
|
* Requirements identify themselves by content, so an inline array does not abandon the attempt.
|
|
2801
1697
|
* Every field counts: a plan turns on `policy`, and `ttl` and `purpose` ride the wire with it.
|
|
2802
1698
|
*/
|
|
2803
1699
|
const keyOf = (domain, requirements) => [domain, requirementsKey(requirements)].join("|");
|
|
2804
|
-
function useController$1({ domain, onApplied, requirements }) {
|
|
1700
|
+
function useController$1({ domain, onApplied, readOnly, requirements }) {
|
|
2805
1701
|
const { transport } = useDomainKit();
|
|
2806
1702
|
const group = transport.provisioning;
|
|
2807
1703
|
return useAttempt({
|
|
@@ -2813,6 +1709,7 @@ function useController$1({ domain, onApplied, requirements }) {
|
|
|
2813
1709
|
}),
|
|
2814
1710
|
group,
|
|
2815
1711
|
onDone: onApplied,
|
|
1712
|
+
readOnly,
|
|
2816
1713
|
plan: useCallback(() => group === void 0 ? null : group.plan({
|
|
2817
1714
|
domain,
|
|
2818
1715
|
requirements
|
|
@@ -2823,66 +1720,12 @@ function useController$1({ domain, onApplied, requirements }) {
|
|
|
2823
1720
|
])
|
|
2824
1721
|
});
|
|
2825
1722
|
}
|
|
2826
|
-
function Root$1({ controller, ...props }) {
|
|
2827
|
-
return usePart("div", props, { status: controller.state._tag }, {
|
|
2828
|
-
"data-domainkit-part": "provisioning-flow",
|
|
2829
|
-
"data-state": controller.state._tag
|
|
2830
|
-
});
|
|
2831
|
-
}
|
|
2832
|
-
function Dialog$1(props) {
|
|
2833
|
-
return /* @__PURE__ */ jsx(Dialog$4, {
|
|
2834
|
-
...props,
|
|
2835
|
-
kind: "provisioning"
|
|
2836
|
-
});
|
|
2837
|
-
}
|
|
2838
|
-
/** Approve and Decline for a plan already under review. */
|
|
2839
|
-
function Actions(props) {
|
|
2840
|
-
return /* @__PURE__ */ jsx(Actions$2, {
|
|
2841
|
-
...props,
|
|
2842
|
-
kind: "provisioning"
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
function Outcome$1(props) {
|
|
2846
|
-
return /* @__PURE__ */ jsx(Outcome$4, {
|
|
2847
|
-
...props,
|
|
2848
|
-
kind: "provisioning"
|
|
2849
|
-
});
|
|
2850
|
-
}
|
|
2851
|
-
function Status$1(props) {
|
|
2852
|
-
return /* @__PURE__ */ jsx(Status$4, {
|
|
2853
|
-
...props,
|
|
2854
|
-
kind: "provisioning"
|
|
2855
|
-
});
|
|
2856
|
-
}
|
|
2857
|
-
/** Provisioning on its own: a trigger that plans, then the review dialog. */
|
|
2858
|
-
function Flow$1({ domain, onApplied, requirements, trigger, ...props }) {
|
|
2859
|
-
const controller = useController$1({
|
|
2860
|
-
domain,
|
|
2861
|
-
requirements,
|
|
2862
|
-
...onApplied === void 0 ? {} : { onApplied }
|
|
2863
|
-
});
|
|
2864
|
-
const [open, setOpen] = useState(false);
|
|
2865
|
-
return /* @__PURE__ */ jsxs(Root$1, {
|
|
2866
|
-
controller,
|
|
2867
|
-
...props,
|
|
2868
|
-
children: [/* @__PURE__ */ jsx(Dialog$1, {
|
|
2869
|
-
controller,
|
|
2870
|
-
onOpenChange: setOpen,
|
|
2871
|
-
open,
|
|
2872
|
-
...trigger === void 0 ? {} : { trigger }
|
|
2873
|
-
}), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller })]
|
|
2874
|
-
});
|
|
2875
|
-
}
|
|
2876
1723
|
//#endregion
|
|
2877
|
-
//#region src/verify.
|
|
1724
|
+
//#region src/verify.ts
|
|
2878
1725
|
var verify_exports = /* @__PURE__ */ __exportAll({
|
|
2879
|
-
Evidence: () => Evidence,
|
|
2880
|
-
ObserveAction: () => ObserveAction,
|
|
2881
|
-
Outcome: () => Outcome,
|
|
2882
|
-
Root: () => Root,
|
|
2883
1726
|
State: () => State,
|
|
2884
|
-
|
|
2885
|
-
|
|
1727
|
+
useController: () => useController,
|
|
1728
|
+
valuesOf: () => valuesOf
|
|
2886
1729
|
});
|
|
2887
1730
|
/**
|
|
2888
1731
|
* What the observer read back for the requirement's name, or `null` when it read nothing back at
|
|
@@ -2901,9 +1744,11 @@ function useController({ domain, polling = true, requirements }) {
|
|
|
2901
1744
|
const timer = useRef(void 0);
|
|
2902
1745
|
const signature = requirements === void 0 ? null : requirementsKey(requirements);
|
|
2903
1746
|
const requested = useMemo(() => requirements === void 0 ? void 0 : { requirements }, [signature]);
|
|
1747
|
+
const held = useRef(domain);
|
|
2904
1748
|
const [observed, setObserved] = useState(domain);
|
|
2905
1749
|
if (observed !== domain) {
|
|
2906
1750
|
setObserved(domain);
|
|
1751
|
+
held.current = domain;
|
|
2907
1752
|
setState(State.Idle());
|
|
2908
1753
|
}
|
|
2909
1754
|
const observe = useCallback(() => {
|
|
@@ -2912,6 +1757,7 @@ function useController({ domain, polling = true, requirements }) {
|
|
|
2912
1757
|
setState((previous) => State.Observing({ readiness: readinessOf(previous) }));
|
|
2913
1758
|
runner.run(requested === void 0 ? verification.observe(domain) : verification.observe(domain, requested), {
|
|
2914
1759
|
onFailure: (error) => {
|
|
1760
|
+
if (held.current !== domain) return;
|
|
2915
1761
|
setState((previous) => State.Failure({
|
|
2916
1762
|
error,
|
|
2917
1763
|
readiness: readinessOf(previous)
|
|
@@ -2921,7 +1767,10 @@ function useController({ domain, polling = true, requirements }) {
|
|
|
2921
1767
|
error
|
|
2922
1768
|
}));
|
|
2923
1769
|
},
|
|
2924
|
-
onSuccess: (readiness) =>
|
|
1770
|
+
onSuccess: (readiness) => {
|
|
1771
|
+
if (held.current !== domain) return;
|
|
1772
|
+
setState(State.Observed({ readiness }));
|
|
1773
|
+
}
|
|
2925
1774
|
});
|
|
2926
1775
|
}, [
|
|
2927
1776
|
domain,
|
|
@@ -2954,350 +1803,114 @@ function useController({ domain, polling = true, requirements }) {
|
|
|
2954
1803
|
state
|
|
2955
1804
|
};
|
|
2956
1805
|
}
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
return usePart("div", props, { status: controller.state._tag }, {
|
|
2960
|
-
"data-domainkit-part": "verification-status",
|
|
2961
|
-
"data-overall": overall ?? void 0,
|
|
2962
|
-
"data-state": controller.state._tag
|
|
2963
|
-
});
|
|
2964
|
-
}
|
|
2965
|
-
function ObserveAction({ controller, ...props }) {
|
|
2966
|
-
const { messages } = useDomainKit();
|
|
2967
|
-
const observing = controller.state._tag === "Observing";
|
|
2968
|
-
return usePart("button", props, { status: controller.state._tag }, {
|
|
2969
|
-
"aria-busy": observing,
|
|
2970
|
-
children: controller.readiness === null ? messages.checkDns : messages.checkAgain,
|
|
2971
|
-
"data-domainkit-part": "observe-action",
|
|
2972
|
-
"data-loading": observing ? "" : void 0,
|
|
2973
|
-
disabled: observing,
|
|
2974
|
-
onClick: controller.observe,
|
|
2975
|
-
type: "button"
|
|
2976
|
-
});
|
|
2977
|
-
}
|
|
2978
|
-
/** Evidence carries no id; its source and position identify it within one requirement. */
|
|
2979
|
-
const evidenceKey = (evidence, index) => [evidence._tag, index].join("-");
|
|
2980
|
-
/**
|
|
2981
|
-
* Per-requirement readiness and, below it, the host's own evidence: an SES identity, a certificate,
|
|
2982
|
-
* anything DomainKit cannot observe but the host feeds in.
|
|
2983
|
-
*/
|
|
2984
|
-
function Evidence({ readiness }) {
|
|
2985
|
-
const { messages } = useDomainKit();
|
|
2986
|
-
const icons = useIcons();
|
|
2987
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2988
|
-
"data-domainkit-part": "observation-list",
|
|
2989
|
-
children: [readiness.requirements.map((requirement) => {
|
|
2990
|
-
const settled = requirement.status === "satisfied";
|
|
2991
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
2992
|
-
"data-domainkit-part": "observation-group",
|
|
2993
|
-
children: [
|
|
2994
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2995
|
-
"data-domainkit-part": "observation-row",
|
|
2996
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
2997
|
-
"data-domainkit-part": "observation-record",
|
|
2998
|
-
children: [
|
|
2999
|
-
messages.recordType(requirement.record),
|
|
3000
|
-
" ",
|
|
3001
|
-
requirement.record.name
|
|
3002
|
-
]
|
|
3003
|
-
}), /* @__PURE__ */ jsx(Status$5, { status: requirement.status })]
|
|
3004
|
-
}),
|
|
3005
|
-
settled ? null : /* @__PURE__ */ jsx("p", {
|
|
3006
|
-
"data-domainkit-part": "observation-expected",
|
|
3007
|
-
children: messages.expectedValue(DnsRecord.data(requirement.record))
|
|
3008
|
-
}),
|
|
3009
|
-
/* @__PURE__ */ jsx("ul", { children: requirement.evidence.map((evidence, index) => {
|
|
3010
|
-
const values = settled ? null : valuesOf(evidence);
|
|
3011
|
-
return /* @__PURE__ */ jsxs("li", {
|
|
3012
|
-
"data-domainkit-part": "observation-source",
|
|
3013
|
-
children: [
|
|
3014
|
-
messages.evidenceSource(evidence),
|
|
3015
|
-
values === null ? null : /* @__PURE__ */ jsx("span", {
|
|
3016
|
-
"data-domainkit-part": "observation-observed",
|
|
3017
|
-
children: values.length === 0 ? messages.observedNothing : messages.observedValues(values)
|
|
3018
|
-
}),
|
|
3019
|
-
evidence.detail === null ? null : /* @__PURE__ */ jsx("p", {
|
|
3020
|
-
"data-domainkit-part": "observation-note",
|
|
3021
|
-
children: evidence.detail
|
|
3022
|
-
})
|
|
3023
|
-
]
|
|
3024
|
-
}, evidenceKey(evidence, index));
|
|
3025
|
-
}) })
|
|
3026
|
-
]
|
|
3027
|
-
}, identity(requirement.record));
|
|
3028
|
-
}), readiness.host.length === 0 ? null : /* @__PURE__ */ jsxs("section", {
|
|
3029
|
-
"data-domainkit-part": "observation-group",
|
|
3030
|
-
"data-source": "host",
|
|
3031
|
-
children: [/* @__PURE__ */ jsx("h3", {
|
|
3032
|
-
"data-domainkit-part": "observation-source",
|
|
3033
|
-
children: messages.hostEvidence
|
|
3034
|
-
}), /* @__PURE__ */ jsx("ul", { children: readiness.host.map((evidence, index) => /* @__PURE__ */ jsxs("li", {
|
|
3035
|
-
"data-domainkit-part": "host-evidence",
|
|
3036
|
-
children: [
|
|
3037
|
-
/* @__PURE__ */ jsx("span", {
|
|
3038
|
-
"aria-hidden": "true",
|
|
3039
|
-
"data-icon": "inline-start",
|
|
3040
|
-
children: evidence.status === "ok" ? icons.success : evidence.status === "pending" ? icons.pending : icons.failure
|
|
3041
|
-
}),
|
|
3042
|
-
/* @__PURE__ */ jsx("span", {
|
|
3043
|
-
"data-domainkit-part": "observation-record",
|
|
3044
|
-
children: evidence.label
|
|
3045
|
-
}),
|
|
3046
|
-
/* @__PURE__ */ jsx("span", {
|
|
3047
|
-
"data-domainkit-part": "record-status",
|
|
3048
|
-
"data-status": evidence.status,
|
|
3049
|
-
children: messages.hostEvidenceStatus(evidence.status)
|
|
3050
|
-
}),
|
|
3051
|
-
evidence.detail === null ? null : /* @__PURE__ */ jsx("p", {
|
|
3052
|
-
"data-domainkit-part": "observation-note",
|
|
3053
|
-
children: evidence.detail
|
|
3054
|
-
})
|
|
3055
|
-
]
|
|
3056
|
-
}, `${evidence.source}-${index}`)) })]
|
|
3057
|
-
})]
|
|
3058
|
-
});
|
|
3059
|
-
}
|
|
1806
|
+
//#endregion
|
|
1807
|
+
//#region src/domain.ts
|
|
3060
1808
|
/**
|
|
3061
|
-
* The
|
|
3062
|
-
* the
|
|
1809
|
+
* The whole lifecycle for one domain in one hook: connect, add the records, observe, clean up.
|
|
1810
|
+
* `useFlow` runs the four controllers together and plans as soon as the domain is attached with
|
|
1811
|
+
* nothing applied, so the styled surface is only markup.
|
|
3063
1812
|
*/
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
const state = controller.state;
|
|
3067
|
-
if (state._tag !== "Failure") return null;
|
|
3068
|
-
const words = outcome(state.error, messages);
|
|
3069
|
-
return /* @__PURE__ */ jsx(Provider, {
|
|
3070
|
-
value: {
|
|
3071
|
-
description: words.description ?? "",
|
|
3072
|
-
layout: props.layout ?? "card",
|
|
3073
|
-
retry: controller.retry,
|
|
3074
|
-
retryPart: "verification-retry",
|
|
3075
|
-
title: words.title,
|
|
3076
|
-
tone: "danger"
|
|
3077
|
-
},
|
|
3078
|
-
children: /* @__PURE__ */ jsx(Root$6, {
|
|
3079
|
-
...props,
|
|
3080
|
-
children: children ?? /* @__PURE__ */ jsx(Composition, {})
|
|
3081
|
-
})
|
|
3082
|
-
});
|
|
3083
|
-
}
|
|
3084
|
-
/** The default verification slot: a button that observes, with the evidence behind it. */
|
|
3085
|
-
function Status({ controller, render, ...props }) {
|
|
3086
|
-
const { colorScheme, messages, portalContainer, themeStyle } = useDomainKit();
|
|
3087
|
-
const readiness = controller.readiness;
|
|
3088
|
-
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3089
|
-
controller.state._tag === "Observing" ? /* @__PURE__ */ jsx("p", {
|
|
3090
|
-
"data-domainkit-part": "verification-progress",
|
|
3091
|
-
role: "status",
|
|
3092
|
-
children: messages.observing
|
|
3093
|
-
}) : null,
|
|
3094
|
-
readiness === null ? /* @__PURE__ */ jsx("p", {
|
|
3095
|
-
"data-domainkit-part": "verification-progress",
|
|
3096
|
-
children: messages.noEvidence
|
|
3097
|
-
}) : /* @__PURE__ */ jsx(Evidence, { readiness }),
|
|
3098
|
-
/* @__PURE__ */ jsx(Outcome, { controller })
|
|
3099
|
-
] });
|
|
3100
|
-
return /* @__PURE__ */ jsx(Root, {
|
|
3101
|
-
controller,
|
|
3102
|
-
...props,
|
|
3103
|
-
children: render === void 0 ? /* @__PURE__ */ jsxs(Popover.Root, { children: [/* @__PURE__ */ jsx(Popover.Trigger, { render: /* @__PURE__ */ jsx(ObserveAction, { controller }) }), /* @__PURE__ */ jsx(Popover.Portal, {
|
|
3104
|
-
container: portalContainer,
|
|
3105
|
-
children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
3106
|
-
align: "end",
|
|
3107
|
-
"data-domainkit-part": "verification-positioner",
|
|
3108
|
-
sideOffset: 6,
|
|
3109
|
-
children: /* @__PURE__ */ jsxs(Popover.Popup, {
|
|
3110
|
-
"aria-busy": controller.state._tag === "Observing",
|
|
3111
|
-
"aria-label": messages.checkDns,
|
|
3112
|
-
"data-color-scheme": colorScheme,
|
|
3113
|
-
"data-domainkit-part": "verification-popover",
|
|
3114
|
-
"data-domainkit-root": "",
|
|
3115
|
-
style: themeStyle,
|
|
3116
|
-
children: [/* @__PURE__ */ jsx(Popover.Arrow, { "data-domainkit-part": "verification-arrow" }), body]
|
|
3117
|
-
})
|
|
3118
|
-
})
|
|
3119
|
-
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ObserveAction, { controller }), render({
|
|
3120
|
-
children: body,
|
|
3121
|
-
open: controller.state._tag !== "Idle"
|
|
3122
|
-
})] })
|
|
3123
|
-
});
|
|
3124
|
-
}
|
|
3125
|
-
//#endregion
|
|
3126
|
-
//#region src/domain.tsx
|
|
3127
|
-
var domain_exports = /* @__PURE__ */ __exportAll({ Flow: () => Flow });
|
|
3128
|
-
/** Built from primitives alone, so the effect that announces it depends on exactly those. */
|
|
1813
|
+
var domain_exports = /* @__PURE__ */ __exportAll({ useFlow: () => useFlow });
|
|
1814
|
+
/** Built from primitives alone, so the memo that reports it depends on exactly those. */
|
|
3129
1815
|
const flowState = (input) => ({
|
|
3130
1816
|
applied: input.receipt !== null,
|
|
3131
1817
|
connected: input.connected,
|
|
3132
1818
|
connection: input.status,
|
|
1819
|
+
label: input.label,
|
|
3133
1820
|
offering: input.offering,
|
|
3134
1821
|
provider: input.provider,
|
|
3135
1822
|
provisioning: input.planning,
|
|
1823
|
+
readOnly: input.readOnly,
|
|
3136
1824
|
receiptId: input.receipt === null ? null : Receipt.ReceiptId.make(input.receipt)
|
|
3137
1825
|
});
|
|
3138
|
-
function
|
|
3139
|
-
const disconnected = controller.state._tag === "Disconnected";
|
|
3140
|
-
const host = hostProvider(controller);
|
|
3141
|
-
if (disconnected && host === null && !offering(controller, connect)) return null;
|
|
3142
|
-
const stated = disconnected && host !== null && offering(controller, connect);
|
|
3143
|
-
return holdsConnection(controller) ? /* @__PURE__ */ jsx(Card, {
|
|
3144
|
-
controller,
|
|
3145
|
-
...onCleaned === void 0 ? {} : { onCleaned }
|
|
3146
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3147
|
-
stated ? null : /* @__PURE__ */ jsx(Status$2, { controller }),
|
|
3148
|
-
/* @__PURE__ */ jsx(Prompt, {
|
|
3149
|
-
connect,
|
|
3150
|
-
controller
|
|
3151
|
-
}),
|
|
3152
|
-
answeredInPlace(controller) ? null : /* @__PURE__ */ jsx(Outcome$2, { controller })
|
|
3153
|
-
] });
|
|
3154
|
-
}
|
|
3155
|
-
function DefaultActions({ connection, onOpenChange, open, provisioning }) {
|
|
3156
|
-
const readOnly = useReadOnly();
|
|
3157
|
-
const { capabilities } = useDomainKit();
|
|
3158
|
-
const connected = connection.state._tag === "Connected";
|
|
3159
|
-
if (readOnly) return null;
|
|
3160
|
-
if (!capabilities.includes("provisioning") || !connected) return null;
|
|
3161
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Dialog$1, {
|
|
3162
|
-
controller: provisioning,
|
|
3163
|
-
onOpenChange,
|
|
3164
|
-
open
|
|
3165
|
-
}), open ? null : /* @__PURE__ */ jsx(Outcome$1, { controller: provisioning })] });
|
|
3166
|
-
}
|
|
3167
|
-
/**
|
|
3168
|
-
* The whole lifecycle for one domain: connect, review, apply, observe, clean up. The flow renders
|
|
3169
|
-
* only what the transport declares, and every part of it is a slot with a default. It adds no
|
|
3170
|
-
* layout containers of its own, so a host's grid can place the slot output directly.
|
|
3171
|
-
*/
|
|
3172
|
-
function Flow({ connect = "detected", domain, onApplied, onCleaned, onState, readOnly, requirements, returnTo, review = "auto", slots = {}, ...props }) {
|
|
1826
|
+
function useFlow({ connect = "detected", domain, onApplied, onCleaned, readOnly, requirements, returnTo }) {
|
|
3173
1827
|
const { capabilities } = useDomainKit();
|
|
3174
1828
|
const inherited = useReadOnly();
|
|
1829
|
+
const surfaceReadOnly = readOnly ?? inherited;
|
|
3175
1830
|
const connection = useController$2({
|
|
3176
1831
|
domain,
|
|
1832
|
+
readOnly: surfaceReadOnly,
|
|
3177
1833
|
...returnTo === void 0 ? {} : { returnTo }
|
|
3178
1834
|
});
|
|
3179
1835
|
const refresh = connection.refresh;
|
|
3180
1836
|
const provisioning = useController$1({
|
|
3181
1837
|
domain,
|
|
1838
|
+
readOnly: surfaceReadOnly,
|
|
3182
1839
|
onApplied: useCallback((receipt) => {
|
|
3183
1840
|
refresh();
|
|
3184
1841
|
onApplied?.(receipt);
|
|
3185
1842
|
}, [onApplied, refresh]),
|
|
3186
1843
|
requirements
|
|
3187
1844
|
});
|
|
3188
|
-
const cleanup = useController$3({
|
|
3189
|
-
domain,
|
|
3190
|
-
onCleaned: useCallback((receipt) => {
|
|
3191
|
-
refresh();
|
|
3192
|
-
onCleaned?.(receipt);
|
|
3193
|
-
}, [onCleaned, refresh]),
|
|
3194
|
-
...connection.snapshot?.lastReceiptId == null ? {} : { receiptId: Receipt.ReceiptId.make(connection.snapshot.lastReceiptId) }
|
|
3195
|
-
});
|
|
3196
1845
|
const verification = useController({
|
|
3197
1846
|
domain,
|
|
3198
1847
|
requirements
|
|
3199
1848
|
});
|
|
3200
|
-
const
|
|
3201
|
-
const
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
1849
|
+
const receiptId = connection.snapshot?.lastReceiptId ?? null;
|
|
1850
|
+
const cleanup = useController$3({
|
|
1851
|
+
domain,
|
|
1852
|
+
readOnly: surfaceReadOnly,
|
|
1853
|
+
...onCleaned === void 0 ? {} : { onCleaned },
|
|
1854
|
+
...receiptId === null ? {} : { receiptId: Receipt.ReceiptId.make(receiptId) }
|
|
1855
|
+
});
|
|
1856
|
+
const attached = connection.snapshot?.attachment != null;
|
|
1857
|
+
const signature = surfaceReadOnly || !capabilities.includes("provisioning") || !attached || receiptId !== null ? null : [
|
|
1858
|
+
domain,
|
|
1859
|
+
connection.established,
|
|
1860
|
+
requirementsKey(requirements)
|
|
1861
|
+
].join("|");
|
|
1862
|
+
const planned = useRef(null);
|
|
3211
1863
|
const buildPlan = provisioning.plan;
|
|
3212
1864
|
useEffect(() => {
|
|
3213
|
-
if (
|
|
3214
|
-
|
|
3215
|
-
|
|
1865
|
+
if (signature === null) {
|
|
1866
|
+
planned.current = null;
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (planned.current === signature) return;
|
|
1870
|
+
planned.current = signature;
|
|
3216
1871
|
buildPlan();
|
|
3217
|
-
|
|
3218
|
-
}, [
|
|
3219
|
-
buildPlan,
|
|
3220
|
-
established,
|
|
3221
|
-
review
|
|
3222
|
-
]);
|
|
1872
|
+
}, [buildPlan, signature]);
|
|
3223
1873
|
const connected = holdsConnection(connection);
|
|
3224
1874
|
const offering$1 = offering(connection, connect);
|
|
3225
1875
|
const provider = connection.snapshot?.provider ?? hostProvider(connection)?.id ?? null;
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
announce.current = onState;
|
|
3238
|
-
});
|
|
3239
|
-
useEffect(() => {
|
|
3240
|
-
announce.current?.(flowState({
|
|
1876
|
+
return {
|
|
1877
|
+
capabilities,
|
|
1878
|
+
cleanup,
|
|
1879
|
+
connection,
|
|
1880
|
+
domain,
|
|
1881
|
+
invitation: connect,
|
|
1882
|
+
plan: pendingPlan(provisioning.state),
|
|
1883
|
+
provisioning,
|
|
1884
|
+
readiness: verification.readiness,
|
|
1885
|
+
requirements,
|
|
1886
|
+
state: flowState({
|
|
3241
1887
|
connected,
|
|
1888
|
+
label: connection.snapshot?.attachment?.label ?? null,
|
|
3242
1889
|
offering: offering$1,
|
|
3243
|
-
planning,
|
|
1890
|
+
planning: provisioning.state._tag,
|
|
3244
1891
|
provider,
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
}, [
|
|
3249
|
-
connected,
|
|
3250
|
-
offering$1,
|
|
3251
|
-
planning,
|
|
3252
|
-
provider,
|
|
3253
|
-
receipt,
|
|
3254
|
-
status
|
|
3255
|
-
]);
|
|
3256
|
-
return usePart("div", props, state, {
|
|
3257
|
-
children: /* @__PURE__ */ jsxs(ReadOnly, {
|
|
3258
|
-
value: readOnly ?? inherited,
|
|
3259
|
-
children: [
|
|
3260
|
-
!capabilities.includes("connection") ? null : slots.connection === void 0 ? /* @__PURE__ */ jsx(DefaultConnection, {
|
|
3261
|
-
connect,
|
|
3262
|
-
controller: connection,
|
|
3263
|
-
domain,
|
|
3264
|
-
...onCleaned === void 0 ? {} : { onCleaned }
|
|
3265
|
-
}) : slots.connection({
|
|
3266
|
-
connect,
|
|
3267
|
-
controller: connection,
|
|
3268
|
-
domain
|
|
3269
|
-
}),
|
|
3270
|
-
slots.records === void 0 ? /* @__PURE__ */ jsx(Table, {
|
|
3271
|
-
readiness,
|
|
3272
|
-
records: requirements
|
|
3273
|
-
}) : slots.records({
|
|
3274
|
-
controller: verification,
|
|
3275
|
-
domain,
|
|
3276
|
-
readiness,
|
|
3277
|
-
records: requirements
|
|
3278
|
-
}),
|
|
3279
|
-
!capabilities.includes("verification") ? null : slots.verification === void 0 ? /* @__PURE__ */ jsx(Status, { controller: verification }) : slots.verification({
|
|
3280
|
-
controller: verification,
|
|
3281
|
-
domain
|
|
3282
|
-
}),
|
|
3283
|
-
slots.actions === void 0 ? /* @__PURE__ */ jsx(DefaultActions, {
|
|
3284
|
-
cleanup,
|
|
3285
|
-
connection,
|
|
3286
|
-
domain,
|
|
3287
|
-
onOpenChange: setReviewing,
|
|
3288
|
-
open: reviewing,
|
|
3289
|
-
provisioning
|
|
3290
|
-
}) : slots.actions({
|
|
3291
|
-
cleanup,
|
|
3292
|
-
connection,
|
|
3293
|
-
domain,
|
|
3294
|
-
provisioning
|
|
3295
|
-
})
|
|
3296
|
-
]
|
|
1892
|
+
readOnly: surfaceReadOnly,
|
|
1893
|
+
receipt: receiptId,
|
|
1894
|
+
status: connection.state._tag
|
|
3297
1895
|
}),
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
1896
|
+
verification
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
//#endregion
|
|
1900
|
+
//#region src/outcome.ts
|
|
1901
|
+
var outcome_exports = /* @__PURE__ */ __exportAll({
|
|
1902
|
+
describe: () => describe,
|
|
1903
|
+
useDescribe: () => useDescribe
|
|
1904
|
+
});
|
|
1905
|
+
/** The catalog's title and description for one failure. */
|
|
1906
|
+
const describe = (error, catalog, context = {}) => outcome(error, catalog, context);
|
|
1907
|
+
/**
|
|
1908
|
+
* `describe` bound to the catalog `DomainKit.Root` holds. The reason alone cannot name the
|
|
1909
|
+
* provider a customer typed a token for, so the surface supplies it in the context.
|
|
1910
|
+
*/
|
|
1911
|
+
function useDescribe() {
|
|
1912
|
+
const messages = useMessages();
|
|
1913
|
+
return (error, context = {}) => outcome(error, messages, context);
|
|
3301
1914
|
}
|
|
3302
1915
|
//#endregion
|
|
3303
1916
|
//#region src/testing.ts
|
|
@@ -3311,6 +1924,6 @@ const transport = Testing.transport;
|
|
|
3311
1924
|
//#region src/failure.ts
|
|
3312
1925
|
const Failure = Data.taggedEnum();
|
|
3313
1926
|
//#endregion
|
|
3314
|
-
export { cleanup_exports as Cleanup, connect_exports as Connect, domain_exports as Domain, domain_kit_exports as DomainKit, Event, Failure, messages_exports as Messages,
|
|
1927
|
+
export { cleanup_exports as Cleanup, connect_exports as Connect, domain_exports as Domain, domain_kit_exports as DomainKit, Event, Failure, messages_exports as Messages, outcome_exports as Outcome, provision_exports as Provision, records_exports as Records, testing_exports as Testing, verify_exports as Verify };
|
|
3315
1928
|
|
|
3316
1929
|
//# sourceMappingURL=index.mjs.map
|