nucleus-core-ts 0.9.782 → 0.9.783

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.
Files changed (114) hide show
  1. package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.d.ts +2 -0
  2. package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +85 -0
  3. package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.d.ts +2 -0
  4. package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +165 -0
  5. package/dist/fe/components/IntegrationsPage/components/ConnectionForm.d.ts +10 -0
  6. package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +112 -0
  7. package/dist/fe/components/IntegrationsPage/components/ConnectionList.d.ts +9 -0
  8. package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +184 -0
  9. package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.d.ts +2 -0
  10. package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +131 -0
  11. package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.d.ts +21 -0
  12. package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +49 -0
  13. package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.d.ts +12 -0
  14. package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +95 -0
  15. package/dist/fe/components/IntegrationsPage/components/ConnectionTest.d.ts +9 -0
  16. package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +116 -0
  17. package/dist/fe/components/IntegrationsPage/components/CredentialFields.d.ts +9 -0
  18. package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +171 -0
  19. package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.d.ts +13 -0
  20. package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +63 -0
  21. package/dist/fe/components/IntegrationsPage/components/EndpointForm.d.ts +11 -0
  22. package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +166 -0
  23. package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.d.ts +19 -0
  24. package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +162 -0
  25. package/dist/fe/components/IntegrationsPage/components/EndpointList.d.ts +12 -0
  26. package/dist/fe/components/IntegrationsPage/components/EndpointList.js +205 -0
  27. package/dist/fe/components/IntegrationsPage/components/EndpointSample.d.ts +6 -0
  28. package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +204 -0
  29. package/dist/fe/components/IntegrationsPage/components/EndpointsTab.d.ts +5 -0
  30. package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +101 -0
  31. package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.d.ts +17 -0
  32. package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +179 -0
  33. package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.d.ts +11 -0
  34. package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +115 -0
  35. package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.d.ts +2 -0
  36. package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +175 -0
  37. package/dist/fe/components/IntegrationsPage/components/MappingEditor.d.ts +15 -0
  38. package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +193 -0
  39. package/dist/fe/components/IntegrationsPage/components/MappingList.d.ts +11 -0
  40. package/dist/fe/components/IntegrationsPage/components/MappingList.js +191 -0
  41. package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.d.ts +19 -0
  42. package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +150 -0
  43. package/dist/fe/components/IntegrationsPage/components/MappingsTab.d.ts +5 -0
  44. package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +154 -0
  45. package/dist/fe/components/IntegrationsPage/components/PaginationFields.d.ts +9 -0
  46. package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +218 -0
  47. package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.d.ts +24 -0
  48. package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.js +180 -0
  49. package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.d.ts +33 -0
  50. package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +100 -0
  51. package/dist/fe/components/IntegrationsPage/components/Primitives.d.ts +65 -0
  52. package/dist/fe/components/IntegrationsPage/components/Primitives.js +94 -0
  53. package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.d.ts +10 -0
  54. package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +48 -0
  55. package/dist/fe/components/IntegrationsPage/components/RunHistory.d.ts +10 -0
  56. package/dist/fe/components/IntegrationsPage/components/RunHistory.js +124 -0
  57. package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.d.ts +7 -0
  58. package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +129 -0
  59. package/dist/fe/components/IntegrationsPage/components/RunPanel.d.ts +9 -0
  60. package/dist/fe/components/IntegrationsPage/components/RunPanel.js +160 -0
  61. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.d.ts +5 -0
  62. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +43 -0
  63. package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.d.ts +5 -0
  64. package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +92 -0
  65. package/dist/fe/components/IntegrationsPage/components/RunProgressBar.d.ts +8 -0
  66. package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +77 -0
  67. package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.d.ts +13 -0
  68. package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +65 -0
  69. package/dist/fe/components/IntegrationsPage/components/RunsTab.d.ts +13 -0
  70. package/dist/fe/components/IntegrationsPage/components/RunsTab.js +39 -0
  71. package/dist/fe/components/IntegrationsPage/components/Skeletons.d.ts +9 -0
  72. package/dist/fe/components/IntegrationsPage/components/Skeletons.js +139 -0
  73. package/dist/fe/components/IntegrationsPage/components/authOptions.d.ts +29 -0
  74. package/dist/fe/components/IntegrationsPage/components/authOptions.js +71 -0
  75. package/dist/fe/components/IntegrationsPage/components/connectionDraft.d.ts +64 -0
  76. package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +112 -0
  77. package/dist/fe/components/IntegrationsPage/components/credentialSlots.d.ts +25 -0
  78. package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +67 -0
  79. package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.d.ts +16 -0
  80. package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +41 -0
  81. package/dist/fe/components/IntegrationsPage/components/formSupport.d.ts +19 -0
  82. package/dist/fe/components/IntegrationsPage/components/formSupport.js +25 -0
  83. package/dist/fe/components/IntegrationsPage/components/mappingDraft.d.ts +40 -0
  84. package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +44 -0
  85. package/dist/fe/components/IntegrationsPage/components/pathParams.d.ts +9 -0
  86. package/dist/fe/components/IntegrationsPage/components/pathParams.js +15 -0
  87. package/dist/fe/components/IntegrationsPage/components/runFormat.d.ts +33 -0
  88. package/dist/fe/components/IntegrationsPage/components/runFormat.js +85 -0
  89. package/dist/fe/components/IntegrationsPage/components/useInfiniteList.d.ts +40 -0
  90. package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +110 -0
  91. package/dist/fe/components/IntegrationsPage/index.d.ts +18 -0
  92. package/dist/fe/components/IntegrationsPage/index.js +11 -0
  93. package/dist/fe/components/IntegrationsPage/store/index.d.ts +2 -0
  94. package/dist/fe/components/IntegrationsPage/store/index.js +124 -0
  95. package/dist/fe/components/IntegrationsPage/store/state.d.ts +80 -0
  96. package/dist/fe/components/IntegrationsPage/store/state.js +32 -0
  97. package/dist/fe/components/IntegrationsPage/theme/index.d.ts +103 -0
  98. package/dist/fe/components/IntegrationsPage/theme/index.js +113 -0
  99. package/dist/fe/components/IntegrationsPage/types/actions.d.ts +91 -0
  100. package/dist/fe/components/IntegrationsPage/types/actions.js +1 -0
  101. package/dist/fe/components/IntegrationsPage/types/index.d.ts +3 -0
  102. package/dist/fe/components/IntegrationsPage/types/index.js +3 -0
  103. package/dist/fe/components/IntegrationsPage/types/records.d.ts +139 -0
  104. package/dist/fe/components/IntegrationsPage/types/records.js +11 -0
  105. package/dist/fe/components/IntegrationsPage/types/results.d.ts +52 -0
  106. package/dist/fe/components/IntegrationsPage/types/results.js +4 -0
  107. package/dist/fe/index.d.ts +2 -0
  108. package/dist/fe/index.js +1 -0
  109. package/dist/index.js +9 -9
  110. package/dist/src/ElysiaPlugin/routes/integrations/inspect.d.ts +87 -0
  111. package/dist/src/ElysiaPlugin/routes/integrations/inspectRoutes.d.ts +41 -0
  112. package/dist/src/ElysiaPlugin/routes/integrations/repository.d.ts +31 -0
  113. package/package.json +1 -1
  114. package/scripts/generate-schema.ts +1 -0
