@haneinfo/frontend-instances 0.2.0 → 0.3.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/README.md +95 -73
- package/agent-guide.md +54 -39
- package/dist/action-client.d.ts +16 -0
- package/dist/action-client.d.ts.map +1 -0
- package/dist/action-client.js +42 -0
- package/dist/action-client.js.map +1 -0
- package/dist/client.d.ts +5 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -50
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +69 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -0
- package/dist/contracts.js.map +1 -1
- package/dist/management-client.d.ts +26 -0
- package/dist/management-client.d.ts.map +1 -0
- package/dist/management-client.js +185 -0
- package/dist/management-client.js.map +1 -0
- package/dist/messages.d.ts +97 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +142 -0
- package/dist/messages.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +50 -13
- package/dist/server.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,73 +1,95 @@
|
|
|
1
|
-
# @haneinfo/frontend-instances
|
|
2
|
-
|
|
3
|
-
Reusable instance lists, collaboration and import/export pages for React 19 / Next 16.
|
|
4
|
-
Backend packages stay free of React and NestJS is not a dependency of this package.
|
|
5
|
-
|
|
6
|
-
## Mounting
|
|
7
|
-
|
|
8
|
-
Import `renderInstanceListPage`, `renderInstanceDetailPage` and `renderImportPage` from
|
|
9
|
-
`@haneinfo/frontend-instances/server`. Each returns a server-renderable React tree. Supply:
|
|
10
|
-
|
|
11
|
-
- `apiFetch<T>(path, init?)`, using the current authenticated session and an App-owned API URL.
|
|
12
|
-
- `LinkComponent`, `t(LabelKey)`, `getCurrentUser()`, and `basePath`.
|
|
13
|
-
- An App Server Action calling `performInstanceRequest(apiFetch, request)` and returning
|
|
14
|
-
`{ data }` or `{ error: instanceError(error) }` after checking the session.
|
|
15
|
-
- For imports, an authenticated `uploadAction(FormData)` calling `readImportUpload`, plus
|
|
16
|
-
the App's `{ code, label }[]` machine choices.
|
|
17
|
-
|
|
18
|
-
The list additionally takes `machineCode` and promised `searchParams: { query?: string }`.
|
|
19
|
-
The detail takes `id`. The import takes `machines` and `uploadAction`.
|
|
20
|
-
All UI labels are injected. System field labels use `t`; Form labels stay verbatim from the API.
|
|
21
|
-
See `LabelKey` and the complete English/zh-TW mounting examples in
|
|
22
|
-
`validation/equipment-loan/instance-frontend/app/` in the source repository.
|
|
23
|
-
|
|
24
|
-
Add `@source "../../node_modules/@haneinfo/frontend-instances/dist";` to the App's Tailwind
|
|
25
|
-
CSS source declarations. Mount navigation in the App's sidebar, not generated plugin files.
|
|
26
|
-
Set the App's Server Action body limit to allow multipart uploads (the example uses 16 MB).
|
|
27
|
-
The backend HTTP body limit remains App-owned; API rejections are shown in the UI.
|
|
28
|
-
|
|
29
|
-
## Client components and contracts
|
|
30
|
-
|
|
31
|
-
`@haneinfo/frontend-instances/client` exports `ListClient`, `DetailClient`, `ImportClient`,
|
|
32
|
-
`FilterEditor`, `JobStatus`, `JobReport`, `ImportConfirmation` and `prepareFilter`.
|
|
33
|
-
Use the server renderers for normal mounting. `@haneinfo/frontend-instances/contracts` exports
|
|
34
|
-
HTTP-only types and `labelKeys`. The server entry also exports the contracts, `dateRange`,
|
|
35
|
-
`importMapping`, `normalizeQuery`, `performInstanceRequest`, `readImportUpload` and `instanceError`.
|
|
36
|
-
|
|
37
|
-
Lists support PREDICATE/GROUP AND/OR, all eight operators, inclusive date ranges, up to eight
|
|
38
|
-
sort keys, selected columns and offset/limit pagination (1–500). IN uses one literal per line.
|
|
39
|
-
Datetime-local controls are explicitly UTC. Exports use the last applied query, not unapplied
|
|
40
|
-
editor changes. Saved views default to private; sharing controls depend only on `canShare`.
|
|
41
|
-
|
|
42
|
-
Details use pinned subject Form labels and the merged timeline; mentions come only from the
|
|
43
|
-
instance-specific mentionable API. Following comes from the timeline or mutation response.
|
|
44
|
-
Comments are append-only plain text and AI comments have a visible attribution.
|
|
45
|
-
|
|
46
|
-
CSV and xlsx headers are read using csv-parse and ExcelJS on the server, with a 10 MB upload
|
|
47
|
-
bound. The backend performs the complete preflight and authorization. Duplicate keys are
|
|
48
|
-
optional, mapped top-level keys with a five-key maximum. Preflight never confirms an import.
|
|
49
|
-
Confirmation requires a separate button click; global errors and zero valid rows disable it.
|
|
50
|
-
Re-upload to change a completed preflight's mapping.
|
|
51
|
-
|
|
52
|
-
`JobStatus` polls every two seconds, stops on terminal status or failure, and bounds automatic
|
|
53
|
-
polling to 90 requests. A manual refresh resumes polling. Job state is owner-authorized by the
|
|
54
|
-
backend. Binary result bytes and uploaded job params are removed before returning jobs to clients.
|
|
55
|
-
|
|
56
|
-
## Downloads and safety
|
|
57
|
-
|
|
58
|
-
Mount `${basePath}/download?path=...` as an authenticated, no-store streaming GET proxy. Constrain
|
|
59
|
-
the path to `/import-export/(imports|exports)/<id>/file`, forward the current session token to
|
|
60
|
-
that exact backend-provided `downloadPath`, and forward its disposition/type. Never synthesize
|
|
61
|
-
job download paths or put file bytes/tokens in the URL. The App example includes this route.
|
|
62
|
-
|
|
63
|
-
Use real Next Server Actions so same-origin CSRF checks apply. All mutations have an immediate
|
|
64
|
-
in-memory submission lock and disabled pending buttons. The backend remains authoritative for
|
|
65
|
-
authorization and validation; the UI does not recreate RBAC. 401/403/404 display generic localized
|
|
66
|
-
messages without raw API details. All user content is rendered as text nodes, including errors,
|
|
67
|
-
field values, names, and timeline details. No HTML or Markdown rendering is used.
|
|
68
|
-
|
|
69
|
-
No instance creation, submission, approval, rejection or transition controls are included.
|
|
70
|
-
|
|
71
|
-
## Submitter display
|
|
72
|
-
|
|
73
|
-
List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
|
|
1
|
+
# @haneinfo/frontend-instances
|
|
2
|
+
|
|
3
|
+
Reusable instance lists, collaboration and import/export pages for React 19 / Next 16.
|
|
4
|
+
Backend packages stay free of React and NestJS is not a dependency of this package.
|
|
5
|
+
|
|
6
|
+
## Mounting
|
|
7
|
+
|
|
8
|
+
Import `renderInstanceListPage`, `renderInstanceDetailPage` and `renderImportPage` from
|
|
9
|
+
`@haneinfo/frontend-instances/server`. Each returns a server-renderable React tree. Supply:
|
|
10
|
+
|
|
11
|
+
- `apiFetch<T>(path, init?)`, using the current authenticated session and an App-owned API URL.
|
|
12
|
+
- `LinkComponent`, `t(LabelKey)`, `getCurrentUser()`, and `basePath`.
|
|
13
|
+
- An App Server Action calling `performInstanceRequest(apiFetch, request)` and returning
|
|
14
|
+
`{ data }` or `{ error: instanceError(error) }` after checking the session.
|
|
15
|
+
- For imports, an authenticated `uploadAction(FormData)` calling `readImportUpload`, plus
|
|
16
|
+
the App's `{ code, label }[]` machine choices.
|
|
17
|
+
|
|
18
|
+
The list additionally takes `machineCode` and promised `searchParams: { query?: string }`.
|
|
19
|
+
The detail takes `id`. The import takes `machines` and `uploadAction`.
|
|
20
|
+
All UI labels are injected. System field labels use `t`; Form labels stay verbatim from the API.
|
|
21
|
+
See `LabelKey` and the complete English/zh-TW mounting examples in
|
|
22
|
+
`validation/equipment-loan/instance-frontend/app/` in the source repository.
|
|
23
|
+
|
|
24
|
+
Add `@source "../../node_modules/@haneinfo/frontend-instances/dist";` to the App's Tailwind
|
|
25
|
+
CSS source declarations. Mount navigation in the App's sidebar, not generated plugin files.
|
|
26
|
+
Set the App's Server Action body limit to allow multipart uploads (the example uses 16 MB).
|
|
27
|
+
The backend HTTP body limit remains App-owned; API rejections are shown in the UI.
|
|
28
|
+
|
|
29
|
+
## Client components and contracts
|
|
30
|
+
|
|
31
|
+
`@haneinfo/frontend-instances/client` exports `ListClient`, `DetailClient`, `ImportClient`,
|
|
32
|
+
`FilterEditor`, `JobStatus`, `JobReport`, `ImportConfirmation` and `prepareFilter`.
|
|
33
|
+
Use the server renderers for normal mounting. `@haneinfo/frontend-instances/contracts` exports
|
|
34
|
+
HTTP-only types and `labelKeys`. The server entry also exports the contracts, `dateRange`,
|
|
35
|
+
`importMapping`, `normalizeQuery`, `performInstanceRequest`, `readImportUpload` and `instanceError`.
|
|
36
|
+
|
|
37
|
+
Lists support PREDICATE/GROUP AND/OR, all eight operators, inclusive date ranges, up to eight
|
|
38
|
+
sort keys, selected columns and offset/limit pagination (1–500). IN uses one literal per line.
|
|
39
|
+
Datetime-local controls are explicitly UTC. Exports use the last applied query, not unapplied
|
|
40
|
+
editor changes. Saved views default to private; sharing controls depend only on `canShare`.
|
|
41
|
+
|
|
42
|
+
Details use pinned subject Form labels and the merged timeline; mentions come only from the
|
|
43
|
+
instance-specific mentionable API. Following comes from the timeline or mutation response.
|
|
44
|
+
Comments are append-only plain text and AI comments have a visible attribution.
|
|
45
|
+
|
|
46
|
+
CSV and xlsx headers are read using csv-parse and ExcelJS on the server, with a 10 MB upload
|
|
47
|
+
bound. The backend performs the complete preflight and authorization. Duplicate keys are
|
|
48
|
+
optional, mapped top-level keys with a five-key maximum. Preflight never confirms an import.
|
|
49
|
+
Confirmation requires a separate button click; global errors and zero valid rows disable it.
|
|
50
|
+
Re-upload to change a completed preflight's mapping.
|
|
51
|
+
|
|
52
|
+
`JobStatus` polls every two seconds, stops on terminal status or failure, and bounds automatic
|
|
53
|
+
polling to 90 requests. A manual refresh resumes polling. Job state is owner-authorized by the
|
|
54
|
+
backend. Binary result bytes and uploaded job params are removed before returning jobs to clients.
|
|
55
|
+
|
|
56
|
+
## Downloads and safety
|
|
57
|
+
|
|
58
|
+
Mount `${basePath}/download?path=...` as an authenticated, no-store streaming GET proxy. Constrain
|
|
59
|
+
the path to `/import-export/(imports|exports)/<id>/file`, forward the current session token to
|
|
60
|
+
that exact backend-provided `downloadPath`, and forward its disposition/type. Never synthesize
|
|
61
|
+
job download paths or put file bytes/tokens in the URL. The App example includes this route.
|
|
62
|
+
|
|
63
|
+
Use real Next Server Actions so same-origin CSRF checks apply. All mutations have an immediate
|
|
64
|
+
in-memory submission lock and disabled pending buttons. The backend remains authoritative for
|
|
65
|
+
authorization and validation; the UI does not recreate RBAC. 401/403/404 display generic localized
|
|
66
|
+
messages without raw API details. All user content is rendered as text nodes, including errors,
|
|
67
|
+
field values, names, and timeline details. No HTML or Markdown rendering is used.
|
|
68
|
+
|
|
69
|
+
No instance creation, submission, approval, rejection or transition controls are included.
|
|
70
|
+
|
|
71
|
+
## Submitter display
|
|
72
|
+
|
|
73
|
+
List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Delegation and access management (draft 202610-e2a9)
|
|
77
|
+
|
|
78
|
+
`renderDelegationsPage` accepts the same injected page dependencies as instance pages. The App
|
|
79
|
+
mounts it at `/dashboard/delegations` and supplies an authenticated Server Action calling
|
|
80
|
+
`performInstanceRequest`. Administrative controls depend on the successful admin list API;
|
|
81
|
+
only DELEGATION_NOT_FOUND is treated as a denial. Other failures render an error.
|
|
82
|
+
The UTC datetime inputs require an explicit end; backend rules remain authoritative.
|
|
83
|
+
|
|
84
|
+
Pass `showAccess: true` to `renderInstanceDetailPage` to load the access list. `InstanceAccessPanel`
|
|
85
|
+
is exported from the client entry for other hosts. It renders USER/ROLE, READ/WRITE, sources,
|
|
86
|
+
grantor and timestamps. Only API canManage enables management, and only GRANTED rows have
|
|
87
|
+
change/remove controls. Mutations refresh both the list and the enclosing activity timeline.
|
|
88
|
+
Pickers use bounded explicit searches and keyboard combobox selection; user search needs two
|
|
89
|
+
trimmed characters. Every mutation uses the supplied Server Action and a synchronous submit lock.
|
|
90
|
+
|
|
91
|
+
Merge the bilingual catalog fragments in validation/equipment-loan/delegation-access-frontend/app/
|
|
92
|
+
into the host's instances catalog. The new keys include the known error code allowlist.
|
|
93
|
+
`instanceError` only maps recognized codes. Unknown/missing codes, even on 401/403/404, use
|
|
94
|
+
requestFailed. Backend message, diagnostic message and lastError text are never rendered.
|
|
95
|
+
Upgrade common and frontend-shell together so the production filter and API adapter retain code.
|
package/agent-guide.md
CHANGED
|
@@ -1,39 +1,54 @@
|
|
|
1
|
-
# Instance frontend agent guide
|
|
2
|
-
|
|
3
|
-
Use this package to mount shared instance lists, read-only details with collaboration, and
|
|
4
|
-
human-confirmed bulk imports. Read the README for exact entry points and injected dependencies.
|
|
5
|
-
Do not add React or Next dependencies to list-view, collaboration, import-export or background-jobs.
|
|
6
|
-
|
|
7
|
-
## HTTP contracts
|
|
8
|
-
|
|
9
|
-
- List metadata: GET `/list-view/machines/:code/fields`; query: POST the same machine's `/query`.
|
|
10
|
-
- Views: GET/POST `/list-view/views`; PATCH `/list-view/views/:id/sharing`. Preserve `canShare`.
|
|
11
|
-
- Details: GET `/state-machine-instances/:id` uses the pinned subject Form bundle.
|
|
12
|
-
- Timeline: GET `/state-machine-instances/:id/timeline?offset=&limit=50`, including `following`.
|
|
13
|
-
- People: GET `/state-machine-instances/:id/mentionable?q=`, at most twenty eligible people.
|
|
14
|
-
- Collaboration: POST `comments` with `{ body }`, using `@{id}` from the people picker;
|
|
15
|
-
POST/DELETE `follow`. There are no comment edit/delete controls.
|
|
16
|
-
- Imports: GET `/import-export/machines/:code/fields`; POST `/import-export/imports/preflight`
|
|
17
|
-
with file format/base64, header-to-key mapping, and optional duplicateKey (one to five keys).
|
|
18
|
-
- Human confirmation: POST `/import-export/imports/:id/confirm` with `{ confirmed: true }` only
|
|
19
|
-
from an explicit button event, never from polling, render, an effect, or an AI tool.
|
|
20
|
-
- Exports: POST `/import-export/exports` with `{ format, source: { machineCode, query } }`.
|
|
21
|
-
- Jobs: GET `/background-jobs/:id`; downloads use the result's exact `downloadPath` through
|
|
22
|
-
an App-owned authenticated streaming proxy. The API alone decides job ownership and expiry.
|
|
23
|
-
|
|
24
|
-
The minimal types are local HTTP contracts; never import backend runtime packages for types.
|
|
25
|
-
Keep the API adapter and authentication in the App, with Server Action CSRF protection.
|
|
26
|
-
Keep API error values and all user-controlled content as plain text. Do not inspect role names
|
|
27
|
-
to decide sharing or visibility. Use both English and zh-TW translations for new UI text.
|
|
28
|
-
|
|
29
|
-
## Validation
|
|
30
|
-
|
|
31
|
-
Run package build/typecheck/tests and workspace build:graph, lint, specs:lint. Component tests
|
|
32
|
-
render actual HTML, test authorization and escaping, and verify confirmation and parameters.
|
|
33
|
-
Use the equipment-loan validation overlay for full production-build/browser acceptance. Always
|
|
34
|
-
restore and finish it, including original manifest/lockfile bytes and dependency installation.
|
|
35
|
-
Do not release, version, approve a decision, or commit as part of frontend validation.
|
|
36
|
-
|
|
37
|
-
## Submitter display
|
|
38
|
-
|
|
39
|
-
List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
|
|
1
|
+
# Instance frontend agent guide
|
|
2
|
+
|
|
3
|
+
Use this package to mount shared instance lists, read-only details with collaboration, and
|
|
4
|
+
human-confirmed bulk imports. Read the README for exact entry points and injected dependencies.
|
|
5
|
+
Do not add React or Next dependencies to list-view, collaboration, import-export or background-jobs.
|
|
6
|
+
|
|
7
|
+
## HTTP contracts
|
|
8
|
+
|
|
9
|
+
- List metadata: GET `/list-view/machines/:code/fields`; query: POST the same machine's `/query`.
|
|
10
|
+
- Views: GET/POST `/list-view/views`; PATCH `/list-view/views/:id/sharing`. Preserve `canShare`.
|
|
11
|
+
- Details: GET `/state-machine-instances/:id` uses the pinned subject Form bundle.
|
|
12
|
+
- Timeline: GET `/state-machine-instances/:id/timeline?offset=&limit=50`, including `following`.
|
|
13
|
+
- People: GET `/state-machine-instances/:id/mentionable?q=`, at most twenty eligible people.
|
|
14
|
+
- Collaboration: POST `comments` with `{ body }`, using `@{id}` from the people picker;
|
|
15
|
+
POST/DELETE `follow`. There are no comment edit/delete controls.
|
|
16
|
+
- Imports: GET `/import-export/machines/:code/fields`; POST `/import-export/imports/preflight`
|
|
17
|
+
with file format/base64, header-to-key mapping, and optional duplicateKey (one to five keys).
|
|
18
|
+
- Human confirmation: POST `/import-export/imports/:id/confirm` with `{ confirmed: true }` only
|
|
19
|
+
from an explicit button event, never from polling, render, an effect, or an AI tool.
|
|
20
|
+
- Exports: POST `/import-export/exports` with `{ format, source: { machineCode, query } }`.
|
|
21
|
+
- Jobs: GET `/background-jobs/:id`; downloads use the result's exact `downloadPath` through
|
|
22
|
+
an App-owned authenticated streaming proxy. The API alone decides job ownership and expiry.
|
|
23
|
+
|
|
24
|
+
The minimal types are local HTTP contracts; never import backend runtime packages for types.
|
|
25
|
+
Keep the API adapter and authentication in the App, with Server Action CSRF protection.
|
|
26
|
+
Keep API error values and all user-controlled content as plain text. Do not inspect role names
|
|
27
|
+
to decide sharing or visibility. Use both English and zh-TW translations for new UI text.
|
|
28
|
+
|
|
29
|
+
## Validation
|
|
30
|
+
|
|
31
|
+
Run package build/typecheck/tests and workspace build:graph, lint, specs:lint. Component tests
|
|
32
|
+
render actual HTML, test authorization and escaping, and verify confirmation and parameters.
|
|
33
|
+
Use the equipment-loan validation overlay for full production-build/browser acceptance. Always
|
|
34
|
+
restore and finish it, including original manifest/lockfile bytes and dependency installation.
|
|
35
|
+
Do not release, version, approve a decision, or commit as part of frontend validation.
|
|
36
|
+
|
|
37
|
+
## Submitter display
|
|
38
|
+
|
|
39
|
+
List `$submitterId` cells and the detail submitter field display `submitterName ?? submitterId`. Both are plain React text nodes, including markup-like names. The HTTP contracts include nullable submitterName; older responses without it also fall back to the id. No new translation key, lookup request or App host-page change is required. Upgrade the state-machine and list-view backends with this frontend for names; otherwise the id remains visible.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Delegations, access lists and safe errors
|
|
43
|
+
|
|
44
|
+
Implemented against draft 202610-e2a9; approval/versioning remain with the maintainer.
|
|
45
|
+
Use renderDelegationsPage and showAccess on renderInstanceDetailPage as documented in README.
|
|
46
|
+
Lookup routes are GET /identity/users/lookup?q= (two trimmed characters) and
|
|
47
|
+
GET /rbac/roles/lookup?q= (empty allowed), at most twenty projected public labels.
|
|
48
|
+
GET instance access and the read-only list_instance_access MCP result now include canManage.
|
|
49
|
+
Never infer authority from role names or HTTP status. Admin delegation visibility comes from
|
|
50
|
+
GET /delegations; only DELEGATION_NOT_FOUND means this operation is unavailable to the caller.
|
|
51
|
+
Use a same-origin authenticated Server Action for writes. Permanent sources have no controls.
|
|
52
|
+
Translate only exact known codes in src/messages.ts; never show backend prose or job lastError.
|
|
53
|
+
Both current pages and the new management pages follow this policy. Merge the complete English
|
|
54
|
+
and zh-TW fragments with the existing host catalogs before building an overlay.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ActionError, InstanceAction, InstanceRequest, Labels } from './contracts.js';
|
|
2
|
+
export declare class RequestFailure extends Error {
|
|
3
|
+
readonly reason: ActionError;
|
|
4
|
+
constructor(reason: ActionError);
|
|
5
|
+
}
|
|
6
|
+
export declare function call<T>(action: InstanceAction, request: InstanceRequest): Promise<T>;
|
|
7
|
+
export declare function useTask(): {
|
|
8
|
+
pending: boolean;
|
|
9
|
+
error: ActionError | undefined;
|
|
10
|
+
run: (work: () => Promise<void>) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export declare function ErrorMessage({ error, labels }: {
|
|
13
|
+
error?: ActionError;
|
|
14
|
+
labels: Labels;
|
|
15
|
+
}): import("react").JSX.Element | null;
|
|
16
|
+
//# sourceMappingURL=action-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-client.d.ts","sourceRoot":"","sources":["../src/action-client.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC3F,qBAAa,cAAe,SAAQ,KAAK;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW;gBAAnB,MAAM,EAAE,WAAW;CAGzC;AACD,wBAAsB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAI1F;AACD,wBAAgB,OAAO;;;gBAII,MAAM,OAAO,CAAC,IAAI,CAAC;EAe7C;AACD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,sCAMtF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
export class RequestFailure extends Error {
|
|
5
|
+
constructor(reason) {
|
|
6
|
+
super(reason.key);
|
|
7
|
+
this.reason = reason;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export async function call(action, request) {
|
|
11
|
+
const result = await action(request);
|
|
12
|
+
if (result.error)
|
|
13
|
+
throw new RequestFailure(result.error);
|
|
14
|
+
return result.data;
|
|
15
|
+
}
|
|
16
|
+
export function useTask() {
|
|
17
|
+
const lock = useRef(false);
|
|
18
|
+
const [pending, setPending] = useState(false);
|
|
19
|
+
const [error, setError] = useState();
|
|
20
|
+
async function run(work) {
|
|
21
|
+
if (lock.current)
|
|
22
|
+
return;
|
|
23
|
+
lock.current = true;
|
|
24
|
+
setPending(true);
|
|
25
|
+
setError(undefined);
|
|
26
|
+
try {
|
|
27
|
+
await work();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
setError(error instanceof RequestFailure ? error.reason : { key: 'requestFailed' });
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
lock.current = false;
|
|
34
|
+
setPending(false);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { pending, error, run };
|
|
38
|
+
}
|
|
39
|
+
export function ErrorMessage({ error, labels }) {
|
|
40
|
+
return error ? (_jsx("p", { role: "alert", className: "break-words text-destructive", children: labels[error.key] })) : null;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=action-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-client.js","sourceRoot":"","sources":["../src/action-client.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEzC,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAqB,MAAmB;QACtC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QADC,WAAM,GAAN,MAAM,CAAa;IAExC,CAAC;CACF;AACD,MAAM,CAAC,KAAK,UAAU,IAAI,CAAI,MAAsB,EAAE,OAAwB;IAC5E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,KAAK;QAAE,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC,IAAS,CAAC;AAC1B,CAAC;AACD,MAAM,UAAU,OAAO;IACrB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAe,CAAC;IAClD,KAAK,UAAU,GAAG,CAAC,IAAyB;QAC1C,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,UAAU,CAAC,IAAI,CAAC,CAAC;QACjB,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,YAAY,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC,CAAC;QACtF,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAA2C;IACrF,OAAO,KAAK,CAAC,CAAC,CAAC,CACb,YAAG,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,8BAA8B,YACrD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,GAChB,CACL,CAAC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { AccessList } from './contracts.js';
|
|
1
2
|
import { type Condition, type FormField, type ImportField, type InstanceAction, type InstanceQuery, type Job, type JobResult, type Labels, type ListResult, type QueryField, type SavedView, type Timeline, type UploadAction } from './contracts.js';
|
|
3
|
+
export { call, ErrorMessage, useTask } from './action-client.js';
|
|
4
|
+
export { InstanceAccessPanel } from './management-client.js';
|
|
2
5
|
export declare function FilterEditor({ condition, fields, labels, onChange, depth, }: {
|
|
3
6
|
condition: Condition;
|
|
4
7
|
fields: QueryField[];
|
|
@@ -18,7 +21,7 @@ export declare function ListClient({ fields, initial, initialQuery, views: initi
|
|
|
18
21
|
labels: Labels;
|
|
19
22
|
action: InstanceAction;
|
|
20
23
|
}): import("react").JSX.Element;
|
|
21
|
-
export declare function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, labels: t, action, }: {
|
|
24
|
+
export declare function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, initialAccess, labels: t, action, }: {
|
|
22
25
|
id: string;
|
|
23
26
|
data: Record<string, unknown>;
|
|
24
27
|
fields: FormField[];
|
|
@@ -28,6 +31,7 @@ export declare function DetailClient({ id, data, fields, state, submitterId, sub
|
|
|
28
31
|
createdAt: string;
|
|
29
32
|
initial: Timeline;
|
|
30
33
|
initialNumbers?: string[];
|
|
34
|
+
initialAccess?: AccessList;
|
|
31
35
|
labels: Labels;
|
|
32
36
|
action: InstanceAction;
|
|
33
37
|
}): import("react").JSX.Element;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -1,56 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Button, Input, ListPagination } from '@haneinfo/frontend-shell';
|
|
4
4
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
5
|
+
import { call, ErrorMessage, RequestFailure, useTask } from './action-client.js';
|
|
5
6
|
import { operators, } from './contracts.js';
|
|
7
|
+
import { InstanceAccessPanel } from './management-client.js';
|
|
8
|
+
import { errorKey } from './messages.js';
|
|
9
|
+
export { call, ErrorMessage, useTask } from './action-client.js';
|
|
6
10
|
import { canPoll, dateRange, fieldValue, importMapping, listHref, textValue } from './query.js';
|
|
11
|
+
export { InstanceAccessPanel } from './management-client.js';
|
|
7
12
|
const box = 'rounded-md border bg-card p-4';
|
|
8
13
|
const selectClass = 'min-w-0 rounded-md border bg-background px-3 py-2 text-foreground';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
super(reason.key);
|
|
12
|
-
this.reason = reason;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
async function call(action, request) {
|
|
16
|
-
const result = await action(request);
|
|
17
|
-
if (result.error)
|
|
18
|
-
throw new RequestFailure(result.error);
|
|
19
|
-
return result.data;
|
|
20
|
-
}
|
|
21
|
-
function useTask() {
|
|
22
|
-
const lock = useRef(false);
|
|
23
|
-
const [pending, setPending] = useState(false);
|
|
24
|
-
const [error, setError] = useState();
|
|
25
|
-
async function run(work) {
|
|
26
|
-
if (lock.current)
|
|
27
|
-
return;
|
|
28
|
-
lock.current = true;
|
|
29
|
-
setPending(true);
|
|
30
|
-
setError(undefined);
|
|
31
|
-
try {
|
|
32
|
-
await work();
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
setError(error instanceof RequestFailure
|
|
36
|
-
? error.reason
|
|
37
|
-
: {
|
|
38
|
-
key: error instanceof Error && error.message === 'invalidInput'
|
|
39
|
-
? 'invalidInput'
|
|
40
|
-
: 'requestFailed',
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
lock.current = false;
|
|
45
|
-
setPending(false);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return { pending, error, run };
|
|
49
|
-
}
|
|
50
|
-
function ErrorMessage({ error, labels }) {
|
|
51
|
-
return error ? (_jsxs("p", { role: "alert", className: "break-words text-destructive", children: [labels[error.key], error.message && `: ${error.message}`] })) : null;
|
|
52
|
-
}
|
|
53
|
-
const inaccessible = (error) => error && ['unauthorized', 'forbidden', 'notFound'].includes(error.key);
|
|
14
|
+
const inaccessible = (error) => error &&
|
|
15
|
+
['unauthorized', 'forbidden', 'notFound', 'FORBIDDEN', 'INSTANCE_NOT_FOUND'].includes(error.key);
|
|
54
16
|
function labelOf(field, labels) {
|
|
55
17
|
const system = {
|
|
56
18
|
$state: 'state',
|
|
@@ -264,7 +226,7 @@ function DataFields({ fields, data }) {
|
|
|
264
226
|
.filter((f) => (f.id ?? f.key) in data)
|
|
265
227
|
.map((field) => (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "text-sm text-muted-foreground", children: field.label }), _jsx("dd", { className: "whitespace-pre-wrap break-words", children: textValue(data[field.id ?? field.key]) })] }, field.id ?? field.key))) }));
|
|
266
228
|
}
|
|
267
|
-
export function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, labels: t, action, }) {
|
|
229
|
+
export function DetailClient({ id, data, fields, state, submitterId, submitterName, createdAt, initial, initialNumbers, initialAccess, labels: t, action, }) {
|
|
268
230
|
const task = useTask();
|
|
269
231
|
const [timeline, setTimeline] = useState(initial), [body, setBody] = useState('');
|
|
270
232
|
const [q, setQ] = useState(''), [people, setPeople] = useState(), [selected, setSelected] = useState('');
|
|
@@ -285,7 +247,7 @@ export function DetailClient({ id, data, fields, state, submitterId, submitterNa
|
|
|
285
247
|
: next);
|
|
286
248
|
};
|
|
287
249
|
const numbers = timeline.items.filter((e) => e.kind === 'NUMBER');
|
|
288
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-4", "aria-busy": task.pending, children: [_jsx(ErrorMessage, { error: task.error, labels: t }), task.pending && _jsx("p", { role: "status", children: t.loading }), _jsxs("section", { className: `${box} flex flex-col gap-4`, children: [_jsxs("dl", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { children: [_jsx("dt", { children: t.state }), _jsx("dd", { children: state })] }), _jsxs("div", { children: [_jsx("dt", { children: t.number }), _jsx("dd", { children: initialNumbers?.length
|
|
250
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-4", "aria-busy": task.pending, children: [_jsx(ErrorMessage, { error: task.error, labels: t }), task.pending && _jsx("p", { role: "status", children: t.loading }), initialAccess && (_jsx(InstanceAccessPanel, { id: id, initial: initialAccess, labels: t, action: action, onChanged: () => loadTimeline(false) })), _jsxs("section", { className: `${box} flex flex-col gap-4`, children: [_jsxs("dl", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("div", { children: [_jsx("dt", { children: t.state }), _jsx("dd", { children: state })] }), _jsxs("div", { children: [_jsx("dt", { children: t.number }), _jsx("dd", { children: initialNumbers?.length
|
|
289
251
|
? initialNumbers.join(', ')
|
|
290
252
|
: numbers.length
|
|
291
253
|
? numbers.map((e) => textValue(e.details.number)).join(', ')
|
|
@@ -368,7 +330,7 @@ export function JobStatus({ initial, labels: t, action, downloadBase, onComplete
|
|
|
368
330
|
}, [initial, action, attempt]);
|
|
369
331
|
if (inaccessible(error))
|
|
370
332
|
return _jsx(ErrorMessage, { error: error, labels: t });
|
|
371
|
-
return (_jsxs("section", { className: `${box} flex flex-col gap-2`, "aria-label": t.job, children: [_jsxs("p", { role: "status", children: [t.job, ": ", t[job.status], " (", Math.max(0, Math.min(100, job.progress)), "%)"] }), _jsx("progress", { "aria-label": t.job, value: Math.max(0, Math.min(100, job.progress)), max: 100, className: "w-full" }), _jsx(ErrorMessage, { error: error, labels: t }), job.lastError && (_jsx("p", { role: "alert", className: "break-words text-destructive", children:
|
|
333
|
+
return (_jsxs("section", { className: `${box} flex flex-col gap-2`, "aria-label": t.job, children: [_jsxs("p", { role: "status", children: [t.job, ": ", t[job.status], " (", Math.max(0, Math.min(100, job.progress)), "%)"] }), _jsx("progress", { "aria-label": t.job, value: Math.max(0, Math.min(100, job.progress)), max: 100, className: "w-full" }), _jsx(ErrorMessage, { error: error, labels: t }), job.lastError && (_jsx("p", { role: "alert", className: "break-words text-destructive", children: t.requestFailed })), stopped && (_jsxs("div", { children: [_jsx("p", { children: t.pollStopped }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setAttempt(attempt + 1), children: t.retry })] })), job.status === 'SUCCEEDED' && job.result && (_jsx(JobReport, { result: job.result, labels: t, downloadBase: downloadBase }))] }));
|
|
372
334
|
}
|
|
373
335
|
export function JobReport({ result, labels: t, downloadBase, }) {
|
|
374
336
|
const failures = result.kind === 'preflight'
|
|
@@ -376,7 +338,9 @@ export function JobReport({ result, labels: t, downloadBase, }) {
|
|
|
376
338
|
: Array.isArray(result.failed)
|
|
377
339
|
? result.failed
|
|
378
340
|
: [];
|
|
379
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("span", { children: [t.total, ": ", result.total ?? result.rowCount ?? 0] }), result.kind === 'preflight' && (_jsxs("span", { children: [t.valid, ": ", result.valid ?? 0] })), result.kind === 'import' && (_jsxs("span", { children: [t.success, ": ", result.imported?.length ?? 0] })), result.kind !== 'export' && (_jsxs("span", { children: [t.failed, ": ", Array.isArray(result.failed) ? result.failed.length : (result.failed ?? 0)] }))] }), (result.errors ?? []).map((error) => (
|
|
341
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("span", { children: [t.total, ": ", result.total ?? result.rowCount ?? 0] }), result.kind === 'preflight' && (_jsxs("span", { children: [t.valid, ": ", result.valid ?? 0] })), result.kind === 'import' && (_jsxs("span", { children: [t.success, ": ", result.imported?.length ?? 0] })), result.kind !== 'export' && (_jsxs("span", { children: [t.failed, ": ", Array.isArray(result.failed) ? result.failed.length : (result.failed ?? 0)] }))] }), (result.errors ?? []).map((error) => (_jsx("p", { className: "break-words text-destructive", children: t[errorKey(error.code)] }, `${error.code}-${error.path}-${error.message}`))), !!failures.length && (_jsx("div", { className: "max-h-96 overflow-auto", children: _jsxs("table", { className: "w-full text-left text-sm", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t.row }), _jsx("th", { children: t.value }), _jsx("th", { children: t.code }), _jsx("th", { children: t.reason })] }) }), _jsx("tbody", { children: failures.map((row) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "p-2", children: row.rowNumber }), _jsx("td", { className: "max-w-64 break-words p-2", children: row.data ? textValue(row.data) : '—' }), _jsx("td", { className: "p-2", children: row.errors
|
|
342
|
+
.map((e) => (errorKey(e.code) === 'requestFailed' ? '—' : e.code))
|
|
343
|
+
.join(', ') }), _jsx("td", { className: "max-w-64 break-words p-2", children: row.errors.map((e) => t[errorKey(e.code)]).join('; ') })] }, row.rowNumber))) })] }) })), result.downloadPath && (_jsx("a", { className: "underline", href: `${downloadBase}?${new URLSearchParams({ path: result.downloadPath })}`, children: result.kind === 'export' ? t.download : t.downloadErrors }))] }));
|
|
380
344
|
}
|
|
381
345
|
export function ImportClient({ machines, fields: initialFields, labels: t, action, uploadAction, downloadBase, }) {
|
|
382
346
|
const task = useTask();
|