nucleus-core-ts 0.10.147 → 0.10.151
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/LICENSE +48 -48
- package/dist/.build-ok +1 -1
- package/dist/fe/components/AuthorizationPage/labels.js +13 -13
- package/dist/fe/components/Captcha/labels.js +17 -17
- package/dist/fe/components/ChatPanel/helpers.js +17 -17
- package/dist/fe/components/ChatPanel/labels.js +16 -16
- package/dist/fe/components/DataTable/labels.js +16 -16
- package/dist/fe/components/DevicesPage/components/DeviceCard.js +15 -15
- package/dist/fe/components/DevicesPage/components/DevicesPage.js +9 -9
- package/dist/fe/components/DevicesPage/labels.js +12 -12
- package/dist/fe/components/ForgotPasswordPage/labels.js +8 -8
- package/dist/fe/components/FormBuilder/components/FormField.js +5 -5
- package/dist/fe/components/FormBuilder/utils/fieldMapping.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +8 -8
- package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +3 -3
- package/dist/fe/components/IntegrationsPage/components/ConnectionExport.js +23 -23
- package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +22 -22
- package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +24 -24
- package/dist/fe/components/IntegrationsPage/components/ConnectionSwitcher.js +19 -19
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +21 -21
- package/dist/fe/components/IntegrationsPage/components/ConnectionTransfer.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +19 -19
- package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +8 -8
- package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +12 -12
- package/dist/fe/components/IntegrationsPage/components/EndpointList.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +36 -36
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +29 -29
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +60 -60
- package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/MappingList.js +23 -23
- package/dist/fe/components/IntegrationsPage/components/MappingPasswordField.js +12 -12
- package/dist/fe/components/IntegrationsPage/components/MappingScheduleFields.js +23 -23
- package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +31 -31
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +56 -56
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +11 -11
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/Primitives.js +28 -28
- package/dist/fe/components/IntegrationsPage/components/ReferenceCheckRows.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunHistory.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +40 -40
- package/dist/fe/components/IntegrationsPage/components/RunPasswordField.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunPlanReferences.js +12 -12
- package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.js +11 -11
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/Skeletons.js +7 -7
- package/dist/fe/components/IntegrationsPage/components/authOptions.js +4 -4
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +27 -27
- package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +25 -25
- package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.js +9 -9
- package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +4 -4
- package/dist/fe/components/IntegrationsPage/components/formSupport.js +34 -34
- package/dist/fe/components/IntegrationsPage/components/jsonArray.js +14 -14
- package/dist/fe/components/IntegrationsPage/components/jsonObjectField.js +15 -15
- package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +10 -10
- package/dist/fe/components/IntegrationsPage/components/mappingStaleness.js +22 -22
- package/dist/fe/components/IntegrationsPage/components/nextRuns.js +8 -8
- package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.js +13 -13
- package/dist/fe/components/IntegrationsPage/components/pathParams.js +6 -6
- package/dist/fe/components/IntegrationsPage/components/runFormat.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/settleOwnership.js +18 -18
- package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +8 -8
- package/dist/fe/components/IntegrationsPage/store/index.js +20 -20
- package/dist/fe/components/IntegrationsPage/store/upsertInto.js +12 -12
- package/dist/fe/components/IntegrationsPage/text/context.js +6 -6
- package/dist/fe/components/IntegrationsPage/text/index.js +18 -18
- package/dist/fe/components/IntegrationsPage/theme/index.js +29 -29
- package/dist/fe/components/IntegrationsPage/types/records.js +10 -10
- package/dist/fe/components/IntegrationsPage/types/results.js +3 -3
- package/dist/fe/components/NotificationCenter/labels.js +17 -17
- package/dist/fe/components/ProfilePage/labels.js +10 -10
- package/dist/fe/components/ResetPasswordPage/labels.js +4 -4
- package/dist/fe/components/SetPasswordPage/labels.js +11 -11
- package/dist/fe/components/UsersPage/labels.js +14 -14
- package/dist/fe/utils/columnUtils.js +24 -24
- package/dist/fe/utils/searchFold.js +36 -36
- package/dist/index.js +6 -6
- package/dist/server.js +6 -6
- package/dist/src/Client/Proxy/authz.js +4 -4
- package/dist/src/Client/Proxy/httpProxy.js +136 -136
- package/dist/src/Client/Proxy/largeBody.js +29 -29
- package/dist/src/Client/Proxy/utils.js +32 -32
- package/dist/src/Client/Proxy/wsProxy.js +59 -59
- package/dist/src/Managers/Redis/index.d.ts +14 -0
- package/dist/src/Nucleus/routes/auth/admin/impersonationStore.d.ts +3 -4
- package/dist/src/Nucleus/routes/auth/refresh/issuedPair.d.ts +67 -0
- package/dist/src/Nucleus/routes/auth/refresh/recordIssued.d.ts +15 -6
- package/dist/src/Nucleus/routes/auth/refresh/refreshChain.d.ts +56 -0
- package/dist/src/Services/Auth/SessionStore/utils.d.ts +2 -0
- package/dist/src/Shared/cron.js +71 -71
- package/dist/src/Shared/redactSecrets.js +156 -156
- package/dist/src/Shared/redaction/bare.js +61 -61
- package/dist/src/Shared/redaction/floor.js +48 -48
- package/dist/src/Shared/redaction/freeText.js +70 -70
- package/dist/src/Shared/redaction/json.js +30 -30
- package/dist/src/Shared/redaction/lists.js +23 -23
- package/dist/src/Shared/redaction/markers.js +28 -28
- package/dist/src/Shared/redaction/names.js +49 -49
- package/dist/src/Shared/redaction/pairs.js +54 -54
- package/dist/src/Shared/redaction/secretString.js +29 -29
- package/dist/src/Shared/redaction/structured.js +52 -52
- package/dist/src/Shared/redaction/walk.js +25 -25
- package/infra/scripts/generate-project.ts +766 -766
- package/infra/templates/README.md +57 -57
- package/infra/templates/backend/.dockerignore +9 -9
- package/infra/templates/backend/.env.example +15 -15
- package/infra/templates/backend/README.md +28 -28
- package/infra/templates/backend/azure-pipelines.yml +135 -135
- package/infra/templates/backend/k8s/Dockerfile +31 -31
- package/infra/templates/backend/k8s/configmap.yaml +26 -26
- package/infra/templates/backend/k8s/deployment.yaml +85 -85
- package/infra/templates/backend/k8s/pvc.yaml +12 -12
- package/infra/templates/backend/k8s/service.yaml +13 -13
- package/infra/templates/backend/tsconfig.json +22 -22
- package/infra/templates/frontend/.dockerignore +10 -10
- package/infra/templates/frontend/.env.example +8 -8
- package/infra/templates/frontend/.env.local.example +10 -10
- package/infra/templates/frontend/README.md +69 -69
- package/infra/templates/frontend/app/[tablename]/page.tsx +37 -37
- package/infra/templates/frontend/app/_components/AuthWrapper/index.tsx +63 -63
- package/infra/templates/frontend/app/authorization/page.tsx +43 -43
- package/infra/templates/frontend/app/change-password/page.tsx +23 -23
- package/infra/templates/frontend/app/devices/page.tsx +127 -127
- package/infra/templates/frontend/app/forgot-password/page.tsx +20 -20
- package/infra/templates/frontend/app/globals.css +28 -28
- package/infra/templates/frontend/app/layout.tsx +35 -35
- package/infra/templates/frontend/app/login/page.tsx +33 -33
- package/infra/templates/frontend/app/page.tsx +565 -565
- package/infra/templates/frontend/app/profile/page.tsx +92 -92
- package/infra/templates/frontend/app/register/page.tsx +27 -27
- package/infra/templates/frontend/app/reset-password/page.tsx +60 -60
- package/infra/templates/frontend/app/set-password/page.tsx +43 -43
- package/infra/templates/frontend/app/ui-docs/page.tsx +52 -52
- package/infra/templates/frontend/app/users/page.tsx +52 -52
- package/infra/templates/frontend/app/verify-email/page.tsx +41 -41
- package/infra/templates/frontend/app/verify-magic-link/page.tsx +46 -46
- package/infra/templates/frontend/azure-pipelines.yml +137 -137
- package/infra/templates/frontend/code-and-design-principles.md +438 -438
- package/infra/templates/frontend/k8s/Dockerfile +50 -50
- package/infra/templates/frontend/k8s/configmap.yaml +18 -18
- package/infra/templates/frontend/k8s/deployment.yaml +72 -72
- package/infra/templates/frontend/k8s/ingress.yaml +36 -36
- package/infra/templates/frontend/k8s/service.yaml +14 -14
- package/infra/templates/frontend/lib/api/endpoints.ts +37 -37
- package/infra/templates/frontend/lib/api/factory.ts +44 -44
- package/infra/templates/frontend/lib/api/hook.ts +10 -10
- package/infra/templates/frontend/next.config.ts +19 -19
- package/infra/templates/frontend/package.json +34 -34
- package/infra/templates/frontend/postcss.config.mjs +5 -5
- package/infra/templates/frontend/server.ts +114 -114
- package/infra/templates/frontend/tsconfig.json +23 -23
- package/package.json +1 -1
- package/public/components/CTA.tsx +25 -25
- package/public/components/Capabilities.tsx +67 -67
- package/public/components/Features.tsx +107 -107
- package/public/components/Footer.tsx +58 -58
- package/public/components/Hero.tsx +51 -51
- package/public/components/Managers.tsx +55 -55
- package/public/components/Nav.tsx +91 -91
- package/public/components/Stack.tsx +36 -36
- package/public/components/docs/Docs.tsx +60 -60
- package/public/components/docs/DocsContent.tsx +176 -176
- package/public/components/docs/DocsRightSidebar.tsx +53 -53
- package/public/components/docs/DocsSidebar.tsx +54 -54
- package/public/components/docs/content/authentication.ts +836 -836
- package/public/components/docs/content/authorization.ts +268 -268
- package/public/components/docs/content/backupRestore.ts +173 -173
- package/public/components/docs/content/clientModule.ts +287 -287
- package/public/components/docs/content/entityCrud.ts +682 -682
- package/public/components/docs/content/monitoring.ts +313 -313
- package/public/components/docs/content/multiTenant.ts +269 -269
- package/public/components/docs/content/oauthApiKeys.ts +325 -325
- package/public/components/docs/content/pubsub.ts +131 -131
- package/public/components/docs/content/storageCdn.ts +171 -171
- package/public/components/docs/content/verification.ts +289 -289
- package/public/components/docs/docsData.ts +113 -113
- package/public/components/docs/index.ts +6 -6
- package/public/components/index.ts +9 -9
- package/public/docs.css +629 -629
- package/public/index.html +81 -81
- package/public/index.tsx +42 -42
- package/public/store.ts +76 -76
- package/public/styles.css +858 -858
- package/schemas/config.nucleus.json +2651 -2651
- package/scripts/audit-purge-noise.ts +120 -120
- package/scripts/build.ts +582 -582
- package/scripts/generate-system-tables-types.ts +18 -18
- package/scripts/generate-types-schema.test.ts +73 -73
- package/scripts/generate-types-schema.ts +1284 -1284
- package/scripts/packagedScripts.test.ts +87 -87
- package/scripts/publish.ts +276 -276
- package/scripts/skillCitations.test.ts +107 -107
- package/scripts/verify-publishable.ts +40 -40
|
@@ -6,12 +6,12 @@ import { clientAuthOptions, grantTypeOptions, toClientAuth, toGrantType } from '
|
|
|
6
6
|
import { usesOauth } from './connectionDraft';
|
|
7
7
|
import { describedBy } from './formSupport';
|
|
8
8
|
import { Field } from './Primitives';
|
|
9
|
-
/**
|
|
10
|
-
* The OAuth 2 half of the auth section.
|
|
11
|
-
*
|
|
12
|
-
* It renders nothing at all for the other auth types, so the caller can place it
|
|
13
|
-
* unconditionally and the question "is this connection OAuth" is answered in one
|
|
14
|
-
* place instead of at every field.
|
|
9
|
+
/**
|
|
10
|
+
* The OAuth 2 half of the auth section.
|
|
11
|
+
*
|
|
12
|
+
* It renders nothing at all for the other auth types, so the caller can place it
|
|
13
|
+
* unconditionally and the question "is this connection OAuth" is answered in one
|
|
14
|
+
* place instead of at every field.
|
|
15
15
|
*/ const theme = integrationsPageTheme;
|
|
16
16
|
export function ConnectionOauthFields({ draft, errors, disabled, onChange }) {
|
|
17
17
|
const labels = useLabels();
|
|
@@ -7,33 +7,33 @@ import { ConnectionExport } from './ConnectionExport';
|
|
|
7
7
|
import { ConnectionForm } from './ConnectionForm';
|
|
8
8
|
import { ConnectionTest } from './ConnectionTest';
|
|
9
9
|
import { CredentialFields } from './CredentialFields';
|
|
10
|
-
/**
|
|
11
|
-
* Everything about the connection itself: its address and authentication, its
|
|
12
|
-
* credentials, and whether it answers.
|
|
13
|
-
*
|
|
14
|
-
* These three were written and then never reachable — the shell mounted the
|
|
15
|
-
* connection list and the three child tabs and nothing else, so the panel could
|
|
16
|
-
* show you connections but gave you no way to create or edit one. That is the
|
|
17
|
-
* first screen an operator needs and it was the one screen missing.
|
|
18
|
-
*
|
|
19
|
-
* Credentials sit between the form and the test on purpose: an operator fills in
|
|
20
|
-
* the address, enters the secret, then presses test. Putting the test above the
|
|
21
|
-
* credential boxes invites pressing it before there is anything to authenticate
|
|
22
|
-
* with, and reading the failure as "the address is wrong".
|
|
10
|
+
/**
|
|
11
|
+
* Everything about the connection itself: its address and authentication, its
|
|
12
|
+
* credentials, and whether it answers.
|
|
13
|
+
*
|
|
14
|
+
* These three were written and then never reachable — the shell mounted the
|
|
15
|
+
* connection list and the three child tabs and nothing else, so the panel could
|
|
16
|
+
* show you connections but gave you no way to create or edit one. That is the
|
|
17
|
+
* first screen an operator needs and it was the one screen missing.
|
|
18
|
+
*
|
|
19
|
+
* Credentials sit between the form and the test on purpose: an operator fills in
|
|
20
|
+
* the address, enters the secret, then presses test. Putting the test above the
|
|
21
|
+
* credential boxes invites pressing it before there is anything to authenticate
|
|
22
|
+
* with, and reading the failure as "the address is wrong".
|
|
23
23
|
*/ export function ConnectionSettingsTab({ actions, onSaved, onDeleted }) {
|
|
24
24
|
const store = useIntegrationsStore();
|
|
25
25
|
const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId) ?? null;
|
|
26
|
-
/**
|
|
27
|
-
* Waiting for THIS record, and nothing else.
|
|
28
|
-
*
|
|
29
|
-
* It used to be `store.sourcesLoading || missingFromPage`, and sourcesLoading
|
|
30
|
-
* is true whenever the rail is fetching ANY page — including a page fetched
|
|
31
|
-
* because somebody typed a letter into the search box. With no connection
|
|
32
|
-
* chosen this tab is the blank "new connection" form, and every keystroke in
|
|
33
|
-
* the rail beside it replaced that form with skeletons.
|
|
34
|
-
*
|
|
35
|
-
* A form that is not editing a record is never loading: there is nothing to
|
|
36
|
-
* wait for. A form that IS editing one waits only until that record is here.
|
|
26
|
+
/**
|
|
27
|
+
* Waiting for THIS record, and nothing else.
|
|
28
|
+
*
|
|
29
|
+
* It used to be `store.sourcesLoading || missingFromPage`, and sourcesLoading
|
|
30
|
+
* is true whenever the rail is fetching ANY page — including a page fetched
|
|
31
|
+
* because somebody typed a letter into the search box. With no connection
|
|
32
|
+
* chosen this tab is the blank "new connection" form, and every keystroke in
|
|
33
|
+
* the rail beside it replaced that form with skeletons.
|
|
34
|
+
*
|
|
35
|
+
* A form that is not editing a record is never loading: there is nothing to
|
|
36
|
+
* wait for. A form that IS editing one waits only until that record is here.
|
|
37
37
|
*/ const waitingForRecord = store.selectedSourceId !== null && source === null;
|
|
38
38
|
return /*#__PURE__*/ _jsxs("div", {
|
|
39
39
|
className: theme.layout.main,
|
|
@@ -4,28 +4,28 @@ import { cn } from '../../../utils/cn';
|
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
5
|
import { useLabels } from '../text/context';
|
|
6
6
|
import { integrationsPageTheme as theme } from '../theme';
|
|
7
|
-
/**
|
|
8
|
-
* The chosen connection, as one line instead of a column.
|
|
9
|
-
*
|
|
10
|
-
* The panel used to keep the connection list as a permanent rail, so the work
|
|
11
|
-
* happened in what was left — and the work is itself a list beside an editor.
|
|
12
|
-
* Three columns inside an admin page that already has a nav is how a form ends
|
|
13
|
-
* up past the right edge of the screen with the page scrolling sideways.
|
|
14
|
-
*
|
|
15
|
-
* A connection is CHOSEN once and then worked in. Once it is chosen the rail
|
|
16
|
-
* has nothing left to say that this line does not, so the rail goes and the
|
|
17
|
-
* whole width goes to the step.
|
|
7
|
+
/**
|
|
8
|
+
* The chosen connection, as one line instead of a column.
|
|
9
|
+
*
|
|
10
|
+
* The panel used to keep the connection list as a permanent rail, so the work
|
|
11
|
+
* happened in what was left — and the work is itself a list beside an editor.
|
|
12
|
+
* Three columns inside an admin page that already has a nav is how a form ends
|
|
13
|
+
* up past the right edge of the screen with the page scrolling sideways.
|
|
14
|
+
*
|
|
15
|
+
* A connection is CHOSEN once and then worked in. Once it is chosen the rail
|
|
16
|
+
* has nothing left to say that this line does not, so the rail goes and the
|
|
17
|
+
* whole width goes to the step.
|
|
18
18
|
*/ export function ConnectionSwitcher() {
|
|
19
19
|
const store = useIntegrationsStore();
|
|
20
20
|
const labels = useLabels();
|
|
21
|
-
/**
|
|
22
|
-
* The way back does not depend on finding anything.
|
|
23
|
-
*
|
|
24
|
-
* `store.sources` is the rail's CURRENT PAGE, and the rail is unmounted the
|
|
25
|
-
* moment a connection is chosen — so a selected connection that is not on
|
|
26
|
-
* that page rendered nothing at all here: no name, and no way back to the
|
|
27
|
-
* list short of reloading the page. The name is what needs looking up; the
|
|
28
|
-
* button is not.
|
|
21
|
+
/**
|
|
22
|
+
* The way back does not depend on finding anything.
|
|
23
|
+
*
|
|
24
|
+
* `store.sources` is the rail's CURRENT PAGE, and the rail is unmounted the
|
|
25
|
+
* moment a connection is chosen — so a selected connection that is not on
|
|
26
|
+
* that page rendered nothing at all here: no name, and no way back to the
|
|
27
|
+
* list short of reloading the page. The name is what needs looking up; the
|
|
28
|
+
* button is not.
|
|
29
29
|
*/ if (!store.selectedSourceId) return null;
|
|
30
30
|
const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId);
|
|
31
31
|
return /*#__PURE__*/ _jsxs("div", {
|
|
@@ -4,21 +4,21 @@ import { useRef } from 'react';
|
|
|
4
4
|
import { cn } from '../../../utils/cn';
|
|
5
5
|
import { useLabels } from '../text/context';
|
|
6
6
|
import { integrationsPageTheme } from '../theme';
|
|
7
|
-
/**
|
|
8
|
-
* The four phases of an import, in the order they happen.
|
|
9
|
-
*
|
|
10
|
-
* These were four flat tabs — Connection, Endpoints, Mappings, Runs — sitting
|
|
11
|
-
* side by side with nothing to say that they are a SEQUENCE, that a mapping is
|
|
12
|
-
* meaningless before a call has been tried, or which of them the operator had
|
|
13
|
-
* already finished. People opened the middle one first and met a dead end.
|
|
14
|
-
*
|
|
15
|
-
* So each is a step: a number, its name, one sentence saying what it is for, and
|
|
16
|
-
* a line of evidence once it is done. What is on screen now answers "where am I,
|
|
17
|
-
* what is this for, and what have I already got".
|
|
18
|
-
*
|
|
19
|
-
* It remains a real ARIA tablist — one stop in the tab order, arrow keys between
|
|
20
|
-
* the steps. A row of separately tabbable cards would make a keyboard user press
|
|
21
|
-
* Tab four times to leave a strip they were never inside.
|
|
7
|
+
/**
|
|
8
|
+
* The four phases of an import, in the order they happen.
|
|
9
|
+
*
|
|
10
|
+
* These were four flat tabs — Connection, Endpoints, Mappings, Runs — sitting
|
|
11
|
+
* side by side with nothing to say that they are a SEQUENCE, that a mapping is
|
|
12
|
+
* meaningless before a call has been tried, or which of them the operator had
|
|
13
|
+
* already finished. People opened the middle one first and met a dead end.
|
|
14
|
+
*
|
|
15
|
+
* So each is a step: a number, its name, one sentence saying what it is for, and
|
|
16
|
+
* a line of evidence once it is done. What is on screen now answers "where am I,
|
|
17
|
+
* what is this for, and what have I already got".
|
|
18
|
+
*
|
|
19
|
+
* It remains a real ARIA tablist — one stop in the tab order, arrow keys between
|
|
20
|
+
* the steps. A row of separately tabbable cards would make a keyboard user press
|
|
21
|
+
* Tab four times to leave a strip they were never inside.
|
|
22
22
|
*/ const theme = integrationsPageTheme;
|
|
23
23
|
const ORDER = [
|
|
24
24
|
'connection',
|
|
@@ -7,13 +7,13 @@ import { integrationsPageTheme } from '../theme';
|
|
|
7
7
|
import { errorText } from './formSupport';
|
|
8
8
|
import { Badge, Notice } from './Primitives';
|
|
9
9
|
import { CardSkeleton } from './Skeletons';
|
|
10
|
-
/**
|
|
11
|
-
* One real call to the service, reported exactly as it came back.
|
|
12
|
-
*
|
|
13
|
-
* The failure text is printed whole. The server already names the setting that
|
|
14
|
-
* has to change — a timeout, a certificate it would not accept, a 401 from the
|
|
15
|
-
* token endpoint — and every word of that is worth more than a tidy summary that
|
|
16
|
-
* sends the operator back to guessing.
|
|
10
|
+
/**
|
|
11
|
+
* One real call to the service, reported exactly as it came back.
|
|
12
|
+
*
|
|
13
|
+
* The failure text is printed whole. The server already names the setting that
|
|
14
|
+
* has to change — a timeout, a certificate it would not accept, a 401 from the
|
|
15
|
+
* token endpoint — and every word of that is worth more than a tidy summary that
|
|
16
|
+
* sends the operator back to guessing.
|
|
17
17
|
*/ const theme = integrationsPageTheme;
|
|
18
18
|
export function ConnectionTest({ actions, source, loading = false }) {
|
|
19
19
|
const t = useLabels().errors;
|
|
@@ -26,20 +26,20 @@ export function ConnectionTest({ actions, source, loading = false }) {
|
|
|
26
26
|
setResult(null);
|
|
27
27
|
setTesting(false);
|
|
28
28
|
});
|
|
29
|
-
/**
|
|
30
|
-
* A verdict describes the settings it was given, not the connection's name.
|
|
31
|
-
*
|
|
32
|
-
* Keyed on the id alone, "Reachable" survived editing the base URL and saving:
|
|
33
|
-
* the record object is replaced but its id is not, so this never re-ran and a
|
|
34
|
-
* green badge sat under an address that had never been called. Everything the
|
|
35
|
-
* test actually depends on is in the key.
|
|
36
|
-
*
|
|
37
|
-
* A stored CREDENTIAL is such a setting, and it was the one left out. Clearing
|
|
38
|
-
* a token left the green verdict standing for a connection that would now be
|
|
39
|
-
* refused; the mirror image costs more of an afternoon — a red "Not reachable"
|
|
40
|
-
* from a 401 stayed up after the right password was saved, reading as "the
|
|
41
|
-
* password did not help". The masked hints are what move when a credential
|
|
42
|
-
* does, so they are what this watches.
|
|
29
|
+
/**
|
|
30
|
+
* A verdict describes the settings it was given, not the connection's name.
|
|
31
|
+
*
|
|
32
|
+
* Keyed on the id alone, "Reachable" survived editing the base URL and saving:
|
|
33
|
+
* the record object is replaced but its id is not, so this never re-ran and a
|
|
34
|
+
* green badge sat under an address that had never been called. Everything the
|
|
35
|
+
* test actually depends on is in the key.
|
|
36
|
+
*
|
|
37
|
+
* A stored CREDENTIAL is such a setting, and it was the one left out. Clearing
|
|
38
|
+
* a token left the green verdict standing for a connection that would now be
|
|
39
|
+
* refused; the mirror image costs more of an afternoon — a red "Not reachable"
|
|
40
|
+
* from a 401 stayed up after the right password was saved, reading as "the
|
|
41
|
+
* password did not help". The masked hints are what move when a credential
|
|
42
|
+
* does, so they are what this watches.
|
|
43
43
|
*/ useEffect(()=>{
|
|
44
44
|
clear();
|
|
45
45
|
}, [
|
|
@@ -6,24 +6,24 @@ import { useIntegrationsStore } from '../store';
|
|
|
6
6
|
import { useLabels } from '../text/context';
|
|
7
7
|
import { integrationsPageTheme } from '../theme';
|
|
8
8
|
import { Notice } from './Primitives';
|
|
9
|
-
/**
|
|
10
|
-
* Bringing a whole integration in from another installation.
|
|
11
|
-
*
|
|
12
|
-
* A connection is the connection, the calls it makes, the parameters those calls
|
|
13
|
-
* take from each other, and the mappings that turn the answers into rows.
|
|
14
|
-
* Rebuilding that by hand in a second environment is hours of work and a fresh
|
|
15
|
-
* chance to get one field wrong in a way nobody notices until an import writes
|
|
16
|
-
* it. So it travels as one file.
|
|
17
|
-
*
|
|
18
|
-
* The file carries NO credentials — deliberately, and it says so on both sides,
|
|
19
|
-
* because a connection that arrives looking configured and cannot authenticate
|
|
20
|
-
* is a confusing half hour otherwise.
|
|
21
|
-
*
|
|
22
|
-
* Export used to sit beside this button and could never be pressed: it needs a
|
|
23
|
-
* connection to be CHOSEN, and choosing one replaces this rail with that
|
|
24
|
-
* connection's own screens. It now lives there, on the thing being exported —
|
|
25
|
-
* see ConnectionExport. Importing is the one that belongs here, because
|
|
26
|
-
* importing is what you do when you have nothing chosen yet.
|
|
9
|
+
/**
|
|
10
|
+
* Bringing a whole integration in from another installation.
|
|
11
|
+
*
|
|
12
|
+
* A connection is the connection, the calls it makes, the parameters those calls
|
|
13
|
+
* take from each other, and the mappings that turn the answers into rows.
|
|
14
|
+
* Rebuilding that by hand in a second environment is hours of work and a fresh
|
|
15
|
+
* chance to get one field wrong in a way nobody notices until an import writes
|
|
16
|
+
* it. So it travels as one file.
|
|
17
|
+
*
|
|
18
|
+
* The file carries NO credentials — deliberately, and it says so on both sides,
|
|
19
|
+
* because a connection that arrives looking configured and cannot authenticate
|
|
20
|
+
* is a confusing half hour otherwise.
|
|
21
|
+
*
|
|
22
|
+
* Export used to sit beside this button and could never be pressed: it needs a
|
|
23
|
+
* connection to be CHOSEN, and choosing one replaces this rail with that
|
|
24
|
+
* connection's own screens. It now lives there, on the thing being exported —
|
|
25
|
+
* see ConnectionExport. Importing is the one that belongs here, because
|
|
26
|
+
* importing is what you do when you have nothing chosen yet.
|
|
27
27
|
*/ const theme = integrationsPageTheme;
|
|
28
28
|
const messageOf = (error, fallback)=>error instanceof Error ? error.message : fallback;
|
|
29
29
|
export function ConnectionTransfer({ actions, onImported }) {
|
|
@@ -9,14 +9,14 @@ import { hintColumnsFor, hintFromResult, patchFor, slotsForSource, storedHint }
|
|
|
9
9
|
import { errorText } from './formSupport';
|
|
10
10
|
import { Notice } from './Primitives';
|
|
11
11
|
import { CardSkeleton } from './Skeletons';
|
|
12
|
-
/**
|
|
13
|
-
* The secrets, written and never read.
|
|
14
|
-
*
|
|
15
|
-
* Nothing on this screen has ever seen a stored value and nothing here asks for
|
|
16
|
-
* one: the record carries a masked hint, a save returns a new masked hint, and
|
|
17
|
-
* that is the whole conversation. An empty box therefore cannot mean "make it
|
|
18
|
-
* empty" — it means "leave it alone" — which is why clearing is a separate
|
|
19
|
-
* button per field.
|
|
12
|
+
/**
|
|
13
|
+
* The secrets, written and never read.
|
|
14
|
+
*
|
|
15
|
+
* Nothing on this screen has ever seen a stored value and nothing here asks for
|
|
16
|
+
* one: the record carries a masked hint, a save returns a new masked hint, and
|
|
17
|
+
* that is the whole conversation. An empty box therefore cannot mean "make it
|
|
18
|
+
* empty" — it means "leave it alone" — which is why clearing is a separate
|
|
19
|
+
* button per field.
|
|
20
20
|
*/ const theme = integrationsPageTheme;
|
|
21
21
|
export function CredentialFields({ actions, source, loading = false }) {
|
|
22
22
|
const t = useLabels().errors;
|
|
@@ -52,17 +52,17 @@ export function CredentialFields({ actions, source, loading = false }) {
|
|
|
52
52
|
const sourceRecord = source;
|
|
53
53
|
const slots = slotsForSource(sourceRecord);
|
|
54
54
|
const hintOf = (slot)=>slot in hints ? hints[slot] ?? null : storedHint(sourceRecord, slot);
|
|
55
|
-
/**
|
|
56
|
-
* The new masks, into local state AND into the record everything else reads.
|
|
57
|
-
*
|
|
58
|
-
* Local state alone is not enough, and the way it failed is worth keeping:
|
|
59
|
-
* `hintOf` falls back to `storedHint(sourceRecord, …)` for any slot local
|
|
60
|
-
* state has nothing for, and local state is cleared whenever the chosen
|
|
61
|
-
* connection changes. So a token could be saved, show its green "Stored"
|
|
62
|
-
* badge, and revert to "Not set" the moment the operator visited another tab
|
|
63
|
-
* and came back — because the store's copy of the connection still carried
|
|
64
|
-
* the pre-save hints. The value was in the database throughout; only the
|
|
65
|
-
* record in memory was old.
|
|
55
|
+
/**
|
|
56
|
+
* The new masks, into local state AND into the record everything else reads.
|
|
57
|
+
*
|
|
58
|
+
* Local state alone is not enough, and the way it failed is worth keeping:
|
|
59
|
+
* `hintOf` falls back to `storedHint(sourceRecord, …)` for any slot local
|
|
60
|
+
* state has nothing for, and local state is cleared whenever the chosen
|
|
61
|
+
* connection changes. So a token could be saved, show its green "Stored"
|
|
62
|
+
* badge, and revert to "Not set" the moment the operator visited another tab
|
|
63
|
+
* and came back — because the store's copy of the connection still carried
|
|
64
|
+
* the pre-save hints. The value was in the database throughout; only the
|
|
65
|
+
* record in memory was old.
|
|
66
66
|
*/ const remember = (changed)=>{
|
|
67
67
|
if (Object.keys(changed).length === 0) return;
|
|
68
68
|
setHints((current)=>({
|
|
@@ -6,14 +6,14 @@ import { integrationsPageTheme } from '../theme';
|
|
|
6
6
|
import { slotHint, slotLabel } from './credentialSlots';
|
|
7
7
|
import { describedBy } from './formSupport';
|
|
8
8
|
import { Badge, Field } from './Primitives';
|
|
9
|
-
/**
|
|
10
|
-
* One secret: a box to type a new value into, the masked preview of whatever is
|
|
11
|
-
* already stored, and the only way to remove it.
|
|
12
|
-
*
|
|
13
|
-
* Clearing is its own button rather than "save an empty box", because an empty
|
|
14
|
-
* box is what an operator leaves behind when they came to change something else.
|
|
15
|
-
* Making that mean "delete the credential" would break a working connection by
|
|
16
|
-
* accident, roughly once.
|
|
9
|
+
/**
|
|
10
|
+
* One secret: a box to type a new value into, the masked preview of whatever is
|
|
11
|
+
* already stored, and the only way to remove it.
|
|
12
|
+
*
|
|
13
|
+
* Clearing is its own button rather than "save an empty box", because an empty
|
|
14
|
+
* box is what an operator leaves behind when they came to change something else.
|
|
15
|
+
* Making that mean "delete the credential" would break a working connection by
|
|
16
|
+
* accident, roughly once.
|
|
17
17
|
*/ const theme = integrationsPageTheme;
|
|
18
18
|
export function CredentialSlotRow({ slot, source, value, maskedHint, disabled, onChange, onClear }) {
|
|
19
19
|
const t = useLabels().errors;
|
|
@@ -10,13 +10,13 @@ import { formatJsonObject, parseJsonObject } from './jsonObjectField';
|
|
|
10
10
|
import { PaginationFields } from './PaginationFields';
|
|
11
11
|
import { ParamBindingRows } from './ParamBindingRows';
|
|
12
12
|
import { pathParamNames } from './pathParams';
|
|
13
|
-
/**
|
|
14
|
-
* One call against a connection: what to ask for, and where the records sit in
|
|
15
|
-
* the answer.
|
|
16
|
-
*
|
|
17
|
-
* The draft is seeded once. The parent remounts this form when the selection
|
|
18
|
-
* changes, so a half-typed path is never pulled out from under the operator by
|
|
19
|
-
* a list page arriving in the background.
|
|
13
|
+
/**
|
|
14
|
+
* One call against a connection: what to ask for, and where the records sit in
|
|
15
|
+
* the answer.
|
|
16
|
+
*
|
|
17
|
+
* The draft is seeded once. The parent remounts this form when the selection
|
|
18
|
+
* changes, so a half-typed path is never pulled out from under the operator by
|
|
19
|
+
* a list page arriving in the background.
|
|
20
20
|
*/ const theme = integrationsPageTheme;
|
|
21
21
|
export function EndpointForm({ sourceId, endpoint, actions, onSaved, onDeleted, onCancel }) {
|
|
22
22
|
const t = useLabels().errors;
|
|
@@ -6,11 +6,11 @@ import { useLabels } from '../text/context';
|
|
|
6
6
|
import { integrationsPageTheme } from '../theme';
|
|
7
7
|
import { describedBy } from './formSupport';
|
|
8
8
|
import { Field } from './Primitives';
|
|
9
|
-
/**
|
|
10
|
-
* The plain fields of an endpoint.
|
|
11
|
-
*
|
|
12
|
-
* Split out of the form because the form's job is saving and deleting, and this
|
|
13
|
-
* one's is asking. The draft type lives here, next to the controls that fill it.
|
|
9
|
+
/**
|
|
10
|
+
* The plain fields of an endpoint.
|
|
11
|
+
*
|
|
12
|
+
* Split out of the form because the form's job is saving and deleting, and this
|
|
13
|
+
* one's is asking. The draft type lives here, next to the controls that fill it.
|
|
14
14
|
*/ const theme = integrationsPageTheme;
|
|
15
15
|
const METHODS = [
|
|
16
16
|
'GET',
|
|
@@ -21,13 +21,13 @@ const METHODS = [
|
|
|
21
21
|
];
|
|
22
22
|
export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
23
23
|
const labels = useLabels();
|
|
24
|
-
/**
|
|
25
|
-
* Unique per instance.
|
|
26
|
-
*
|
|
27
|
-
* These ids were literals — `endpoint-tag`, `endpoint-name` — which is fine
|
|
28
|
-
* for one form on a page and wrong the moment there are two: every `<label
|
|
29
|
-
* htmlFor>` binds to whichever rendered first, so clicking a label focuses the
|
|
30
|
-
* OTHER record's field. `useId` makes that structurally impossible.
|
|
24
|
+
/**
|
|
25
|
+
* Unique per instance.
|
|
26
|
+
*
|
|
27
|
+
* These ids were literals — `endpoint-tag`, `endpoint-name` — which is fine
|
|
28
|
+
* for one form on a page and wrong the moment there are two: every `<label
|
|
29
|
+
* htmlFor>` binds to whichever rendered first, so clicking a label focuses the
|
|
30
|
+
* OTHER record's field. `useId` makes that structurally impossible.
|
|
31
31
|
*/ const uid = useId();
|
|
32
32
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
33
33
|
children: [
|
|
@@ -8,13 +8,13 @@ import { integrationsPageTheme } from '../theme';
|
|
|
8
8
|
import { Badge, Field, Notice } from './Primitives';
|
|
9
9
|
import { ListSkeleton } from './Skeletons';
|
|
10
10
|
import { useInfiniteList } from './useInfiniteList';
|
|
11
|
-
/**
|
|
12
|
-
* The calls one connection knows how to make.
|
|
13
|
-
*
|
|
14
|
-
* The server owns the list: the search term goes back out on the next request
|
|
15
|
-
* and the next page arrives when the sentinel scrolls into view. Nothing here
|
|
16
|
-
* narrows rows already fetched, so a connection with six endpoints and one with
|
|
17
|
-
* six hundred behave identically.
|
|
11
|
+
/**
|
|
12
|
+
* The calls one connection knows how to make.
|
|
13
|
+
*
|
|
14
|
+
* The server owns the list: the search term goes back out on the next request
|
|
15
|
+
* and the next page arrives when the sentinel scrolls into view. Nothing here
|
|
16
|
+
* narrows rows already fetched, so a connection with six endpoints and one with
|
|
17
|
+
* six hundred behave identically.
|
|
18
18
|
*/ const theme = integrationsPageTheme;
|
|
19
19
|
const PAGE_SIZE = 20;
|
|
20
20
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
@@ -8,13 +8,13 @@ import { Badge, CodeBlock, Field, Notice, Stat } from './Primitives';
|
|
|
8
8
|
import { pathParamNames } from './pathParams';
|
|
9
9
|
import { CardSkeleton, StatGridSkeleton } from './Skeletons';
|
|
10
10
|
import { settlementOf } from './settleOwnership';
|
|
11
|
-
/**
|
|
12
|
-
* One real request, shown in full.
|
|
13
|
-
*
|
|
14
|
-
* The interesting case is zero records, because that is when a configuration
|
|
15
|
-
* looks finished and imports nothing. The server already answers it — it returns
|
|
16
|
-
* the top-level keys of the body it actually received — so those keys are given
|
|
17
|
-
* their own block here rather than being folded into a generic "no data".
|
|
11
|
+
/**
|
|
12
|
+
* One real request, shown in full.
|
|
13
|
+
*
|
|
14
|
+
* The interesting case is zero records, because that is when a configuration
|
|
15
|
+
* looks finished and imports nothing. The server already answers it — it returns
|
|
16
|
+
* the top-level keys of the body it actually received — so those keys are given
|
|
17
|
+
* their own block here rather than being folded into a generic "no data".
|
|
18
18
|
*/ const theme = integrationsPageTheme;
|
|
19
19
|
export function EndpointSample({ endpoint, actions }) {
|
|
20
20
|
const t = useLabels().errors;
|
|
@@ -27,27 +27,27 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
27
27
|
const selectedRef = useRef(store.selectedEndpointId);
|
|
28
28
|
selectedRef.current = store.selectedEndpointId;
|
|
29
29
|
const params = pathParamNames(endpoint.path);
|
|
30
|
-
/**
|
|
31
|
-
* Only a sample taken of THIS call.
|
|
32
|
-
*
|
|
33
|
-
* The card is keyed per selection, so switching calls removes it and a request
|
|
34
|
-
* still in flight settles with nowhere to report to — except the shared slot,
|
|
35
|
-
* which it writes to regardless. Before the tag, that answer appeared under
|
|
36
|
-
* whichever call was open when it landed, and the mapping built next took its
|
|
37
|
-
* field list from a call that had never returned those fields.
|
|
30
|
+
/**
|
|
31
|
+
* Only a sample taken of THIS call.
|
|
32
|
+
*
|
|
33
|
+
* The card is keyed per selection, so switching calls removes it and a request
|
|
34
|
+
* still in flight settles with nowhere to report to — except the shared slot,
|
|
35
|
+
* which it writes to regardless. Before the tag, that answer appeared under
|
|
36
|
+
* whichever call was open when it landed, and the mapping built next took its
|
|
37
|
+
* field list from a call that had never returned those fields.
|
|
38
38
|
*/ const sample = store.sampleEndpointId === endpoint.id ? store.sample : null;
|
|
39
39
|
let unfilled = 0;
|
|
40
40
|
for (const name of params){
|
|
41
41
|
if (!(values[name] ?? '').trim()) unfilled += 1;
|
|
42
42
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Discard a late answer, but hand the busy flag back on the way out.
|
|
45
|
-
*
|
|
46
|
-
* The discard itself is right — the operator has moved on and the field list
|
|
47
|
-
* must describe the endpoint they are looking at. It used to be a bare
|
|
48
|
-
* `return`, which skipped the clearing too: `sampling` stayed set with nobody
|
|
49
|
-
* left to unset it, and the sample card showed a skeleton until the page was
|
|
50
|
-
* reloaded. See settleOwnership.ts for who owns the flag.
|
|
43
|
+
/**
|
|
44
|
+
* Discard a late answer, but hand the busy flag back on the way out.
|
|
45
|
+
*
|
|
46
|
+
* The discard itself is right — the operator has moved on and the field list
|
|
47
|
+
* must describe the endpoint they are looking at. It used to be a bare
|
|
48
|
+
* `return`, which skipped the clearing too: `sampling` stayed set with nobody
|
|
49
|
+
* left to unset it, and the sample card showed a skeleton until the page was
|
|
50
|
+
* reloaded. See settleOwnership.ts for who owns the flag.
|
|
51
51
|
*/ const settleElsewhere = (mine, requestedFor)=>{
|
|
52
52
|
const { discard, releaseFlag } = settlementOf({
|
|
53
53
|
mine,
|
|
@@ -70,19 +70,19 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
70
70
|
actions.sampleEndpoint(requestedFor, params.length > 0 ? values : undefined).then((result)=>{
|
|
71
71
|
if (settleElsewhere(mine, requestedFor)) return;
|
|
72
72
|
store.setSample(result, requestedFor);
|
|
73
|
-
/**
|
|
74
|
-
* The ROW learns it has been tried, not just this card.
|
|
75
|
-
*
|
|
76
|
-
* The step strip counts "tried" off `endpoints[].sampleResponse`,
|
|
77
|
-
* because whether a call has ever been run is a fact about the call and
|
|
78
|
-
* not about whichever screen is open. The server stores it there. This
|
|
79
|
-
* panel never did, so a connection whose calls had all been sampled
|
|
80
|
-
* went on announcing "7 call(s), 0 tried" — seen exactly that way on a
|
|
81
|
-
* customer's live connection, where it read as "nobody has ever tried
|
|
82
|
-
* these", which was not true.
|
|
83
|
-
*
|
|
84
|
-
* Only a sample that actually returned records counts, which is the
|
|
85
|
-
* same rule the strip applies.
|
|
73
|
+
/**
|
|
74
|
+
* The ROW learns it has been tried, not just this card.
|
|
75
|
+
*
|
|
76
|
+
* The step strip counts "tried" off `endpoints[].sampleResponse`,
|
|
77
|
+
* because whether a call has ever been run is a fact about the call and
|
|
78
|
+
* not about whichever screen is open. The server stores it there. This
|
|
79
|
+
* panel never did, so a connection whose calls had all been sampled
|
|
80
|
+
* went on announcing "7 call(s), 0 tried" — seen exactly that way on a
|
|
81
|
+
* customer's live connection, where it read as "nobody has ever tried
|
|
82
|
+
* these", which was not true.
|
|
83
|
+
*
|
|
84
|
+
* Only a sample that actually returned records counts, which is the
|
|
85
|
+
* same rule the strip applies.
|
|
86
86
|
*/ if (result.ok && result.recordCount > 0) {
|
|
87
87
|
const row = store.endpoints.find((candidate)=>candidate.id === requestedFor);
|
|
88
88
|
if (row) {
|
|
@@ -7,12 +7,12 @@ import { integrationsPageTheme } from '../theme';
|
|
|
7
7
|
import { EndpointForm } from './EndpointForm';
|
|
8
8
|
import { EndpointList } from './EndpointList';
|
|
9
9
|
import { EndpointSample } from './EndpointSample';
|
|
10
|
-
/**
|
|
11
|
-
* The endpoints section: the list on one side, the selected call on the other.
|
|
12
|
-
*
|
|
13
|
-
* The record the list hands over is the record the form edits, so opening an
|
|
14
|
-
* endpoint costs no extra request. The form is keyed by that record's id, which
|
|
15
|
-
* is what makes switching selection a fresh form rather than a merged one.
|
|
10
|
+
/**
|
|
11
|
+
* The endpoints section: the list on one side, the selected call on the other.
|
|
12
|
+
*
|
|
13
|
+
* The record the list hands over is the record the form edits, so opening an
|
|
14
|
+
* endpoint costs no extra request. The form is keyed by that record's id, which
|
|
15
|
+
* is what makes switching selection a fresh form rather than a merged one.
|
|
16
16
|
*/ const theme = integrationsPageTheme;
|
|
17
17
|
export function EndpointsTab({ actions }) {
|
|
18
18
|
const labels = useLabels();
|
|
@@ -20,15 +20,15 @@ export function EndpointsTab({ actions }) {
|
|
|
20
20
|
const sourceId = store.selectedSourceId;
|
|
21
21
|
const [creating, setCreating] = useState(false);
|
|
22
22
|
const [reloadToken, setReloadToken] = useState(0);
|
|
23
|
-
/**
|
|
24
|
-
* The selection is the STORE's, and only the store's.
|
|
25
|
-
*
|
|
26
|
-
* It used to be kept here as well, and the two were reconciled by comparing
|
|
27
|
-
* them on every render. Clicking a second call then left the first form on
|
|
28
|
-
* screen and added the new one under it — two live editors of two different
|
|
29
|
-
* records, both writing to the same hard-coded field ids, growing by one card
|
|
30
|
-
* per click. One source of truth, and one keyed container below, is what makes
|
|
31
|
-
* that impossible rather than unlikely.
|
|
23
|
+
/**
|
|
24
|
+
* The selection is the STORE's, and only the store's.
|
|
25
|
+
*
|
|
26
|
+
* It used to be kept here as well, and the two were reconciled by comparing
|
|
27
|
+
* them on every render. Clicking a second call then left the first form on
|
|
28
|
+
* screen and added the new one under it — two live editors of two different
|
|
29
|
+
* records, both writing to the same hard-coded field ids, growing by one card
|
|
30
|
+
* per click. One source of truth, and one keyed container below, is what makes
|
|
31
|
+
* that impossible rather than unlikely.
|
|
32
32
|
*/ const selectedId = store.selectedEndpointId;
|
|
33
33
|
const editing = selectedId ? store.endpoints.find((e)=>e.id === selectedId) ?? null : null;
|
|
34
34
|
// Another connection is another set of calls. A form left open across the
|
|
@@ -57,20 +57,20 @@ export function EndpointsTab({ actions }) {
|
|
|
57
57
|
// collapse to "pick one" — with the form still half-filled a moment ago.
|
|
58
58
|
store.upsertEndpoint(saved);
|
|
59
59
|
store.selectEndpoint(saved.id);
|
|
60
|
-
/**
|
|
61
|
-
* The sample described the call as it was BEFORE this save.
|
|
62
|
-
*
|
|
63
|
-
* Re-selecting was supposed to drop it, and does not: `selectEndpoint`
|
|
64
|
-
* returns early when the id has not changed, which on a save it never has —
|
|
65
|
-
* it is the guard that stops re-choosing the open call from throwing away a
|
|
66
|
-
* sample somebody just paid a real request for. Correct there, and the
|
|
67
|
-
* reason nothing was cleared here.
|
|
68
|
-
*
|
|
69
|
-
* So the sample sat under an edited call: change `responseRootPath` from
|
|
70
|
-
* empty to `data`, save, and the card still reports "Status 200, 0 records"
|
|
71
|
-
* with the old body keys — and the mapping's field list is built from those
|
|
72
|
-
* keys. The answer on screen belonged to a request that is no longer the one
|
|
73
|
-
* configured.
|
|
60
|
+
/**
|
|
61
|
+
* The sample described the call as it was BEFORE this save.
|
|
62
|
+
*
|
|
63
|
+
* Re-selecting was supposed to drop it, and does not: `selectEndpoint`
|
|
64
|
+
* returns early when the id has not changed, which on a save it never has —
|
|
65
|
+
* it is the guard that stops re-choosing the open call from throwing away a
|
|
66
|
+
* sample somebody just paid a real request for. Correct there, and the
|
|
67
|
+
* reason nothing was cleared here.
|
|
68
|
+
*
|
|
69
|
+
* So the sample sat under an edited call: change `responseRootPath` from
|
|
70
|
+
* empty to `data`, save, and the card still reports "Status 200, 0 records"
|
|
71
|
+
* with the old body keys — and the mapping's field list is built from those
|
|
72
|
+
* keys. The answer on screen belonged to a request that is no longer the one
|
|
73
|
+
* configured.
|
|
74
74
|
*/ store.setSample(null, null);
|
|
75
75
|
setReloadToken((token)=>token + 1);
|
|
76
76
|
};
|
|
@@ -4,13 +4,13 @@ import { cn } from '../../../utils/cn';
|
|
|
4
4
|
import { useLabels } from '../text/context';
|
|
5
5
|
import { integrationsPageTheme } from '../theme';
|
|
6
6
|
import { FIELD_MAPPING_GRID, PARENT_PREFIX, TRANSFORM_LABEL, TRANSFORMS, toTransform } from './fieldMappingOptions';
|
|
7
|
-
/**
|
|
8
|
-
* One line of a field mapping: where the value comes from, what it becomes, and
|
|
9
|
-
* which column it lands in.
|
|
10
|
-
*
|
|
11
|
-
* The row owns no state. It reports a patch and the editor above decides what
|
|
12
|
-
* the list of rows now is, so ordering and validation live in one place instead
|
|
13
|
-
* of being spread across however many rows are on screen.
|
|
7
|
+
/**
|
|
8
|
+
* One line of a field mapping: where the value comes from, what it becomes, and
|
|
9
|
+
* which column it lands in.
|
|
10
|
+
*
|
|
11
|
+
* The row owns no state. It reports a patch and the editor above decides what
|
|
12
|
+
* the list of rows now is, so ordering and validation live in one place instead
|
|
13
|
+
* of being spread across however many rows are on screen.
|
|
14
14
|
*/ const theme = integrationsPageTheme;
|
|
15
15
|
/** Label above each control on a phone; the header row replaces it from lg up. */ function Cell({ id, label, children }) {
|
|
16
16
|
return /*#__PURE__*/ _jsxs("div", {
|