nucleus-core-ts 0.9.781 → 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/client.js +1 -1
- 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 +14 -14
- package/dist/src/Client/ApiCaller/system-tables.d.ts +505 -0
- package/dist/src/Client/Proxy/wsProxy.js +25 -1
- package/dist/src/ElysiaPlugin/routes/backup/types.d.ts +3 -1
- package/dist/src/ElysiaPlugin/routes/integrations/index.d.ts +49 -0
- 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 +88 -0
- package/dist/src/ElysiaPlugin/routes/integrations/types.d.ts +22 -0
- package/dist/src/ElysiaPlugin/routes/pubsub/frame.d.ts +37 -0
- package/dist/src/ElysiaPlugin/routes/pubsub/frame.test.d.ts +1 -0
- package/dist/src/Services/Backup/BackupService.d.ts +16 -8
- package/dist/src/Services/Backup/types.d.ts +25 -0
- package/dist/src/Services/Integrations/bindings.d.ts +116 -0
- package/dist/src/Services/Integrations/bindings.test.d.ts +1 -0
- package/dist/src/Services/Integrations/collect.d.ts +68 -0
- package/dist/src/Services/Integrations/collect.test.d.ts +1 -0
- package/dist/src/Services/Integrations/fetcher.d.ts +100 -0
- package/dist/src/Services/Integrations/fetcher.test.d.ts +1 -0
- package/dist/src/Services/Integrations/lock.d.ts +77 -0
- package/dist/src/Services/Integrations/lock.test.d.ts +1 -0
- package/dist/src/Services/Integrations/mapping.d.ts +63 -0
- package/dist/src/Services/Integrations/mapping.test.d.ts +1 -0
- package/dist/src/Services/Integrations/oauth.d.ts +89 -0
- package/dist/src/Services/Integrations/oauth.test.d.ts +1 -0
- package/dist/src/Services/Integrations/pagination.d.ts +97 -0
- package/dist/src/Services/Integrations/pagination.test.d.ts +1 -0
- package/dist/src/Services/Integrations/plan.d.ts +74 -0
- package/dist/src/Services/Integrations/plan.test.d.ts +1 -0
- package/dist/src/Services/Integrations/revert.d.ts +78 -0
- package/dist/src/Services/Integrations/revert.test.d.ts +1 -0
- package/dist/src/Services/Integrations/runner.d.ts +126 -0
- package/dist/src/Services/Integrations/runner.test.d.ts +1 -0
- package/dist/src/Services/Integrations/types.d.ts +208 -0
- package/dist/src/Services/Integrations/writer.d.ts +105 -0
- package/dist/src/Services/Integrations/writer.test.d.ts +1 -0
- package/dist/src/Utils/cron.d.ts +65 -0
- package/dist/src/Utils/cron.test.d.ts +1 -0
- package/dist/src/types.d.ts +67 -1
- package/package.json +1 -1
- package/schemas/config.nucleus.json +43 -0
- package/schemas/table.schema.json +2 -1
- package/scripts/generate-schema.test.ts +78 -0
- package/scripts/generate-schema.ts +43 -5
- package/src/system.tables.json +362 -0
|
@@ -0,0 +1,63 @@
|
|
|
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 { slotHint, slotLabel } from './credentialSlots';
|
|
6
|
+
import { describedBy } from './formSupport';
|
|
7
|
+
import { Badge, Field } from './Primitives';
|
|
8
|
+
/**
|
|
9
|
+
* One secret: a box to type a new value into, the masked preview of whatever is
|
|
10
|
+
* already stored, and the only way to remove it.
|
|
11
|
+
*
|
|
12
|
+
* Clearing is its own button rather than "save an empty box", because an empty
|
|
13
|
+
* box is what an operator leaves behind when they came to change something else.
|
|
14
|
+
* Making that mean "delete the credential" would break a working connection by
|
|
15
|
+
* accident, roughly once.
|
|
16
|
+
*/ const theme = integrationsPageTheme;
|
|
17
|
+
export function CredentialSlotRow({ slot, source, value, maskedHint, disabled, onChange, onClear }) {
|
|
18
|
+
const id = `credential-${slot}`;
|
|
19
|
+
const label = slotLabel(slot, source);
|
|
20
|
+
const stored = maskedHint !== null && maskedHint.length > 0;
|
|
21
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
22
|
+
className: "flex flex-col gap-2",
|
|
23
|
+
children: [
|
|
24
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
25
|
+
hint: slotHint(slot, source),
|
|
26
|
+
htmlFor: id,
|
|
27
|
+
label: label,
|
|
28
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
29
|
+
"aria-describedby": describedBy(id, true, false),
|
|
30
|
+
autoComplete: "new-password",
|
|
31
|
+
className: theme.field.input,
|
|
32
|
+
disabled: disabled,
|
|
33
|
+
id: id,
|
|
34
|
+
onChange: (event)=>onChange(event.target.value),
|
|
35
|
+
placeholder: stored ? 'Leave empty to keep the stored value' : 'Nothing stored yet',
|
|
36
|
+
type: "password",
|
|
37
|
+
value: value
|
|
38
|
+
})
|
|
39
|
+
}),
|
|
40
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
41
|
+
className: "flex flex-wrap items-center gap-2",
|
|
42
|
+
children: [
|
|
43
|
+
/*#__PURE__*/ _jsx(Badge, {
|
|
44
|
+
tone: stored ? 'success' : 'neutral',
|
|
45
|
+
children: stored ? 'Stored' : 'Not set'
|
|
46
|
+
}),
|
|
47
|
+
stored ? /*#__PURE__*/ _jsx("span", {
|
|
48
|
+
className: cn(theme.field.hint, 'truncate font-mono'),
|
|
49
|
+
children: maskedHint
|
|
50
|
+
}) : null,
|
|
51
|
+
/*#__PURE__*/ _jsx("button", {
|
|
52
|
+
"aria-label": `Clear the stored ${label.toLowerCase()}`,
|
|
53
|
+
className: cn(theme.button.ghost, 'ml-auto'),
|
|
54
|
+
disabled: disabled || !stored,
|
|
55
|
+
onClick: onClear,
|
|
56
|
+
type: "button",
|
|
57
|
+
children: "Clear"
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IntegrationEndpoint, IntegrationsActions } from '../types';
|
|
2
|
+
export type EndpointFormProps = {
|
|
3
|
+
sourceId: string;
|
|
4
|
+
/** Null builds a new endpoint. */
|
|
5
|
+
endpoint: IntegrationEndpoint | null;
|
|
6
|
+
actions: IntegrationsActions;
|
|
7
|
+
onSaved: (endpoint: IntegrationEndpoint) => void;
|
|
8
|
+
onDeleted: () => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function EndpointForm({ sourceId, endpoint, actions, onSaved, onDeleted, onCancel, }: EndpointFormProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,166 @@
|
|
|
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 { EndpointFormFields } from './EndpointFormFields';
|
|
7
|
+
import { PaginationFields } from './PaginationFields';
|
|
8
|
+
import { ParamBindingRows } from './ParamBindingRows';
|
|
9
|
+
import { pathParamNames } from './pathParams';
|
|
10
|
+
/**
|
|
11
|
+
* One call against a connection: what to ask for, and where the records sit in
|
|
12
|
+
* the answer.
|
|
13
|
+
*
|
|
14
|
+
* The draft is seeded once. The parent remounts this form when the selection
|
|
15
|
+
* changes, so a half-typed path is never pulled out from under the operator by
|
|
16
|
+
* a list page arriving in the background.
|
|
17
|
+
*/ const theme = integrationsPageTheme;
|
|
18
|
+
export function EndpointForm({ sourceId, endpoint, actions, onSaved, onDeleted, onCancel }) {
|
|
19
|
+
const store = useIntegrationsStore();
|
|
20
|
+
const [saving, setSaving] = useState(false);
|
|
21
|
+
const [deleting, setDeleting] = useState(false);
|
|
22
|
+
const [draft, setDraft] = useState({
|
|
23
|
+
tag: endpoint?.tag ?? '',
|
|
24
|
+
name: endpoint?.name ?? '',
|
|
25
|
+
method: endpoint?.method ?? 'GET',
|
|
26
|
+
path: endpoint?.path ?? '',
|
|
27
|
+
description: endpoint?.description ?? '',
|
|
28
|
+
responseRootPath: endpoint?.responseRootPath ?? '',
|
|
29
|
+
enabled: endpoint?.enabled ?? true,
|
|
30
|
+
pagination: endpoint?.pagination ?? {
|
|
31
|
+
kind: 'none'
|
|
32
|
+
},
|
|
33
|
+
paramBindings: endpoint?.paramBindings ?? []
|
|
34
|
+
});
|
|
35
|
+
// Which tables a binding may read a column from. Loaded once; the list is
|
|
36
|
+
// small and an operator picking a parameter source should not wait for it.
|
|
37
|
+
const [targets, setTargets] = useState([]);
|
|
38
|
+
const loadTargets = useEffectEvent(()=>{
|
|
39
|
+
actions.listTargets().then(setTargets).catch(()=>{
|
|
40
|
+
// A binding can still be made against another endpoint or a fixed value,
|
|
41
|
+
// so this is a reduced menu rather than a broken screen.
|
|
42
|
+
setTargets([]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
useEffect(()=>{
|
|
46
|
+
loadTargets();
|
|
47
|
+
}, []);
|
|
48
|
+
const set = (patch)=>{
|
|
49
|
+
setDraft((previous)=>({
|
|
50
|
+
...previous,
|
|
51
|
+
...patch
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
54
|
+
const fail = (fallback)=>(error)=>{
|
|
55
|
+
setSaving(false);
|
|
56
|
+
setDeleting(false);
|
|
57
|
+
store.setError(error instanceof Error ? error.message : fallback);
|
|
58
|
+
};
|
|
59
|
+
const tag = draft.tag.trim();
|
|
60
|
+
const path = draft.path.trim();
|
|
61
|
+
const busy = saving || deleting;
|
|
62
|
+
const canSave = tag.length > 0 && path.length > 0;
|
|
63
|
+
const handleSubmit = (event)=>{
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
if (!canSave || busy) return;
|
|
66
|
+
save();
|
|
67
|
+
};
|
|
68
|
+
const save = ()=>{
|
|
69
|
+
setSaving(true);
|
|
70
|
+
store.setError(null);
|
|
71
|
+
actions.saveEndpoint({
|
|
72
|
+
id: endpoint?.id,
|
|
73
|
+
sourceId,
|
|
74
|
+
tag,
|
|
75
|
+
name: draft.name.trim() || null,
|
|
76
|
+
method: draft.method,
|
|
77
|
+
path,
|
|
78
|
+
description: draft.description.trim() || null,
|
|
79
|
+
responseRootPath: draft.responseRootPath.trim() || null,
|
|
80
|
+
pagination: draft.pagination,
|
|
81
|
+
paramBindings: draft.paramBindings,
|
|
82
|
+
enabled: draft.enabled
|
|
83
|
+
}).then((saved)=>{
|
|
84
|
+
setSaving(false);
|
|
85
|
+
if (saved) onSaved(saved);
|
|
86
|
+
else store.setError('The server did not return the saved endpoint.');
|
|
87
|
+
}).catch(fail('Saving the endpoint failed.'));
|
|
88
|
+
};
|
|
89
|
+
const remove = ()=>{
|
|
90
|
+
if (!endpoint) return;
|
|
91
|
+
setDeleting(true);
|
|
92
|
+
store.setError(null);
|
|
93
|
+
actions.deleteEndpoint(endpoint.id).then((ok)=>{
|
|
94
|
+
setDeleting(false);
|
|
95
|
+
if (ok) onDeleted();
|
|
96
|
+
else store.setError('The endpoint was not deleted.');
|
|
97
|
+
}).catch(fail('Deleting the endpoint failed.'));
|
|
98
|
+
};
|
|
99
|
+
return /*#__PURE__*/ _jsxs("form", {
|
|
100
|
+
className: theme.card.wrapper,
|
|
101
|
+
noValidate: true,
|
|
102
|
+
onSubmit: handleSubmit,
|
|
103
|
+
children: [
|
|
104
|
+
/*#__PURE__*/ _jsx("header", {
|
|
105
|
+
className: theme.card.header,
|
|
106
|
+
children: /*#__PURE__*/ _jsx("h3", {
|
|
107
|
+
className: theme.card.title,
|
|
108
|
+
children: endpoint ? 'Edit endpoint' : 'New endpoint'
|
|
109
|
+
})
|
|
110
|
+
}),
|
|
111
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
112
|
+
className: theme.card.body,
|
|
113
|
+
children: [
|
|
114
|
+
/*#__PURE__*/ _jsx(EndpointFormFields, {
|
|
115
|
+
disabled: busy,
|
|
116
|
+
draft: draft,
|
|
117
|
+
onChange: set
|
|
118
|
+
}),
|
|
119
|
+
/*#__PURE__*/ _jsx(PaginationFields, {
|
|
120
|
+
disabled: busy,
|
|
121
|
+
idPrefix: "endpoint-pagination",
|
|
122
|
+
onChange: (pagination)=>set({
|
|
123
|
+
pagination
|
|
124
|
+
}),
|
|
125
|
+
value: draft.pagination
|
|
126
|
+
}),
|
|
127
|
+
/*#__PURE__*/ _jsx(ParamBindingRows, {
|
|
128
|
+
disabled: busy,
|
|
129
|
+
entities: targets,
|
|
130
|
+
onChange: (paramBindings)=>set({
|
|
131
|
+
paramBindings
|
|
132
|
+
}),
|
|
133
|
+
required: pathParamNames(path),
|
|
134
|
+
siblings: store.endpoints.filter((candidate)=>candidate.id !== endpoint?.id),
|
|
135
|
+
value: draft.paramBindings
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
}),
|
|
139
|
+
/*#__PURE__*/ _jsxs("footer", {
|
|
140
|
+
className: theme.card.footer,
|
|
141
|
+
children: [
|
|
142
|
+
endpoint ? /*#__PURE__*/ _jsx("button", {
|
|
143
|
+
className: theme.button.danger,
|
|
144
|
+
disabled: busy,
|
|
145
|
+
onClick: remove,
|
|
146
|
+
type: "button",
|
|
147
|
+
children: deleting ? 'Deleting…' : 'Delete'
|
|
148
|
+
}) : null,
|
|
149
|
+
/*#__PURE__*/ _jsx("button", {
|
|
150
|
+
className: theme.button.secondary,
|
|
151
|
+
disabled: busy,
|
|
152
|
+
onClick: onCancel,
|
|
153
|
+
type: "button",
|
|
154
|
+
children: "Cancel"
|
|
155
|
+
}),
|
|
156
|
+
/*#__PURE__*/ _jsx("button", {
|
|
157
|
+
className: theme.button.primary,
|
|
158
|
+
disabled: busy || !canSave,
|
|
159
|
+
type: "submit",
|
|
160
|
+
children: saving ? 'Saving…' : 'Save endpoint'
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
})
|
|
164
|
+
]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IntegrationEndpoint, PaginationProfileValue, ParamBindingValue } from '../types';
|
|
2
|
+
export type EndpointDraft = {
|
|
3
|
+
tag: string;
|
|
4
|
+
name: string;
|
|
5
|
+
method: IntegrationEndpoint['method'];
|
|
6
|
+
path: string;
|
|
7
|
+
description: string;
|
|
8
|
+
responseRootPath: string;
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
pagination: PaginationProfileValue;
|
|
11
|
+
/** Where each path/query parameter of this endpoint gets its value. */
|
|
12
|
+
paramBindings: ParamBindingValue[];
|
|
13
|
+
};
|
|
14
|
+
export type EndpointFormFieldsProps = {
|
|
15
|
+
draft: EndpointDraft;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
onChange: (patch: Partial<EndpointDraft>) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function EndpointFormFields({ draft, disabled, onChange }: EndpointFormFieldsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { integrationsPageTheme } from '../theme';
|
|
5
|
+
import { Field } from './Primitives';
|
|
6
|
+
/**
|
|
7
|
+
* The plain fields of an endpoint.
|
|
8
|
+
*
|
|
9
|
+
* Split out of the form because the form's job is saving and deleting, and this
|
|
10
|
+
* one's is asking. The draft type lives here, next to the controls that fill it.
|
|
11
|
+
*/ const theme = integrationsPageTheme;
|
|
12
|
+
const METHODS = [
|
|
13
|
+
'GET',
|
|
14
|
+
'POST',
|
|
15
|
+
'PUT',
|
|
16
|
+
'PATCH',
|
|
17
|
+
'DELETE'
|
|
18
|
+
];
|
|
19
|
+
export function EndpointFormFields({ draft, disabled, onChange }) {
|
|
20
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
23
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
26
|
+
hint: "Groups the endpoints of one API.",
|
|
27
|
+
htmlFor: "endpoint-tag",
|
|
28
|
+
label: "Tag",
|
|
29
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
30
|
+
"aria-describedby": "endpoint-tag-hint",
|
|
31
|
+
className: theme.field.input,
|
|
32
|
+
disabled: disabled,
|
|
33
|
+
id: "endpoint-tag",
|
|
34
|
+
onChange: (event)=>onChange({
|
|
35
|
+
tag: event.target.value
|
|
36
|
+
}),
|
|
37
|
+
placeholder: "directory",
|
|
38
|
+
type: "text",
|
|
39
|
+
value: draft.tag
|
|
40
|
+
})
|
|
41
|
+
}),
|
|
42
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
43
|
+
htmlFor: "endpoint-name",
|
|
44
|
+
label: "Name",
|
|
45
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
46
|
+
className: theme.field.input,
|
|
47
|
+
disabled: disabled,
|
|
48
|
+
id: "endpoint-name",
|
|
49
|
+
onChange: (event)=>onChange({
|
|
50
|
+
name: event.target.value
|
|
51
|
+
}),
|
|
52
|
+
placeholder: "List people",
|
|
53
|
+
type: "text",
|
|
54
|
+
value: draft.name
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
}),
|
|
59
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
60
|
+
className: "flex flex-col gap-3 sm:flex-row",
|
|
61
|
+
children: [
|
|
62
|
+
/*#__PURE__*/ _jsx("div", {
|
|
63
|
+
className: "sm:w-32",
|
|
64
|
+
children: /*#__PURE__*/ _jsx(Field, {
|
|
65
|
+
htmlFor: "endpoint-method",
|
|
66
|
+
label: "Method",
|
|
67
|
+
children: /*#__PURE__*/ _jsx("select", {
|
|
68
|
+
className: theme.field.input,
|
|
69
|
+
disabled: disabled,
|
|
70
|
+
id: "endpoint-method",
|
|
71
|
+
onChange: (event)=>{
|
|
72
|
+
for (const method of METHODS){
|
|
73
|
+
if (method === event.target.value) onChange({
|
|
74
|
+
method
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
value: draft.method,
|
|
79
|
+
children: METHODS.map((method)=>/*#__PURE__*/ _jsx("option", {
|
|
80
|
+
value: method,
|
|
81
|
+
children: method
|
|
82
|
+
}, method))
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
}),
|
|
86
|
+
/*#__PURE__*/ _jsx("div", {
|
|
87
|
+
className: "min-w-0 flex-1",
|
|
88
|
+
children: /*#__PURE__*/ _jsx(Field, {
|
|
89
|
+
hint: "{brace} and :colon path parameters are supported — parameter bindings supply their values.",
|
|
90
|
+
htmlFor: "endpoint-path",
|
|
91
|
+
label: "Path",
|
|
92
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
93
|
+
"aria-describedby": "endpoint-path-hint",
|
|
94
|
+
className: theme.field.input,
|
|
95
|
+
disabled: disabled,
|
|
96
|
+
id: "endpoint-path",
|
|
97
|
+
onChange: (event)=>onChange({
|
|
98
|
+
path: event.target.value
|
|
99
|
+
}),
|
|
100
|
+
placeholder: "/v1/people/{id}",
|
|
101
|
+
type: "text",
|
|
102
|
+
value: draft.path
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
}),
|
|
108
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
109
|
+
hint: "Dotted path to the array of records. Leave it empty when the array is the response root.",
|
|
110
|
+
htmlFor: "endpoint-root",
|
|
111
|
+
label: "Response root path",
|
|
112
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
113
|
+
"aria-describedby": "endpoint-root-hint",
|
|
114
|
+
className: theme.field.input,
|
|
115
|
+
disabled: disabled,
|
|
116
|
+
id: "endpoint-root",
|
|
117
|
+
onChange: (event)=>onChange({
|
|
118
|
+
responseRootPath: event.target.value
|
|
119
|
+
}),
|
|
120
|
+
placeholder: "data.items",
|
|
121
|
+
type: "text",
|
|
122
|
+
value: draft.responseRootPath
|
|
123
|
+
})
|
|
124
|
+
}),
|
|
125
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
126
|
+
htmlFor: "endpoint-description",
|
|
127
|
+
label: "Description",
|
|
128
|
+
children: /*#__PURE__*/ _jsx("textarea", {
|
|
129
|
+
className: cn(theme.field.input, 'h-auto min-h-20 py-2'),
|
|
130
|
+
disabled: disabled,
|
|
131
|
+
id: "endpoint-description",
|
|
132
|
+
onChange: (event)=>onChange({
|
|
133
|
+
description: event.target.value
|
|
134
|
+
}),
|
|
135
|
+
placeholder: "What this call returns, in one line",
|
|
136
|
+
rows: 3,
|
|
137
|
+
value: draft.description
|
|
138
|
+
})
|
|
139
|
+
}),
|
|
140
|
+
/*#__PURE__*/ _jsxs("label", {
|
|
141
|
+
className: "flex items-center gap-2",
|
|
142
|
+
htmlFor: "endpoint-enabled",
|
|
143
|
+
children: [
|
|
144
|
+
/*#__PURE__*/ _jsx("input", {
|
|
145
|
+
checked: draft.enabled,
|
|
146
|
+
className: "size-4 shrink-0 accent-sky-600 dark:accent-sky-500",
|
|
147
|
+
disabled: disabled,
|
|
148
|
+
id: "endpoint-enabled",
|
|
149
|
+
onChange: (event)=>onChange({
|
|
150
|
+
enabled: event.target.checked
|
|
151
|
+
}),
|
|
152
|
+
type: "checkbox"
|
|
153
|
+
}),
|
|
154
|
+
/*#__PURE__*/ _jsx("span", {
|
|
155
|
+
className: theme.field.label,
|
|
156
|
+
children: "Enabled — a disabled endpoint is never called"
|
|
157
|
+
})
|
|
158
|
+
]
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IntegrationEndpoint, IntegrationsActions } from '../types';
|
|
2
|
+
export type EndpointListProps = {
|
|
3
|
+
sourceId: string;
|
|
4
|
+
actions: IntegrationsActions;
|
|
5
|
+
/** Asked for a blank form. The selection is cleared before this runs. */
|
|
6
|
+
onNewEndpoint?: () => void;
|
|
7
|
+
/** Handed the whole record so the form beside it needs no second fetch. */
|
|
8
|
+
onSelect?: (endpoint: IntegrationEndpoint) => void;
|
|
9
|
+
/** Bump after a save or a delete to pull the list again. */
|
|
10
|
+
reloadToken?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloadToken, }: EndpointListProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,205 @@
|
|
|
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 calls one connection knows how to make.
|
|
12
|
+
*
|
|
13
|
+
* The server owns the list: the search term goes back out on the next request
|
|
14
|
+
* and the next page arrives when the sentinel scrolls into view. Nothing here
|
|
15
|
+
* narrows rows already fetched, so a connection with six endpoints and one with
|
|
16
|
+
* six hundred behave identically.
|
|
17
|
+
*/ const theme = integrationsPageTheme;
|
|
18
|
+
const PAGE_SIZE = 20;
|
|
19
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
20
|
+
const METHOD_TONE = {
|
|
21
|
+
GET: 'info',
|
|
22
|
+
POST: 'success',
|
|
23
|
+
PUT: 'warning',
|
|
24
|
+
PATCH: 'warning',
|
|
25
|
+
DELETE: 'danger'
|
|
26
|
+
};
|
|
27
|
+
export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloadToken = 0 }) {
|
|
28
|
+
const store = useIntegrationsStore();
|
|
29
|
+
const search = store.endpointsSearch;
|
|
30
|
+
const selectedId = store.selectedEndpointId;
|
|
31
|
+
const [term, setTerm] = useState(search);
|
|
32
|
+
// Typed characters become a request only once the typing stops; a fetch per
|
|
33
|
+
// keystroke would put page-1 answers in a race with each other.
|
|
34
|
+
const commitSearch = useEffectEvent(()=>{
|
|
35
|
+
store.setEndpointsSearch(term);
|
|
36
|
+
});
|
|
37
|
+
useEffect(()=>{
|
|
38
|
+
const timer = setTimeout(commitSearch, SEARCH_DEBOUNCE_MS);
|
|
39
|
+
return ()=>clearTimeout(timer);
|
|
40
|
+
}, [
|
|
41
|
+
term
|
|
42
|
+
]);
|
|
43
|
+
const list = useInfiniteList({
|
|
44
|
+
load: (page)=>actions.listEndpoints(sourceId, {
|
|
45
|
+
page,
|
|
46
|
+
limit: PAGE_SIZE,
|
|
47
|
+
search: search.length > 0 ? search : undefined,
|
|
48
|
+
sort: [
|
|
49
|
+
{
|
|
50
|
+
field: 'tag',
|
|
51
|
+
direction: 'asc'
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}).then((result)=>({
|
|
55
|
+
items: result.items,
|
|
56
|
+
total: result.meta?.totalItems ?? result.items.length
|
|
57
|
+
})),
|
|
58
|
+
deps: [
|
|
59
|
+
sourceId,
|
|
60
|
+
search,
|
|
61
|
+
reloadToken
|
|
62
|
+
],
|
|
63
|
+
pageSize: PAGE_SIZE
|
|
64
|
+
});
|
|
65
|
+
// Mirrored so the mapping screens can name an endpoint without fetching it a
|
|
66
|
+
// second time. The rows themselves still come from, and only from, the server.
|
|
67
|
+
const publishItems = useEffectEvent(()=>{
|
|
68
|
+
store.setEndpoints(list.items, list.total);
|
|
69
|
+
});
|
|
70
|
+
const publishLoading = useEffectEvent(()=>{
|
|
71
|
+
store.setEndpointsLoading(list.loading);
|
|
72
|
+
});
|
|
73
|
+
useEffect(()=>{
|
|
74
|
+
publishItems();
|
|
75
|
+
}, [
|
|
76
|
+
list.items,
|
|
77
|
+
list.total
|
|
78
|
+
]);
|
|
79
|
+
useEffect(()=>{
|
|
80
|
+
publishLoading();
|
|
81
|
+
}, [
|
|
82
|
+
list.loading
|
|
83
|
+
]);
|
|
84
|
+
const handleNew = ()=>{
|
|
85
|
+
store.selectEndpoint(null);
|
|
86
|
+
onNewEndpoint?.();
|
|
87
|
+
};
|
|
88
|
+
const handleSelect = (endpoint)=>{
|
|
89
|
+
store.selectEndpoint(endpoint.id);
|
|
90
|
+
onSelect?.(endpoint);
|
|
91
|
+
};
|
|
92
|
+
const firstLoad = list.loading && list.items.length === 0;
|
|
93
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
94
|
+
"aria-label": "Endpoints",
|
|
95
|
+
className: theme.layout.aside,
|
|
96
|
+
children: [
|
|
97
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
98
|
+
className: theme.header.wrapper,
|
|
99
|
+
children: [
|
|
100
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
101
|
+
children: [
|
|
102
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
103
|
+
className: theme.header.title,
|
|
104
|
+
children: "Endpoints"
|
|
105
|
+
}),
|
|
106
|
+
/*#__PURE__*/ _jsx("p", {
|
|
107
|
+
className: theme.header.subtitle,
|
|
108
|
+
children: list.total === 1 ? '1 endpoint' : `${list.total} endpoints`
|
|
109
|
+
})
|
|
110
|
+
]
|
|
111
|
+
}),
|
|
112
|
+
/*#__PURE__*/ _jsx("div", {
|
|
113
|
+
className: theme.header.actions,
|
|
114
|
+
children: /*#__PURE__*/ _jsx("button", {
|
|
115
|
+
className: theme.button.primary,
|
|
116
|
+
onClick: handleNew,
|
|
117
|
+
type: "button",
|
|
118
|
+
children: "New endpoint"
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
}),
|
|
123
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
124
|
+
htmlFor: "endpoint-search",
|
|
125
|
+
label: "Search",
|
|
126
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
127
|
+
className: theme.field.input,
|
|
128
|
+
id: "endpoint-search",
|
|
129
|
+
onChange: (event)=>setTerm(event.target.value),
|
|
130
|
+
placeholder: "Tag, name or path",
|
|
131
|
+
type: "search",
|
|
132
|
+
value: term
|
|
133
|
+
})
|
|
134
|
+
}),
|
|
135
|
+
firstLoad ? /*#__PURE__*/ _jsx(ListSkeleton, {
|
|
136
|
+
rows: 6
|
|
137
|
+
}) : null,
|
|
138
|
+
list.error ? /*#__PURE__*/ _jsxs(Notice, {
|
|
139
|
+
title: "This list could not be loaded",
|
|
140
|
+
tone: "danger",
|
|
141
|
+
children: [
|
|
142
|
+
list.error,
|
|
143
|
+
' ',
|
|
144
|
+
/*#__PURE__*/ _jsx("button", {
|
|
145
|
+
className: theme.button.ghost,
|
|
146
|
+
onClick: list.reload,
|
|
147
|
+
type: "button",
|
|
148
|
+
children: "Try again"
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
}) : null,
|
|
152
|
+
!firstLoad && list.items.length === 0 && !list.error ? /*#__PURE__*/ _jsx("p", {
|
|
153
|
+
className: theme.list.empty,
|
|
154
|
+
children: search.length > 0 ? `No endpoint matches “${search}”. Clear the search to see them all.` : 'No endpoints yet. An endpoint is one call this connection can make — a method, a path, and where the records sit in the answer. Start with New endpoint.'
|
|
155
|
+
}) : null,
|
|
156
|
+
list.items.length > 0 ? /*#__PURE__*/ _jsx("ul", {
|
|
157
|
+
className: theme.list.wrapper,
|
|
158
|
+
children: list.items.map((endpoint)=>/*#__PURE__*/ _jsx("li", {
|
|
159
|
+
children: /*#__PURE__*/ _jsxs("button", {
|
|
160
|
+
"aria-current": endpoint.id === selectedId ? 'true' : undefined,
|
|
161
|
+
className: cn(theme.list.item, endpoint.id === selectedId && theme.list.itemSelected),
|
|
162
|
+
onClick: ()=>handleSelect(endpoint),
|
|
163
|
+
type: "button",
|
|
164
|
+
children: [
|
|
165
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
166
|
+
className: "flex w-full flex-wrap items-center gap-1.5",
|
|
167
|
+
children: [
|
|
168
|
+
/*#__PURE__*/ _jsx(Badge, {
|
|
169
|
+
tone: METHOD_TONE[endpoint.method],
|
|
170
|
+
children: endpoint.method
|
|
171
|
+
}),
|
|
172
|
+
/*#__PURE__*/ _jsx(Badge, {
|
|
173
|
+
tone: "neutral",
|
|
174
|
+
children: endpoint.tag
|
|
175
|
+
}),
|
|
176
|
+
endpoint.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
|
|
177
|
+
tone: "neutral",
|
|
178
|
+
children: "Off"
|
|
179
|
+
}) : null
|
|
180
|
+
]
|
|
181
|
+
}),
|
|
182
|
+
/*#__PURE__*/ _jsx("span", {
|
|
183
|
+
className: theme.list.itemTitle,
|
|
184
|
+
children: endpoint.name ?? endpoint.path
|
|
185
|
+
}),
|
|
186
|
+
/*#__PURE__*/ _jsx("span", {
|
|
187
|
+
className: theme.list.itemMeta,
|
|
188
|
+
children: endpoint.path
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
})
|
|
192
|
+
}, endpoint.id))
|
|
193
|
+
}) : null,
|
|
194
|
+
/*#__PURE__*/ _jsx("div", {
|
|
195
|
+
"aria-hidden": "true",
|
|
196
|
+
className: theme.list.sentinel,
|
|
197
|
+
ref: list.sentinelRef
|
|
198
|
+
}),
|
|
199
|
+
list.loading && list.items.length > 0 ? /*#__PURE__*/ _jsx("p", {
|
|
200
|
+
className: theme.field.hint,
|
|
201
|
+
children: "Loading more…"
|
|
202
|
+
}) : null
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IntegrationEndpoint, IntegrationsActions } from '../types';
|
|
2
|
+
export type EndpointSampleProps = {
|
|
3
|
+
endpoint: IntegrationEndpoint;
|
|
4
|
+
actions: IntegrationsActions;
|
|
5
|
+
};
|
|
6
|
+
export declare function EndpointSample({ endpoint, actions }: EndpointSampleProps): import("react/jsx-runtime").JSX.Element;
|