@endora-commerce/mod-credentials 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/api/credentials-client.d.ts +11 -0
- package/dist/admin/api/credentials-client.d.ts.map +1 -0
- package/dist/admin/api/credentials-client.js +38 -0
- package/dist/admin/api/credentials-client.js.map +1 -0
- package/dist/admin/components/ConfigurationForm.d.ts +22 -0
- package/dist/admin/components/ConfigurationForm.d.ts.map +1 -0
- package/dist/admin/components/ConfigurationForm.js +89 -0
- package/dist/admin/components/ConfigurationForm.js.map +1 -0
- package/dist/admin/index.d.ts +47 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +65 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CredentialsNewPage.d.ts +19 -0
- package/dist/admin/pages/CredentialsNewPage.d.ts.map +1 -0
- package/dist/admin/pages/CredentialsNewPage.js +22 -0
- package/dist/admin/pages/CredentialsNewPage.js.map +1 -0
- package/dist/admin/pages/CredentialsPage.d.ts +22 -0
- package/dist/admin/pages/CredentialsPage.d.ts.map +1 -0
- package/dist/admin/pages/CredentialsPage.js +154 -0
- package/dist/admin/pages/CredentialsPage.js.map +1 -0
- package/dist/admin-ui/ConfigurationPreviewModal.d.ts +16 -0
- package/dist/admin-ui/ConfigurationPreviewModal.d.ts.map +1 -0
- package/dist/admin-ui/ConfigurationPreviewModal.js +15 -0
- package/dist/admin-ui/ConfigurationPreviewModal.js.map +1 -0
- package/dist/admin-ui/index.d.ts +57 -0
- package/dist/admin-ui/index.d.ts.map +1 -0
- package/dist/admin-ui/index.js +56 -0
- package/dist/admin-ui/index.js.map +1 -0
- package/dist/backend/commands/create-configuration.command.d.ts +20 -0
- package/dist/backend/commands/create-configuration.command.d.ts.map +1 -0
- package/dist/backend/commands/create-configuration.command.js +70 -0
- package/dist/backend/commands/create-configuration.command.js.map +1 -0
- package/dist/backend/commands/delete-configuration.command.d.ts +16 -0
- package/dist/backend/commands/delete-configuration.command.d.ts.map +1 -0
- package/dist/backend/commands/delete-configuration.command.js +48 -0
- package/dist/backend/commands/delete-configuration.command.js.map +1 -0
- package/dist/backend/commands/update-configuration.command.d.ts +20 -0
- package/dist/backend/commands/update-configuration.command.d.ts.map +1 -0
- package/dist/backend/commands/update-configuration.command.js +94 -0
- package/dist/backend/commands/update-configuration.command.js.map +1 -0
- package/dist/backend/entities/credential-configuration.entity.d.ts +30 -0
- package/dist/backend/entities/credential-configuration.entity.d.ts.map +1 -0
- package/dist/backend/entities/credential-configuration.entity.js +84 -0
- package/dist/backend/entities/credential-configuration.entity.js.map +1 -0
- package/dist/backend/index.d.ts +73 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +80 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +43 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +25 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +27 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +50 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/services/configuration-type-registry.d.ts +105 -0
- package/dist/backend/services/configuration-type-registry.d.ts.map +1 -0
- package/dist/backend/services/configuration-type-registry.js +111 -0
- package/dist/backend/services/configuration-type-registry.js.map +1 -0
- package/dist/backend/services/credentials.service.d.ts +63 -0
- package/dist/backend/services/credentials.service.d.ts.map +1 -0
- package/dist/backend/services/credentials.service.js +128 -0
- package/dist/backend/services/credentials.service.js.map +1 -0
- package/dist/backend/services/field-validator.d.ts +65 -0
- package/dist/backend/services/field-validator.d.ts.map +1 -0
- package/dist/backend/services/field-validator.js +165 -0
- package/dist/backend/services/field-validator.js.map +1 -0
- package/dist/backend/services/registry-singleton.d.ts +28 -0
- package/dist/backend/services/registry-singleton.d.ts.map +1 -0
- package/dist/backend/services/registry-singleton.js +29 -0
- package/dist/backend/services/registry-singleton.js.map +1 -0
- package/dist/backend/services/secret-value-codec.d.ts +55 -0
- package/dist/backend/services/secret-value-codec.d.ts.map +1 -0
- package/dist/backend/services/secret-value-codec.js +105 -0
- package/dist/backend/services/secret-value-codec.js.map +1 -0
- package/dist/backend/types/email-adapter.type.d.ts +10 -0
- package/dist/backend/types/email-adapter.type.d.ts.map +1 -0
- package/dist/backend/types/email-adapter.type.js +49 -0
- package/dist/backend/types/email-adapter.type.js.map +1 -0
- package/dist/backend/types/llm.type.d.ts +3 -0
- package/dist/backend/types/llm.type.d.ts.map +1 -0
- package/dist/backend/types/llm.type.js +35 -0
- package/dist/backend/types/llm.type.js.map +1 -0
- package/dist/manifest.d.ts +176 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +167 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260721T011509_credentials_init.d.ts +17 -0
- package/dist/migrations/20260721T011509_credentials_init.d.ts.map +1 -0
- package/dist/migrations/20260721T011509_credentials_init.js +36 -0
- package/dist/migrations/20260721T011509_credentials_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/credentials.md +202 -0
- package/i18n/en.json +47 -0
- package/i18n/pl.json +47 -0
- package/package.json +98 -0
- package/tailwind.css +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
|
|
2
|
+
|
|
3
|
+
# @endora-commerce/mod-credentials
|
|
4
|
+
|
|
5
|
+
Reusable credential configurations (LLM, email adapter, …) referenced by settings and resolved by consumers; secrets encrypted at rest and write-only at the boundary.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
An **Endora Commerce module package**. Its module id is `credentials` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
|
|
10
|
+
|
|
11
|
+
A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
| Import | Contents |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@endora-commerce/mod-credentials` | the module manifest — its id, version, dependencies, settings and activation |
|
|
18
|
+
| `@endora-commerce/mod-credentials/admin` | the Admin UI contribution: the screens, navigation and palette actions it adds |
|
|
19
|
+
| `@endora-commerce/mod-credentials/admin-ui` | React components this module publishes for another module’s admin screens |
|
|
20
|
+
| `@endora-commerce/mod-credentials/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
|
|
21
|
+
| `@endora-commerce/mod-credentials/migrations` | the module’s own schema migrations, in the order the platform runs them |
|
|
22
|
+
| `@endora-commerce/mod-credentials/tailwind.css` | the Tailwind source declaration for the admin code above |
|
|
23
|
+
|
|
24
|
+
## Depends on
|
|
25
|
+
|
|
26
|
+
Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
|
|
27
|
+
|
|
28
|
+
**Endora packages**
|
|
29
|
+
|
|
30
|
+
- `@endora-commerce/admin-kit` — *optional*
|
|
31
|
+
- `@endora-commerce/contracts`
|
|
32
|
+
- `@endora-commerce/platform`
|
|
33
|
+
|
|
34
|
+
**Third-party**
|
|
35
|
+
|
|
36
|
+
- `@mikro-orm/core` ^6
|
|
37
|
+
- `@mikro-orm/migrations` ^6
|
|
38
|
+
- `@mikro-orm/postgresql` ^6
|
|
39
|
+
- `fastify` ^5
|
|
40
|
+
- `react` ^19 — *optional*
|
|
41
|
+
- `react-router-dom` ^7 — *optional*
|
|
42
|
+
- `zod` ^4
|
|
43
|
+
|
|
44
|
+
## What the tarball carries
|
|
45
|
+
|
|
46
|
+
- `dist/` — the compiled JavaScript and its type declarations
|
|
47
|
+
- `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
|
|
48
|
+
- `docs/` — the package’s own pages in the Endora Commerce documentation site
|
|
49
|
+
- `tailwind.css` — the Tailwind source declaration
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm add @endora-commerce/mod-credentials
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Licence
|
|
58
|
+
|
|
59
|
+
MIT — the text is in `LICENSE`, beside this file.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConfigurationDto, ConfigurationTypeDescriptor, CreateConfiguration, UpdateConfiguration } from '@endora-commerce/contracts';
|
|
2
|
+
export declare const credentialsClient: {
|
|
3
|
+
listTypes(): Promise<ConfigurationTypeDescriptor[]>;
|
|
4
|
+
list(type?: string): Promise<ConfigurationDto[]>;
|
|
5
|
+
get(code: string): Promise<ConfigurationDto>;
|
|
6
|
+
preview(code: string): Promise<ConfigurationDto>;
|
|
7
|
+
create(body: CreateConfiguration): Promise<ConfigurationDto>;
|
|
8
|
+
update(code: string, body: UpdateConfiguration): Promise<ConfigurationDto>;
|
|
9
|
+
delete(code: string): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=credentials-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-client.d.ts","sourceRoot":"","sources":["../../../src/admin/api/credentials-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAWpC,eAAO,MAAM,iBAAiB;iBACf,OAAO,CAAC,2BAA2B,EAAE,CAAC;gBAMvC,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;cAOtC,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;kBAI9B,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;iBAInC,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;iBAI/C,MAAM,QAAQ,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC;iBAI7D,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGpC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { apiClient } from '@endora-commerce/admin-kit/lib';
|
|
2
|
+
/**
|
|
3
|
+
* Admin API client for the credentials module (feature 058).
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper over `apiClient` calls to `/api/v1/admin/credentials/...`; every
|
|
6
|
+
* shape comes from `@endora-commerce/contracts`. Secrets are never returned in plaintext —
|
|
7
|
+
* a `ConfigurationDto` field carries only `isSet` for secret fields.
|
|
8
|
+
*/
|
|
9
|
+
const BASE = '/api/v1/admin/credentials';
|
|
10
|
+
export const credentialsClient = {
|
|
11
|
+
listTypes() {
|
|
12
|
+
return apiClient
|
|
13
|
+
.get(`${BASE}/types`)
|
|
14
|
+
.then((r) => r.types);
|
|
15
|
+
},
|
|
16
|
+
list(type) {
|
|
17
|
+
const q = type ? `?type=${encodeURIComponent(type)}` : '';
|
|
18
|
+
return apiClient
|
|
19
|
+
.get(`${BASE}${q}`)
|
|
20
|
+
.then((r) => r.configurations);
|
|
21
|
+
},
|
|
22
|
+
get(code) {
|
|
23
|
+
return apiClient.get(`${BASE}/${encodeURIComponent(code)}`);
|
|
24
|
+
},
|
|
25
|
+
preview(code) {
|
|
26
|
+
return apiClient.get(`${BASE}/${encodeURIComponent(code)}/preview`);
|
|
27
|
+
},
|
|
28
|
+
create(body) {
|
|
29
|
+
return apiClient.post(BASE, body);
|
|
30
|
+
},
|
|
31
|
+
update(code, body) {
|
|
32
|
+
return apiClient.put(`${BASE}/${encodeURIComponent(code)}`, body);
|
|
33
|
+
},
|
|
34
|
+
delete(code) {
|
|
35
|
+
return apiClient.delete(`${BASE}/${encodeURIComponent(code)}`);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=credentials-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-client.js","sourceRoot":"","sources":["../../../src/admin/api/credentials-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAQ3D;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,2BAA2B,CAAC;AAEzC,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,SAAS;QACP,OAAO,SAAS;aACb,GAAG,CAA2C,GAAG,IAAI,QAAQ,CAAC;aAC9D,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,IAAa;QAChB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,OAAO,SAAS;aACb,GAAG,CAAyC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;aAC1D,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,SAAS,CAAC,GAAG,CAAmB,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,OAAO,SAAS,CAAC,GAAG,CAAmB,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,CAAC,IAAyB;QAC9B,OAAO,SAAS,CAAC,IAAI,CAAmB,IAAI,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,IAAyB;QAC5C,OAAO,SAAS,CAAC,GAAG,CAAmB,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtF,CAAC;IAED,MAAM,CAAC,IAAY;QACjB,OAAO,SAAS,CAAC,MAAM,CAAO,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ConfigurationDto, ConfigurationTypeDescriptor, CreateConfiguration, UpdateConfiguration } from '@endora-commerce/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* Dynamic create/edit form for a credential configuration (feature 058 US1).
|
|
5
|
+
*
|
|
6
|
+
* The type + provider pickers drive which fields render (from the descriptor's
|
|
7
|
+
* `FieldDefinition[]`). Secret fields are write-only password inputs showing an
|
|
8
|
+
* is-Set placeholder; a blank secret on edit preserves the stored value. Type,
|
|
9
|
+
* provider and code are immutable once created.
|
|
10
|
+
*/
|
|
11
|
+
export interface ConfigurationFormProps {
|
|
12
|
+
types: ConfigurationTypeDescriptor[];
|
|
13
|
+
/** Present when editing an existing configuration. */
|
|
14
|
+
existing?: ConfigurationDto | null;
|
|
15
|
+
onCreate: (body: CreateConfiguration) => Promise<void>;
|
|
16
|
+
onUpdate: (code: string, body: UpdateConfiguration) => Promise<void>;
|
|
17
|
+
onCancel: () => void;
|
|
18
|
+
busy?: boolean;
|
|
19
|
+
error?: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function ConfigurationForm({ types, existing, onCreate, onUpdate, onCancel, busy, error, }: ConfigurationFormProps): ReactNode;
|
|
22
|
+
//# sourceMappingURL=ConfigurationForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigurationForm.d.ts","sourceRoot":"","sources":["../../../src/admin/components/ConfigurationForm.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,KAAK,EACV,gBAAgB,EAChB,2BAA2B,EAC3B,mBAAmB,EAEnB,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,2BAA2B,EAAE,CAAC;IACrC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,GACN,EAAE,sBAAsB,GAAG,SAAS,CA2IpC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { Button, Input, Label, Select } from '@endora-commerce/admin-kit/ui';
|
|
4
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
5
|
+
export function ConfigurationForm({ types, existing, onCreate, onUpdate, onCancel, busy, error, }) {
|
|
6
|
+
const t = useTranslation('credentials');
|
|
7
|
+
const isEdit = Boolean(existing);
|
|
8
|
+
const [name, setName] = useState(existing?.name ?? '');
|
|
9
|
+
const [code, setCode] = useState(existing?.code ?? '');
|
|
10
|
+
const [typeCode, setTypeCode] = useState(existing?.typeCode ?? types[0]?.code ?? '');
|
|
11
|
+
const descriptor = types.find((ty) => ty.code === typeCode);
|
|
12
|
+
const [providerCode, setProviderCode] = useState(existing?.providerCode ?? descriptor?.providers[0]?.code ?? '');
|
|
13
|
+
const provider = descriptor?.providers.find((p) => p.code === providerCode);
|
|
14
|
+
// Field values keyed by field key. Secret fields start blank (write-only);
|
|
15
|
+
// non-secret fields prefill from the existing DTO.
|
|
16
|
+
const initialValues = useMemo(() => {
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const f of provider?.fields ?? []) {
|
|
19
|
+
if (f.secret)
|
|
20
|
+
continue;
|
|
21
|
+
const dtoField = existing?.fields.find((x) => x.key === f.key);
|
|
22
|
+
if (dtoField && 'value' in dtoField && dtoField.value !== undefined && dtoField.value !== null) {
|
|
23
|
+
out[f.key] = String(dtoField.value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
// Recompute when the provider changes.
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, [providerCode, typeCode]);
|
|
30
|
+
const [values, setValues] = useState(initialValues);
|
|
31
|
+
const secretIsSet = (key) => existing?.fields.find((x) => x.key === key)?.isSet === true;
|
|
32
|
+
const setValue = (key, v) => setValues((prev) => ({ ...prev, [key]: v }));
|
|
33
|
+
const submit = async () => {
|
|
34
|
+
const payloadValues = {};
|
|
35
|
+
for (const f of provider?.fields ?? []) {
|
|
36
|
+
const raw = values[f.key];
|
|
37
|
+
if (f.secret) {
|
|
38
|
+
// Only send a secret when the operator typed a new one (write-only).
|
|
39
|
+
if (raw && raw.length > 0)
|
|
40
|
+
payloadValues[f.key] = raw;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (raw === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
payloadValues[f.key] = coerce(f, raw);
|
|
46
|
+
}
|
|
47
|
+
if (isEdit && existing) {
|
|
48
|
+
await onUpdate(existing.code, { name, values: payloadValues, expectedVersion: existing.version });
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
await onCreate({ code, name, typeCode, providerCode, values: payloadValues });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return (_jsxs("div", { className: "space-y-4 rounded-lg border p-4", children: [error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "cred-name", children: t('form.name') }), _jsx(Input, { id: "cred-name", value: name, onChange: (e) => setName(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "cred-code", children: t('form.code') }), _jsx(Input, { id: "cred-code", value: code, onChange: (e) => setCode(e.target.value), disabled: isEdit })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "cred-type", children: t('form.type') }), _jsx(Select, { id: "cred-type", value: typeCode, disabled: isEdit, onChange: (e) => {
|
|
55
|
+
const next = e.target.value;
|
|
56
|
+
setTypeCode(next);
|
|
57
|
+
const nextProvider = types.find((ty) => ty.code === next)?.providers[0]?.code ?? '';
|
|
58
|
+
setProviderCode(nextProvider);
|
|
59
|
+
setValues({});
|
|
60
|
+
}, children: types.map((ty) => (_jsx("option", { value: ty.code, children: ty.label }, ty.code))) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "cred-provider", children: t('form.provider') }), _jsx(Select, { id: "cred-provider", value: providerCode, disabled: isEdit, onChange: (e) => {
|
|
61
|
+
setProviderCode(e.target.value);
|
|
62
|
+
setValues({});
|
|
63
|
+
}, children: (descriptor?.providers ?? []).map((p) => (_jsx("option", { value: p.code, children: p.label }, p.code))) })] })] }), _jsx("div", { className: "space-y-3", children: (provider?.fields ?? []).map((f) => (_jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { htmlFor: `cred-field-${f.key}`, children: [f.label, f.required ? _jsx("span", { className: "text-destructive", children: " *" }) : null] }), renderField(f, values[f.key] ?? '', (v) => setValue(f.key, v), {
|
|
64
|
+
secretPlaceholder: secretIsSet(f.key) ? t('form.secretSet') : t('form.secretUnset'),
|
|
65
|
+
fieldId: `cred-field-${f.key}`,
|
|
66
|
+
})] }, f.key))) }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onCancel, disabled: busy, children: t('action.cancel') }), _jsx(Button, { onClick: () => void submit(), disabled: busy, children: t('action.save') })] })] }));
|
|
67
|
+
}
|
|
68
|
+
function coerce(field, raw) {
|
|
69
|
+
if (field.kind === 'number') {
|
|
70
|
+
const n = Number(raw);
|
|
71
|
+
return Number.isNaN(n) ? raw : n;
|
|
72
|
+
}
|
|
73
|
+
if (field.kind === 'boolean')
|
|
74
|
+
return raw === 'true';
|
|
75
|
+
return raw;
|
|
76
|
+
}
|
|
77
|
+
function renderField(field, value, onChange, opts) {
|
|
78
|
+
if (field.secret) {
|
|
79
|
+
return (_jsx(Input, { id: opts.fieldId, type: "password", autoComplete: "new-password", value: value, placeholder: opts.secretPlaceholder, onChange: (e) => onChange(e.target.value) }));
|
|
80
|
+
}
|
|
81
|
+
if (field.kind === 'boolean') {
|
|
82
|
+
return (_jsxs(Select, { id: opts.fieldId, value: value || 'false', onChange: (e) => onChange(e.target.value), children: [_jsx("option", { value: "false", children: "false" }), _jsx("option", { value: "true", children: "true" })] }));
|
|
83
|
+
}
|
|
84
|
+
if (field.kind === 'select') {
|
|
85
|
+
return (_jsxs(Select, { id: opts.fieldId, value: value, onChange: (e) => onChange(e.target.value), children: [_jsx("option", { value: "", children: "\u2014" }), (field.options ?? []).map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value)))] }));
|
|
86
|
+
}
|
|
87
|
+
return (_jsx(Input, { id: opts.fieldId, type: field.kind === 'number' ? 'number' : 'text', value: value, placeholder: field.placeholder ?? '', onChange: (e) => onChange(e.target.value) }));
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=ConfigurationForm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigurationForm.js","sourceRoot":"","sources":["../../../src/admin/components/ConfigurationForm.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `credentials`' admin surface — two routes and one sidebar entry, declared by
|
|
3
|
+
* the module that owns them (feature 091, Phase 4, batch 10;
|
|
4
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
|
|
5
|
+
*
|
|
6
|
+
* **This is the batch's paying half.** `credentials` was 2-in / 0-out in the
|
|
7
|
+
* re-derived table and both incoming reaches were one file — `settings`'
|
|
8
|
+
* `ConfigurationReferenceInput.tsx`, which imported this module's admin API
|
|
9
|
+
* client *and* its `ConfigurationPreviewModal`. P6 took the client half (the
|
|
10
|
+
* caller builds `GET /api/v1/admin/credentials?type=…` from the published
|
|
11
|
+
* `apiClient` and `ConfigurationListResponse`), and this batch takes the
|
|
12
|
+
* component half, which is why `settings` and `credentials` move together: the
|
|
13
|
+
* repair's two endpoints are both inside it, so neither module pays a reach
|
|
14
|
+
* whose other end it is not moving.
|
|
15
|
+
*
|
|
16
|
+
* **The component half is `./admin-ui`, not the kit and not a zone.**
|
|
17
|
+
* `admin-component-contribution.md` Z1 decides it from the signature —
|
|
18
|
+
* `(open, configuration, onClose)` is a value in and a value back, question 1,
|
|
19
|
+
* therefore a published component — and Z1.1's kit test refuses it because the
|
|
20
|
+
* modal renders out of this module's own translation namespace. The reasoning
|
|
21
|
+
* in full, including why the R-1 §9.2 remedy is wrong for this one, is in
|
|
22
|
+
* `../admin-ui/index.ts`.
|
|
23
|
+
*
|
|
24
|
+
* **`/credentials/new` is a file rather than a prop.** `App.tsx` wrote
|
|
25
|
+
* `element={<CredentialsPage initialMode="new" />}`; a route contribution's
|
|
26
|
+
* `component` is a factory returning a module and the registry reads its
|
|
27
|
+
* `default`, so there is nowhere to put an argument. `CredentialsNewPage` is
|
|
28
|
+
* those two lines, held by the module instead of by the host, and it is the
|
|
29
|
+
* `new-credential` palette action's destination.
|
|
30
|
+
*
|
|
31
|
+
* **Both routes leave `App.tsx` ungated and arrive gated**, which is batch
|
|
32
|
+
* five's operator-visible repair arriving again: a host `<Route>` is the whole
|
|
33
|
+
* of what `App.tsx` declares, so before this batch a deep link to
|
|
34
|
+
* `/credentials` rendered the screen for any signed-in admin — and did so with
|
|
35
|
+
* the module switched **off** — leaving the API to answer 403 or 503 field by
|
|
36
|
+
* field. `ModuleRoute` gates both axes at the URL now.
|
|
37
|
+
*
|
|
38
|
+
* **This entry exports data and nothing else** (R2), like every `./admin`
|
|
39
|
+
* layer: `check:module-boundary`'s D-171 rule designates a subpath as contract
|
|
40
|
+
* surface when it emits no runtime binding, and `./admin` deliberately does not
|
|
41
|
+
* qualify, so a consumer reaching into another module's `./admin` stays a
|
|
42
|
+
* counted boundary reach. `./admin-ui` does not qualify either, deliberately —
|
|
43
|
+
* see Z11.
|
|
44
|
+
*/
|
|
45
|
+
import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
46
|
+
export declare const contributions: AdminContributions;
|
|
47
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AA6BnF,eAAO,MAAM,aAAa,EAAE,kBAuC3B,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** The module's landing route: the configuration list. */
|
|
2
|
+
const ROUTE_PATH = '/credentials';
|
|
3
|
+
/**
|
|
4
|
+
* The code that opens the screen.
|
|
5
|
+
*
|
|
6
|
+
* `GET /api/v1/admin/credentials` and `GET /api/v1/admin/credentials/types` are
|
|
7
|
+
* both `requireAdmin('credentials:read')`, and the list fetches both on mount.
|
|
8
|
+
* The screen writes too — create, edit, delete — behind `credentials:write`,
|
|
9
|
+
* which is a second code this module declares and which is **not** what gates
|
|
10
|
+
* the surface: an operator holding only the read code opens the list and sees
|
|
11
|
+
* the write affordances refuse.
|
|
12
|
+
*/
|
|
13
|
+
const READ_PERMISSION = 'credentials:read';
|
|
14
|
+
/**
|
|
15
|
+
* The code the create route enforces.
|
|
16
|
+
*
|
|
17
|
+
* `POST /api/v1/admin/credentials` is `requireAdmin('credentials:write')`, and
|
|
18
|
+
* `/credentials/new` lands straight on the form — a screen whose only purpose
|
|
19
|
+
* is a write. The codes are opaque strings, so an operator holding the read
|
|
20
|
+
* code alone must not reach it; the module's own `new-credential` palette
|
|
21
|
+
* action declares the same code, which is what `check:action-route-permissions`
|
|
22
|
+
* holds the pair to.
|
|
23
|
+
*/
|
|
24
|
+
const WRITE_PERMISSION = 'credentials:write';
|
|
25
|
+
export const contributions = {
|
|
26
|
+
routes: [
|
|
27
|
+
{
|
|
28
|
+
path: ROUTE_PATH,
|
|
29
|
+
component: () => import('./pages/CredentialsPage.js'),
|
|
30
|
+
requiredPermission: READ_PERMISSION,
|
|
31
|
+
index: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
path: `${ROUTE_PATH}/new`,
|
|
35
|
+
component: () => import('./pages/CredentialsNewPage.js'),
|
|
36
|
+
requiredPermission: WRITE_PERMISSION,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
nav: [
|
|
40
|
+
{
|
|
41
|
+
to: ROUTE_PATH,
|
|
42
|
+
// Module-relative (R8), resolved in this module's own namespace out of
|
|
43
|
+
// `packages/modules/credentials/i18n/`. It was `appShell.nav.credentials`
|
|
44
|
+
// in the shared `_i18n` bundle, one of the four shared files a module
|
|
45
|
+
// author had to edit.
|
|
46
|
+
labelKey: 'nav.credentials.label',
|
|
47
|
+
// The glyph `AppShell.tsx` rendered by hand, and already on
|
|
48
|
+
// `KnownIconNameSchema` — this module's `open-credentials` action names
|
|
49
|
+
// it too.
|
|
50
|
+
icon: 'KeyRound',
|
|
51
|
+
section: 'system',
|
|
52
|
+
// The hand-written position in *System* times a hundred, which is batch
|
|
53
|
+
// four's convention: `/credentials` was the ninth row of that section,
|
|
54
|
+
// directly above the three `/settings*` rows this batch converts at 1000,
|
|
55
|
+
// 1100 and 1200 and below `/webhooks` at 800. *System* still holds
|
|
56
|
+
// host-declared entries (`/platform/modules`, `/catalog/bulk-operations`),
|
|
57
|
+
// so `composeNav` appends every registry row after both whatever the
|
|
58
|
+
// weight says; what the weight can still do is keep the converted rows in
|
|
59
|
+
// the order the operator already had.
|
|
60
|
+
weight: 900,
|
|
61
|
+
requiredPermission: READ_PERMISSION,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AA8CA,0DAA0D;AAC1D,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC;;;;;;;;;GASG;AACH,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAE3C;;;;;;;;;GASG;AACH,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAE7C,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,4BAA4B,CAAC;YACrD,kBAAkB,EAAE,eAAe;YACnC,KAAK,EAAE,IAAI;SACZ;QACD;YACE,IAAI,EAAE,GAAG,UAAU,MAAM;YACzB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,+BAA+B,CAAC;YACxD,kBAAkB,EAAE,gBAAgB;SACrC;KACF;IACD,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,0EAA0E;YAC1E,sEAAsE;YACtE,sBAAsB;YACtB,QAAQ,EAAE,uBAAuB;YACjC,4DAA4D;YAC5D,wEAAwE;YACxE,UAAU;YACV,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,QAAQ;YACjB,wEAAwE;YACxE,uEAAuE;YACvE,0EAA0E;YAC1E,mEAAmE;YACnE,2EAA2E;YAC3E,qEAAqE;YACrE,0EAA0E;YAC1E,sCAAsC;YACtC,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,eAAe;SACpC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `/credentials/new` — the same screen, landing on the empty form (feature 058
|
|
4
|
+
* US1; feature 091, Phase 4, batch 10).
|
|
5
|
+
*
|
|
6
|
+
* A file rather than a prop on the declaration, because
|
|
7
|
+
* `AdminRouteContribution.component` is a factory returning a module and reads
|
|
8
|
+
* its `default`: there is nowhere in the declaration to put an argument, and
|
|
9
|
+
* adding one would be a props channel the registry has to interpret for every
|
|
10
|
+
* module. `App.tsx` wrote `element={<CredentialsPage initialMode="new" />}`
|
|
11
|
+
* here, which is the same two lines with the host holding them.
|
|
12
|
+
*
|
|
13
|
+
* It is the destination of this module's `new-credential` palette action, so
|
|
14
|
+
* the deep link an operator follows from ⌘K lands on the form rather than on
|
|
15
|
+
* the list with a button to press.
|
|
16
|
+
*/
|
|
17
|
+
export declare function CredentialsNewPage(): ReactNode;
|
|
18
|
+
export default CredentialsNewPage;
|
|
19
|
+
//# sourceMappingURL=CredentialsNewPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CredentialsNewPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/CredentialsNewPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,IAAI,SAAS,CAE9C;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { CredentialsPage } from './CredentialsPage.js';
|
|
3
|
+
/**
|
|
4
|
+
* `/credentials/new` — the same screen, landing on the empty form (feature 058
|
|
5
|
+
* US1; feature 091, Phase 4, batch 10).
|
|
6
|
+
*
|
|
7
|
+
* A file rather than a prop on the declaration, because
|
|
8
|
+
* `AdminRouteContribution.component` is a factory returning a module and reads
|
|
9
|
+
* its `default`: there is nowhere in the declaration to put an argument, and
|
|
10
|
+
* adding one would be a props channel the registry has to interpret for every
|
|
11
|
+
* module. `App.tsx` wrote `element={<CredentialsPage initialMode="new" />}`
|
|
12
|
+
* here, which is the same two lines with the host holding them.
|
|
13
|
+
*
|
|
14
|
+
* It is the destination of this module's `new-credential` palette action, so
|
|
15
|
+
* the deep link an operator follows from ⌘K lands on the form rather than on
|
|
16
|
+
* the list with a button to press.
|
|
17
|
+
*/
|
|
18
|
+
export function CredentialsNewPage() {
|
|
19
|
+
return _jsx(CredentialsPage, { initialMode: "new" });
|
|
20
|
+
}
|
|
21
|
+
export default CredentialsNewPage;
|
|
22
|
+
//# sourceMappingURL=CredentialsNewPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CredentialsNewPage.js","sourceRoot":"","sources":["../../../src/admin/pages/CredentialsNewPage.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,KAAC,eAAe,IAAC,WAAW,EAAC,KAAK,GAAG,CAAC;AAC/C,CAAC;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Credentials management page (feature 058 US1).
|
|
4
|
+
*
|
|
5
|
+
* Lists configurations (secrets masked), supports create/edit via the dynamic
|
|
6
|
+
* `ConfigurationForm`, delete via `window.confirm`, and a read-only preview.
|
|
7
|
+
* Inert configurations (unregistered type) render read-only.
|
|
8
|
+
*
|
|
9
|
+
* `initialMode` lets the `/credentials/new` route (the command-palette
|
|
10
|
+
* "New credential configuration" action) land straight on the empty form.
|
|
11
|
+
*/
|
|
12
|
+
export interface CredentialsPageProps {
|
|
13
|
+
initialMode?: 'list' | 'new';
|
|
14
|
+
}
|
|
15
|
+
export declare function CredentialsPage({ initialMode }?: CredentialsPageProps): ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
18
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
19
|
+
* the spelling this module's own code and its tests use.
|
|
20
|
+
*/
|
|
21
|
+
export default CredentialsPage;
|
|
22
|
+
//# sourceMappingURL=CredentialsPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CredentialsPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/CredentialsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBzE;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC9B;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAoB,EAAE,GAAE,oBAAyB,GAAG,SAAS,CAoM9F;AAYD;;;;GAIG;AACH,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { useNavigate } from 'react-router-dom';
|
|
4
|
+
import { Badge, Button, PageHeader } from '@endora-commerce/admin-kit/ui';
|
|
5
|
+
import { ResponsiveTable } from '@endora-commerce/admin-kit/components';
|
|
6
|
+
import { ApiError } from '@endora-commerce/admin-kit/lib';
|
|
7
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
8
|
+
import { credentialsClient } from '../api/credentials-client.js';
|
|
9
|
+
import { ConfigurationForm } from '../components/ConfigurationForm.js';
|
|
10
|
+
import { ConfigurationPreviewModal } from '../../admin-ui/ConfigurationPreviewModal.js';
|
|
11
|
+
export function CredentialsPage({ initialMode = 'list' } = {}) {
|
|
12
|
+
const t = useTranslation('credentials');
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
const [types, setTypes] = useState([]);
|
|
15
|
+
const [items, setItems] = useState([]);
|
|
16
|
+
const [loading, setLoading] = useState(true);
|
|
17
|
+
const [mode, setMode] = useState(initialMode);
|
|
18
|
+
const [editing, setEditing] = useState(null);
|
|
19
|
+
const [preview, setPreview] = useState(null);
|
|
20
|
+
const [busy, setBusy] = useState(false);
|
|
21
|
+
const [formError, setFormError] = useState(null);
|
|
22
|
+
const [listError, setListError] = useState(null);
|
|
23
|
+
const messageFor = useCallback((err) => {
|
|
24
|
+
if (err instanceof ApiError) {
|
|
25
|
+
const code = err.envelope.error.code;
|
|
26
|
+
// CREDENTIAL_IN_USE carries the referencing settings — list them so the
|
|
27
|
+
// operator knows exactly what to detach before deleting (FR-012).
|
|
28
|
+
if (code === 'CREDENTIAL_IN_USE') {
|
|
29
|
+
const details = err.envelope.error.details;
|
|
30
|
+
const referrers = (details?.referencedBy ?? [])
|
|
31
|
+
.map((r) => (r.salesChannelCode ? `${r.settingCode} (${r.salesChannelCode})` : r.settingCode))
|
|
32
|
+
.join(', ');
|
|
33
|
+
return referrers
|
|
34
|
+
? t('error.inUseWith', { settings: referrers })
|
|
35
|
+
: t('error.inUse');
|
|
36
|
+
}
|
|
37
|
+
const key = ERROR_KEY_BY_CODE[code];
|
|
38
|
+
if (key)
|
|
39
|
+
return t(key);
|
|
40
|
+
return err.envelope.error.message;
|
|
41
|
+
}
|
|
42
|
+
return t('error.generic');
|
|
43
|
+
}, [t]);
|
|
44
|
+
const reload = useCallback(async () => {
|
|
45
|
+
setLoading(true);
|
|
46
|
+
setListError(null);
|
|
47
|
+
try {
|
|
48
|
+
const [ts, list] = await Promise.all([credentialsClient.listTypes(), credentialsClient.list()]);
|
|
49
|
+
setTypes(ts);
|
|
50
|
+
setItems(list);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
setListError(messageFor(err));
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, [messageFor]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
void reload();
|
|
61
|
+
}, [reload]);
|
|
62
|
+
// Leaving the form. When the page was entered through the /credentials/new
|
|
63
|
+
// deep link, drop that segment too so a reload does not reopen the form.
|
|
64
|
+
const backToList = useCallback(() => {
|
|
65
|
+
setMode('list');
|
|
66
|
+
setEditing(null);
|
|
67
|
+
setFormError(null);
|
|
68
|
+
if (initialMode === 'new')
|
|
69
|
+
navigate('/credentials', { replace: true });
|
|
70
|
+
}, [initialMode, navigate]);
|
|
71
|
+
const onCreate = async (body) => {
|
|
72
|
+
setBusy(true);
|
|
73
|
+
setFormError(null);
|
|
74
|
+
try {
|
|
75
|
+
await credentialsClient.create(body);
|
|
76
|
+
backToList();
|
|
77
|
+
await reload();
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
setFormError(messageFor(err));
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
setBusy(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const onUpdate = async (code, body) => {
|
|
87
|
+
setBusy(true);
|
|
88
|
+
setFormError(null);
|
|
89
|
+
try {
|
|
90
|
+
await credentialsClient.update(code, body);
|
|
91
|
+
backToList();
|
|
92
|
+
await reload();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
setFormError(messageFor(err));
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
setBusy(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const onDelete = async (row) => {
|
|
102
|
+
if (!window.confirm(t('page.deleteConfirm')))
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
await credentialsClient.delete(row.code);
|
|
106
|
+
await reload();
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
setListError(messageFor(err));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const columns = [
|
|
113
|
+
{ id: 'name', header: t('table.name'), primary: true, render: (r) => r.name },
|
|
114
|
+
{ id: 'code', header: t('table.code'), render: (r) => _jsx("code", { className: "text-xs", children: r.code }) },
|
|
115
|
+
{
|
|
116
|
+
id: 'type',
|
|
117
|
+
header: t('table.type'),
|
|
118
|
+
render: (r) => r.inert ? _jsx(Badge, { variant: "destructive", children: r.typeCode }) : (r.typeLabel ?? r.typeCode),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'provider',
|
|
122
|
+
header: t('table.provider'),
|
|
123
|
+
render: (r) => r.providerLabel ?? r.providerCode,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
if (mode !== 'list') {
|
|
127
|
+
return (_jsxs("div", { children: [_jsx(PageHeader, { title: mode === 'new' ? t('page.new') : t('page.edit') }), _jsx(ConfigurationForm, { types: types, existing: mode === 'edit' ? editing : null, onCreate: onCreate, onUpdate: onUpdate, onCancel: backToList, busy: busy, error: formError })] }));
|
|
128
|
+
}
|
|
129
|
+
return (_jsxs("div", { children: [_jsx(PageHeader, { title: t('page.title'), description: t('page.subtitle'), actions: _jsx(Button, { onClick: () => {
|
|
130
|
+
setEditing(null);
|
|
131
|
+
setFormError(null);
|
|
132
|
+
setMode('new');
|
|
133
|
+
}, disabled: types.length === 0, children: t('page.new') }) }), listError ? _jsx("p", { className: "mb-3 text-sm text-destructive", children: listError }) : null, loading ? null : (_jsx(ResponsiveTable, { columns: columns, data: items, keyExtractor: (r) => r.id, emptyState: _jsx("p", { className: "text-sm text-muted-foreground", children: t('page.empty') }), renderActions: (r) => (_jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPreview(r), children: t('action.preview') }), _jsx(Button, { variant: "outline", size: "sm", disabled: r.inert, onClick: () => {
|
|
134
|
+
setEditing(r);
|
|
135
|
+
setFormError(null);
|
|
136
|
+
setMode('edit');
|
|
137
|
+
}, children: t('action.edit') }), _jsx(Button, { variant: "destructive", size: "sm", onClick: () => void onDelete(r), children: t('action.delete') })] })) })), _jsx(ConfigurationPreviewModal, { open: preview !== null, configuration: preview, onClose: () => setPreview(null) })] }));
|
|
138
|
+
}
|
|
139
|
+
/** Map contract error codes to translation keys for user-facing messages. */
|
|
140
|
+
const ERROR_KEY_BY_CODE = {
|
|
141
|
+
CREDENTIAL_NOT_FOUND: 'error.notFound',
|
|
142
|
+
CREDENTIAL_CODE_TAKEN: 'error.codeTaken',
|
|
143
|
+
CREDENTIAL_TYPE_UNKNOWN: 'error.typeUnknown',
|
|
144
|
+
CREDENTIAL_VALIDATION_FAILED: 'error.validationFailed',
|
|
145
|
+
CREDENTIAL_IN_USE: 'error.inUse',
|
|
146
|
+
CREDENTIAL_TYPE_IMMUTABLE: 'error.typeImmutable',
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* The registry loads a route component through a dynamic-import factory and
|
|
150
|
+
* reads its default export (feature 091, FR-013). The named export stays: it is
|
|
151
|
+
* the spelling this module's own code and its tests use.
|
|
152
|
+
*/
|
|
153
|
+
export default CredentialsPage;
|
|
154
|
+
//# sourceMappingURL=CredentialsPage.js.map
|