@@ -0,0 +1,2 @@
1
+ import { type ConnectionFieldsProps } from './connectionDraft';
2
+ export declare function ConnectionAuthFields(props: ConnectionFieldsProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,85 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { integrationsPageTheme } from '../theme';
4
+ import { AUTH_TYPE_OPTIONS, authTypeHint, toAuthType } from './authOptions';
5
+ import { ConnectionOauthFields } from './ConnectionOauthFields';
6
+ import { usesHeaderName, usesQueryParam } from './connectionDraft';
7
+ import { describedBy } from './formSupport';
8
+ import { Field } from './Primitives';
9
+ /**
10
+ * How the connection proves who it is.
11
+ *
12
+ * Only the boxes the chosen auth type reads are on screen. A form that showed
13
+ * all of them would invite an operator to fill in a token URL that a bearer
14
+ * connection never looks at, and then to spend an afternoon on why it changed
15
+ * nothing. The secrets themselves are not here — they live in CredentialFields,
16
+ * because they are written and never read back.
17
+ */ const theme = integrationsPageTheme;
18
+ export function ConnectionAuthFields(props) {
19
+ const { draft, disabled, onChange } = props;
20
+ return /*#__PURE__*/ _jsxs("section", {
21
+ "aria-label": "Authentication",
22
+ className: "flex flex-col gap-3",
23
+ children: [
24
+ /*#__PURE__*/ _jsx(Field, {
25
+ hint: authTypeHint(draft.authType),
26
+ htmlFor: "connection-auth-type",
27
+ label: "Auth type",
28
+ children: /*#__PURE__*/ _jsx("select", {
29
+ "aria-describedby": describedBy('connection-auth-type', true, false),
30
+ className: theme.field.input,
31
+ disabled: disabled,
32
+ id: "connection-auth-type",
33
+ onChange: (event)=>{
34
+ const next = toAuthType(event.target.value);
35
+ if (next) onChange({
36
+ authType: next
37
+ });
38
+ },
39
+ value: draft.authType,
40
+ children: AUTH_TYPE_OPTIONS.map((option)=>/*#__PURE__*/ _jsx("option", {
41
+ value: option.value,
42
+ children: option.label
43
+ }, option.value))
44
+ })
45
+ }),
46
+ usesHeaderName(draft.authType) ? /*#__PURE__*/ _jsx(Field, {
47
+ hint: "Empty means Authorization. Set it when the service reads the credential from a header of its own.",
48
+ htmlFor: "connection-auth-header",
49
+ label: "Header name",
50
+ children: /*#__PURE__*/ _jsx("input", {
51
+ "aria-describedby": describedBy('connection-auth-header', true, false),
52
+ className: theme.field.input,
53
+ disabled: disabled,
54
+ id: "connection-auth-header",
55
+ onChange: (event)=>onChange({
56
+ authHeaderName: event.target.value
57
+ }),
58
+ placeholder: "X-Api-Key",
59
+ type: "text",
60
+ value: draft.authHeaderName
61
+ })
62
+ }) : null,
63
+ usesQueryParam(draft.authType) ? /*#__PURE__*/ _jsx(Field, {
64
+ hint: "Use this instead of a header when the key belongs in the query string. Fill in one or the other, not both.",
65
+ htmlFor: "connection-auth-query",
66
+ label: "Query parameter",
67
+ children: /*#__PURE__*/ _jsx("input", {
68
+ "aria-describedby": describedBy('connection-auth-query', true, false),
69
+ className: theme.field.input,
70
+ disabled: disabled,
71
+ id: "connection-auth-query",
72
+ onChange: (event)=>onChange({
73
+ authQueryParam: event.target.value
74
+ }),
75
+ placeholder: "api_key",
76
+ type: "text",
77
+ value: draft.authQueryParam
78
+ })
79
+ }) : null,
80
+ /*#__PURE__*/ _jsx(ConnectionOauthFields, {
81
+ ...props
82
+ })
83
+ ]
84
+ });
85
+ }
@@ -0,0 +1,2 @@
1
+ import type { ConnectionFieldsProps } from './connectionDraft';
2
+ export declare function ConnectionBasicFields({ draft, errors, disabled, onChange, }: ConnectionFieldsProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../../../utils/cn';
4
+ import { integrationsPageTheme } from '../theme';
5
+ import { describedBy } from './formSupport';
6
+ import { Field } from './Primitives';
7
+ /**
8
+ * What the connection IS: its name, where it lives, and the two switches that
9
+ * decide whether it is used and how carefully.
10
+ */ const theme = integrationsPageTheme;
11
+ const CHECKBOX = 'h-4 w-4 shrink-0 accent-sky-600 dark:accent-sky-500';
12
+ const BASE_URL_HINT = 'The scheme is free. https:// and plain http:// are both accepted and neither is rewritten — calling a service inside the same cluster over http, for example http://hr-service:8080, is a legitimate setup.';
13
+ export function ConnectionBasicFields({ draft, errors, disabled, onChange }) {
14
+ return /*#__PURE__*/ _jsxs("section", {
15
+ "aria-label": "Connection details",
16
+ className: "flex flex-col gap-3",
17
+ children: [
18
+ /*#__PURE__*/ _jsx(Field, {
19
+ error: errors.name,
20
+ htmlFor: "connection-name",
21
+ label: "Name",
22
+ children: /*#__PURE__*/ _jsx("input", {
23
+ "aria-describedby": describedBy('connection-name', false, Boolean(errors.name)),
24
+ className: theme.field.input,
25
+ disabled: disabled,
26
+ id: "connection-name",
27
+ onChange: (event)=>onChange({
28
+ name: event.target.value
29
+ }),
30
+ placeholder: "Directory service",
31
+ type: "text",
32
+ value: draft.name
33
+ })
34
+ }),
35
+ /*#__PURE__*/ _jsx(Field, {
36
+ error: errors.slug,
37
+ hint: "Used to refer to this connection elsewhere. It does not change once things point at it.",
38
+ htmlFor: "connection-slug",
39
+ label: "Slug",
40
+ children: /*#__PURE__*/ _jsx("input", {
41
+ "aria-describedby": describedBy('connection-slug', true, Boolean(errors.slug)),
42
+ className: theme.field.input,
43
+ disabled: disabled,
44
+ id: "connection-slug",
45
+ onChange: (event)=>onChange({
46
+ slug: event.target.value
47
+ }),
48
+ placeholder: "hr-service",
49
+ type: "text",
50
+ value: draft.slug
51
+ })
52
+ }),
53
+ /*#__PURE__*/ _jsx(Field, {
54
+ error: errors.baseUrl,
55
+ hint: BASE_URL_HINT,
56
+ htmlFor: "connection-base-url",
57
+ label: "Base URL",
58
+ children: /*#__PURE__*/ _jsx("input", {
59
+ "aria-describedby": describedBy('connection-base-url', true, Boolean(errors.baseUrl)),
60
+ className: theme.field.input,
61
+ disabled: disabled,
62
+ id: "connection-base-url",
63
+ onChange: (event)=>onChange({
64
+ baseUrl: event.target.value
65
+ }),
66
+ placeholder: "https://api.example.com",
67
+ type: "text",
68
+ value: draft.baseUrl
69
+ })
70
+ }),
71
+ /*#__PURE__*/ _jsx(Field, {
72
+ hint: "For whoever has to work out what this connection is, months from now.",
73
+ htmlFor: "connection-description",
74
+ label: "Description",
75
+ children: /*#__PURE__*/ _jsx("textarea", {
76
+ "aria-describedby": describedBy('connection-description', true, false),
77
+ className: cn(theme.field.input, 'h-20 py-2 sm:h-20'),
78
+ disabled: disabled,
79
+ id: "connection-description",
80
+ onChange: (event)=>onChange({
81
+ description: event.target.value
82
+ }),
83
+ placeholder: "Read-only employee directory used by the nightly import.",
84
+ value: draft.description
85
+ })
86
+ }),
87
+ /*#__PURE__*/ _jsxs("div", {
88
+ className: theme.field.wrapper,
89
+ children: [
90
+ /*#__PURE__*/ _jsxs("label", {
91
+ className: cn(theme.field.label, 'flex items-center gap-2'),
92
+ htmlFor: "connection-enabled",
93
+ children: [
94
+ /*#__PURE__*/ _jsx("input", {
95
+ "aria-describedby": "connection-enabled-hint",
96
+ checked: draft.enabled,
97
+ className: CHECKBOX,
98
+ disabled: disabled,
99
+ id: "connection-enabled",
100
+ onChange: (event)=>onChange({
101
+ enabled: event.target.checked
102
+ }),
103
+ type: "checkbox"
104
+ }),
105
+ "Enabled"
106
+ ]
107
+ }),
108
+ /*#__PURE__*/ _jsx("span", {
109
+ className: theme.field.hint,
110
+ id: "connection-enabled-hint",
111
+ children: "Turning this off stops every endpoint and import that goes through this connection. The settings are kept."
112
+ })
113
+ ]
114
+ }),
115
+ /*#__PURE__*/ _jsxs("div", {
116
+ className: theme.field.wrapper,
117
+ children: [
118
+ /*#__PURE__*/ _jsxs("label", {
119
+ className: cn(theme.field.label, 'flex items-center gap-2'),
120
+ htmlFor: "connection-tls-verify",
121
+ children: [
122
+ /*#__PURE__*/ _jsx("input", {
123
+ "aria-describedby": "connection-tls-verify-hint",
124
+ checked: draft.tlsVerify,
125
+ className: CHECKBOX,
126
+ disabled: disabled,
127
+ id: "connection-tls-verify",
128
+ onChange: (event)=>onChange({
129
+ tlsVerify: event.target.checked
130
+ }),
131
+ type: "checkbox"
132
+ }),
133
+ "Verify TLS certificate"
134
+ ]
135
+ }),
136
+ /*#__PURE__*/ _jsx("span", {
137
+ className: theme.field.hint,
138
+ id: "connection-tls-verify-hint",
139
+ children: draft.tlsVerify ? 'On. Unchecking it applies to requests made through THIS connection only — no other connection and nothing else in the app is affected.' : 'Off. Certificates are not checked for requests made through THIS connection only. Every other connection still verifies. Use it for an internal service with its own certificate authority.'
140
+ })
141
+ ]
142
+ }),
143
+ /*#__PURE__*/ _jsx(Field, {
144
+ error: errors.timeoutMs,
145
+ hint: "How long one request may take before it is given up on. Empty means the server default.",
146
+ htmlFor: "connection-timeout",
147
+ label: "Timeout (ms)",
148
+ children: /*#__PURE__*/ _jsx("input", {
149
+ "aria-describedby": describedBy('connection-timeout', true, Boolean(errors.timeoutMs)),
150
+ className: theme.field.input,
151
+ disabled: disabled,
152
+ id: "connection-timeout",
153
+ inputMode: "numeric",
154
+ min: 1,
155
+ onChange: (event)=>onChange({
156
+ timeoutMs: event.target.value
157
+ }),
158
+ placeholder: "30000",
159
+ type: "number",
160
+ value: draft.timeoutMs
161
+ })
162
+ })
163
+ ]
164
+ });
165
+ }
@@ -0,0 +1,10 @@
1
+ import type { IntegrationSource, IntegrationsActions } from '../types';
2
+ export type ConnectionFormProps = {
3
+ actions: IntegrationsActions;
4
+ /** The connection being edited, or null for a blank form. */
5
+ source: IntegrationSource | null;
6
+ /** True while the record being edited is still on its way. */
7
+ loading?: boolean;
8
+ onSaved?: (source: IntegrationSource) => void;
9
+ };
10
+ export declare function ConnectionForm({ actions, source, loading, onSaved }: ConnectionFormProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useEffectEvent, useState } from 'react';
4
+ import { useIntegrationsStore } from '../store';
5
+ import { integrationsPageTheme } from '../theme';
6
+ import { ConnectionAuthFields } from './ConnectionAuthFields';
7
+ import { ConnectionBasicFields } from './ConnectionBasicFields';
8
+ import { draftFromSource, draftToSource, emptyConnectionDraft, validateConnectionDraft } from './connectionDraft';
9
+ import { errorText } from './formSupport';
10
+ import { Badge } from './Primitives';
11
+ import { CardSkeleton } from './Skeletons';
12
+ /**
13
+ * Add or edit one connection.
14
+ *
15
+ * The form holds a draft rather than editing the record in place, so abandoning
16
+ * a half-finished change costs nothing and the saved record is never a mixture
17
+ * of what was stored and what is being typed.
18
+ */ const theme = integrationsPageTheme;
19
+ export function ConnectionForm({ actions, source, loading = false, onSaved }) {
20
+ const store = useIntegrationsStore();
21
+ const [draft, setDraft] = useState(()=>source ? draftFromSource(source) : emptyConnectionDraft);
22
+ const [errors, setErrors] = useState({});
23
+ const [saving, setSaving] = useState(false);
24
+ // Only the identity resets the boxes. Re-reading the same record — which is
25
+ // what a save returns — would throw away anything typed since.
26
+ const resetDraft = useEffectEvent(()=>{
27
+ setDraft(source ? draftFromSource(source) : emptyConnectionDraft);
28
+ setErrors({});
29
+ setSaving(false);
30
+ });
31
+ useEffect(()=>{
32
+ resetDraft();
33
+ }, [
34
+ source?.id
35
+ ]);
36
+ const update = (patch)=>{
37
+ setDraft((current)=>({
38
+ ...current,
39
+ ...patch
40
+ }));
41
+ };
42
+ const handleSubmit = (event)=>{
43
+ event.preventDefault();
44
+ const found = validateConnectionDraft(draft);
45
+ setErrors(found);
46
+ if (Object.keys(found).length > 0) return;
47
+ setSaving(true);
48
+ store.setError(null);
49
+ actions.saveSource(draftToSource(draft, source?.id)).then((saved)=>{
50
+ setSaving(false);
51
+ if (!saved) {
52
+ store.setError('The connection was not saved. The server did not return the record.');
53
+ return;
54
+ }
55
+ // Selecting resets everything hanging off a connection, so it is only
56
+ // right for a NEW one — doing it on an edit would empty the endpoint,
57
+ // mapping and run lists the operator is working in.
58
+ if (!source) store.selectSource(saved.id);
59
+ onSaved?.(saved);
60
+ }).catch((reason)=>{
61
+ setSaving(false);
62
+ store.setError(errorText(reason, 'The connection could not be saved.'));
63
+ });
64
+ };
65
+ if (loading) return /*#__PURE__*/ _jsx(CardSkeleton, {});
66
+ return /*#__PURE__*/ _jsxs("form", {
67
+ className: theme.card.wrapper,
68
+ noValidate: true,
69
+ onSubmit: handleSubmit,
70
+ children: [
71
+ /*#__PURE__*/ _jsxs("div", {
72
+ className: theme.card.header,
73
+ children: [
74
+ /*#__PURE__*/ _jsx("h2", {
75
+ className: theme.card.title,
76
+ children: source ? 'Edit connection' : 'New connection'
77
+ }),
78
+ source ? /*#__PURE__*/ _jsx(Badge, {
79
+ tone: source.enabled === false ? 'neutral' : 'success',
80
+ children: source.enabled === false ? 'Disabled' : 'Enabled'
81
+ }) : null
82
+ ]
83
+ }),
84
+ /*#__PURE__*/ _jsxs("div", {
85
+ className: theme.card.body,
86
+ children: [
87
+ /*#__PURE__*/ _jsx(ConnectionBasicFields, {
88
+ disabled: saving,
89
+ draft: draft,
90
+ errors: errors,
91
+ onChange: update
92
+ }),
93
+ /*#__PURE__*/ _jsx(ConnectionAuthFields, {
94
+ disabled: saving,
95
+ draft: draft,
96
+ errors: errors,
97
+ onChange: update
98
+ })
99
+ ]
100
+ }),
101
+ /*#__PURE__*/ _jsx("div", {
102
+ className: theme.card.footer,
103
+ children: /*#__PURE__*/ _jsx("button", {
104
+ className: theme.button.primary,
105
+ disabled: saving,
106
+ type: "submit",
107
+ children: saving ? 'Saving…' : source ? 'Save changes' : 'Create connection'
108
+ })
109
+ })
110
+ ]
111
+ });
112
+ }
@@ -0,0 +1,9 @@
1
+ import type { IntegrationsActions } from '../types';
2
+ export type ConnectionListProps = {
3
+ actions: IntegrationsActions;
4
+ /** Asked for a blank form. The selection is cleared before this runs. */
5
+ onNewConnection?: () => void;
6
+ /** Bump after a save or a delete to pull the list again. */
7
+ reloadToken?: number;
8
+ };
9
+ export declare function ConnectionList({ actions, onNewConnection, reloadToken }: ConnectionListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useEffectEvent, useState } from 'react';
4
+ import { cn } from '../../../utils/cn';
5
+ import { useIntegrationsStore } from '../store';
6
+ import { integrationsPageTheme } from '../theme';
7
+ import { Badge, Field, Notice } from './Primitives';
8
+ import { ListSkeleton } from './Skeletons';
9
+ import { useInfiniteList } from './useInfiniteList';
10
+ /**
11
+ * The rail of connections.
12
+ *
13
+ * The server owns the list: what the operator types goes back out as a `search`
14
+ * on the next request, and the next page arrives when the sentinel comes into
15
+ * view. Nothing here narrows what has already been fetched, so the rail behaves
16
+ * the same at four connections and at four hundred.
17
+ */ const theme = integrationsPageTheme;
18
+ const PAGE_SIZE = 20;
19
+ const SEARCH_DEBOUNCE_MS = 300;
20
+ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
21
+ const store = useIntegrationsStore();
22
+ const search = store.sourcesSearch;
23
+ const selectedId = store.selectedSourceId;
24
+ const [term, setTerm] = useState(search);
25
+ // Typed characters become a request only once the typing stops; every
26
+ // keystroke would be a page-1 fetch and the answers would race each other.
27
+ const commitSearch = useEffectEvent(()=>{
28
+ store.setSourcesSearch(term);
29
+ });
30
+ useEffect(()=>{
31
+ const timer = setTimeout(commitSearch, SEARCH_DEBOUNCE_MS);
32
+ return ()=>clearTimeout(timer);
33
+ }, [
34
+ term
35
+ ]);
36
+ const list = useInfiniteList({
37
+ load: (page)=>actions.listSources({
38
+ page,
39
+ limit: PAGE_SIZE,
40
+ search: search.length > 0 ? search : undefined
41
+ }).then((result)=>({
42
+ items: result.items,
43
+ total: result.meta?.totalItems ?? result.items.length
44
+ })),
45
+ deps: [
46
+ search,
47
+ reloadToken
48
+ ],
49
+ pageSize: PAGE_SIZE
50
+ });
51
+ // Mirrored so the form, the credential boxes and the test button can all read
52
+ // the selected record without any of them fetching it a second time.
53
+ const publishItems = useEffectEvent(()=>{
54
+ store.setSources(list.items, list.total);
55
+ });
56
+ const publishLoading = useEffectEvent(()=>{
57
+ store.setSourcesLoading(list.loading);
58
+ });
59
+ useEffect(()=>{
60
+ publishItems();
61
+ }, [
62
+ list.items,
63
+ list.total
64
+ ]);
65
+ useEffect(()=>{
66
+ publishLoading();
67
+ }, [
68
+ list.loading
69
+ ]);
70
+ const handleNew = ()=>{
71
+ store.selectSource(null);
72
+ onNewConnection?.();
73
+ };
74
+ const firstLoad = list.loading && list.items.length === 0;
75
+ return(// The shell owns the landmark and its name; naming it again here gave a
76
+ // screen reader two regions called "Connections" nested inside each other.
77
+ /*#__PURE__*/ _jsxs("div", {
78
+ className: theme.layout.aside,
79
+ children: [
80
+ /*#__PURE__*/ _jsxs("div", {
81
+ className: theme.header.wrapper,
82
+ children: [
83
+ /*#__PURE__*/ _jsxs("div", {
84
+ children: [
85
+ /*#__PURE__*/ _jsx("h2", {
86
+ className: theme.header.title,
87
+ children: "Connections"
88
+ }),
89
+ /*#__PURE__*/ _jsx("p", {
90
+ className: theme.header.subtitle,
91
+ children: list.total === 1 ? '1 connection' : `${list.total} connections`
92
+ })
93
+ ]
94
+ }),
95
+ /*#__PURE__*/ _jsx("div", {
96
+ className: theme.header.actions,
97
+ children: /*#__PURE__*/ _jsx("button", {
98
+ className: theme.button.primary,
99
+ onClick: handleNew,
100
+ type: "button",
101
+ children: "New connection"
102
+ })
103
+ })
104
+ ]
105
+ }),
106
+ /*#__PURE__*/ _jsx(Field, {
107
+ htmlFor: "connection-search",
108
+ label: "Search",
109
+ children: /*#__PURE__*/ _jsx("input", {
110
+ className: theme.field.input,
111
+ id: "connection-search",
112
+ onChange: (event)=>setTerm(event.target.value),
113
+ placeholder: "Name or slug",
114
+ type: "search",
115
+ value: term
116
+ })
117
+ }),
118
+ firstLoad ? /*#__PURE__*/ _jsx(ListSkeleton, {
119
+ rows: 6
120
+ }) : null,
121
+ list.error ? /*#__PURE__*/ _jsxs(Notice, {
122
+ title: "This list could not be loaded",
123
+ tone: "danger",
124
+ children: [
125
+ list.error,
126
+ ' ',
127
+ /*#__PURE__*/ _jsx("button", {
128
+ className: theme.button.ghost,
129
+ onClick: list.reload,
130
+ type: "button",
131
+ children: "Try again"
132
+ })
133
+ ]
134
+ }) : null,
135
+ !firstLoad && list.items.length === 0 && !list.error ? /*#__PURE__*/ _jsx("p", {
136
+ className: theme.list.empty,
137
+ children: search.length > 0 ? `No connection matches “${search}”. Clear the search to see them all.` : 'No connections yet. A connection is one service this app calls — its base URL, how it authenticates and how long it may take. Start with New connection.'
138
+ }) : null,
139
+ list.items.length > 0 ? /*#__PURE__*/ _jsx("ul", {
140
+ className: theme.list.wrapper,
141
+ children: list.items.map((source)=>/*#__PURE__*/ _jsx("li", {
142
+ children: /*#__PURE__*/ _jsxs("button", {
143
+ "aria-current": source.id === selectedId ? 'true' : undefined,
144
+ className: cn(theme.list.item, source.id === selectedId && theme.list.itemSelected),
145
+ onClick: ()=>store.selectSource(source.id),
146
+ type: "button",
147
+ children: [
148
+ /*#__PURE__*/ _jsxs("span", {
149
+ className: "flex w-full items-center gap-2",
150
+ children: [
151
+ /*#__PURE__*/ _jsx("span", {
152
+ className: cn(theme.list.itemTitle, 'min-w-0 flex-1'),
153
+ children: source.name
154
+ }),
155
+ source.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
156
+ tone: "neutral",
157
+ children: "Off"
158
+ }) : null
159
+ ]
160
+ }),
161
+ /*#__PURE__*/ _jsx("span", {
162
+ className: theme.list.itemMeta,
163
+ children: source.slug
164
+ }),
165
+ /*#__PURE__*/ _jsx("span", {
166
+ className: theme.list.itemMeta,
167
+ children: source.baseUrl
168
+ })
169
+ ]
170
+ })
171
+ }, source.id))
172
+ }) : null,
173
+ /*#__PURE__*/ _jsx("div", {
174
+ "aria-hidden": "true",
175
+ className: theme.list.sentinel,
176
+ ref: list.sentinelRef
177
+ }),
178
+ list.loading && list.items.length > 0 ? /*#__PURE__*/ _jsx("p", {
179
+ className: theme.field.hint,
180
+ children: "Loading more…"
181
+ }) : null
182
+ ]
183
+ }));
184
+ }
@@ -0,0 +1,2 @@
1
+ import { type ConnectionFieldsProps } from './connectionDraft';
2
+ export declare function ConnectionOauthFields({ draft, errors, disabled, onChange, }: ConnectionFieldsProps): import("react/jsx-runtime").JSX.Element | null;