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.
- package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +85 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +165 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionForm.d.ts +10 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +112 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +184 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +131 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.d.ts +21 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +49 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.d.ts +12 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +95 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +116 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialFields.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +171 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +63 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointForm.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +166 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.d.ts +19 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +162 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointList.d.ts +12 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointList.js +205 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.d.ts +6 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +204 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +101 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.d.ts +17 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +179 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +115 -0
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +175 -0
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.d.ts +15 -0
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +193 -0
- package/dist/fe/components/IntegrationsPage/components/MappingList.d.ts +11 -0
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +191 -0
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.d.ts +19 -0
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +150 -0
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +154 -0
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +218 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.d.ts +24 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.js +180 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.d.ts +33 -0
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +100 -0
- package/dist/fe/components/IntegrationsPage/components/Primitives.d.ts +65 -0
- package/dist/fe/components/IntegrationsPage/components/Primitives.js +94 -0
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.d.ts +10 -0
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +48 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistory.d.ts +10 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistory.js +124 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.d.ts +7 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +129 -0
- package/dist/fe/components/IntegrationsPage/components/RunPanel.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +160 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +43 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.d.ts +5 -0
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +92 -0
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.d.ts +8 -0
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +77 -0
- package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +65 -0
- package/dist/fe/components/IntegrationsPage/components/RunsTab.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/RunsTab.js +39 -0
- package/dist/fe/components/IntegrationsPage/components/Skeletons.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/Skeletons.js +139 -0
- package/dist/fe/components/IntegrationsPage/components/authOptions.d.ts +29 -0
- package/dist/fe/components/IntegrationsPage/components/authOptions.js +71 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.d.ts +64 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +112 -0
- package/dist/fe/components/IntegrationsPage/components/credentialSlots.d.ts +25 -0
- package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +67 -0
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.d.ts +16 -0
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +41 -0
- package/dist/fe/components/IntegrationsPage/components/formSupport.d.ts +19 -0
- package/dist/fe/components/IntegrationsPage/components/formSupport.js +25 -0
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.d.ts +40 -0
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +44 -0
- package/dist/fe/components/IntegrationsPage/components/pathParams.d.ts +9 -0
- package/dist/fe/components/IntegrationsPage/components/pathParams.js +15 -0
- package/dist/fe/components/IntegrationsPage/components/runFormat.d.ts +33 -0
- package/dist/fe/components/IntegrationsPage/components/runFormat.js +85 -0
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.d.ts +40 -0
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +110 -0
- package/dist/fe/components/IntegrationsPage/index.d.ts +18 -0
- package/dist/fe/components/IntegrationsPage/index.js +11 -0
- package/dist/fe/components/IntegrationsPage/store/index.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/store/index.js +124 -0
- package/dist/fe/components/IntegrationsPage/store/state.d.ts +80 -0
- package/dist/fe/components/IntegrationsPage/store/state.js +32 -0
- package/dist/fe/components/IntegrationsPage/theme/index.d.ts +103 -0
- package/dist/fe/components/IntegrationsPage/theme/index.js +113 -0
- package/dist/fe/components/IntegrationsPage/types/actions.d.ts +91 -0
- package/dist/fe/components/IntegrationsPage/types/actions.js +1 -0
- package/dist/fe/components/IntegrationsPage/types/index.d.ts +3 -0
- package/dist/fe/components/IntegrationsPage/types/index.js +3 -0
- package/dist/fe/components/IntegrationsPage/types/records.d.ts +139 -0
- package/dist/fe/components/IntegrationsPage/types/records.js +11 -0
- package/dist/fe/components/IntegrationsPage/types/results.d.ts +52 -0
- package/dist/fe/components/IntegrationsPage/types/results.js +4 -0
- package/dist/fe/index.d.ts +2 -0
- package/dist/fe/index.js +1 -0
- package/dist/index.js +9 -9
- package/dist/src/ElysiaPlugin/routes/integrations/inspect.d.ts +87 -0
- package/dist/src/ElysiaPlugin/routes/integrations/inspectRoutes.d.ts +41 -0
- package/dist/src/ElysiaPlugin/routes/integrations/repository.d.ts +31 -0
- package/package.json +1 -1
- package/scripts/generate-schema.ts +1 -0
|
@@ -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,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
|
+
}
|