@ultimat3/admin 30.8.0 → 31.0.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/CLAUDE.md +3 -3
- package/README.md +6 -4
- package/package.json +18 -17
- package/src/adapter.ts +5 -0
- package/src/admin.ts +6 -0
- package/src/audit-pg.ts +2 -2
- package/src/audit.ts +6 -6
- package/src/dev/panel-timeline.ts +24 -8
- package/src/dev/sql-runner.ts +21 -5
- package/src/entity-columns.ts +46 -14
- package/src/error-groups-sink.ts +5 -2
- package/src/error-groups-store.ts +1 -0
- package/src/index.ts +48 -210
- package/src/jobs/operator.ts +2 -2
- package/src/layout.tsx +1 -1
- package/src/localized-app.ts +29 -0
- package/src/mcp-dispatch.ts +221 -0
- package/src/mcp-tools.ts +26 -6
- package/src/mcp.ts +28 -217
- package/src/registry.ts +2 -0
- package/src/routes.ts +9 -1
- package/src/screen-system.tsx +6 -5
package/src/index.ts
CHANGED
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
// Explicit exports only — a barrel that re-exports everything is how internal helpers become
|
|
3
3
|
// someone's dependency.
|
|
4
4
|
|
|
5
|
-
export { AdminActionForm, type AdminActionFormProps } from './action-form';
|
|
6
5
|
export {
|
|
7
|
-
ACTION_NOT_APPLICABLE_REASON,
|
|
8
6
|
type ActionGateInput,
|
|
9
7
|
type AdminActionButton,
|
|
10
8
|
actionButtons,
|
|
11
|
-
actionDecisions,
|
|
12
9
|
adminPermissionForAction,
|
|
13
10
|
decideAction,
|
|
14
11
|
type InvokeInput,
|
|
@@ -16,30 +13,9 @@ export {
|
|
|
16
13
|
invokeAdminAction,
|
|
17
14
|
permissionsForAction,
|
|
18
15
|
} from './action-gate';
|
|
19
|
-
export {
|
|
20
|
-
ACTION_INPUT_PREFIX,
|
|
21
|
-
type ActionInputControl,
|
|
22
|
-
type ActionInputField,
|
|
23
|
-
actionInputFields,
|
|
24
|
-
decodeActionInput,
|
|
25
|
-
} from './action-input';
|
|
26
16
|
export { invokeRowAction, type RowActionInput } from './action-row';
|
|
27
|
-
export {
|
|
28
|
-
|
|
29
|
-
ACTION_PARAM,
|
|
30
|
-
AdminActions,
|
|
31
|
-
type AdminActionsProps,
|
|
32
|
-
actionFormHref,
|
|
33
|
-
BATCH_OPERATION,
|
|
34
|
-
DELETE_OPERATION,
|
|
35
|
-
OPERATION_FIELD,
|
|
36
|
-
} from './actions';
|
|
37
|
-
export {
|
|
38
|
-
type AdminRequestActor,
|
|
39
|
-
ANONYMOUS_ADMIN_ACTOR,
|
|
40
|
-
adminActorFrom,
|
|
41
|
-
requestActor,
|
|
42
|
-
} from './actor';
|
|
17
|
+
export { AdminActions, type AdminActionsProps } from './actions';
|
|
18
|
+
export { adminActorFrom } from './actor';
|
|
43
19
|
export {
|
|
44
20
|
type AdminApp,
|
|
45
21
|
type AdminAuth,
|
|
@@ -58,12 +34,9 @@ export {
|
|
|
58
34
|
type AiPanesOptions,
|
|
59
35
|
type AiRunner,
|
|
60
36
|
aiPanes,
|
|
61
|
-
type GatewayAdapter,
|
|
62
37
|
runAiPane,
|
|
63
38
|
} from './ai-panes';
|
|
64
39
|
export {
|
|
65
|
-
AUDIT_PAGE_DEFAULT,
|
|
66
|
-
AUDIT_PAGE_MAX,
|
|
67
40
|
type AuditCursor,
|
|
68
41
|
type AuditDraft,
|
|
69
42
|
type AuditEntry,
|
|
@@ -72,25 +45,15 @@ export {
|
|
|
72
45
|
type AuditLogOptions,
|
|
73
46
|
type AuditOutcome,
|
|
74
47
|
type AuditQuery,
|
|
75
|
-
type AuditSink,
|
|
76
48
|
auditCursorOf,
|
|
77
|
-
auditEntry,
|
|
78
49
|
auditEntryMatches,
|
|
79
50
|
auditWindow,
|
|
80
51
|
deniedDraft,
|
|
81
52
|
diffRows,
|
|
82
53
|
memoryAuditLog,
|
|
83
54
|
} from './audit';
|
|
84
|
-
export {
|
|
85
|
-
|
|
86
|
-
postgresAuditLog,
|
|
87
|
-
} from './audit-pg';
|
|
88
|
-
export {
|
|
89
|
-
type AuditRequest,
|
|
90
|
-
auditHref,
|
|
91
|
-
auditRequestOf,
|
|
92
|
-
HISTORY_PARAM,
|
|
93
|
-
} from './audit-request';
|
|
55
|
+
export { type PostgresAuditLogOptions, postgresAuditLog } from './audit-pg';
|
|
56
|
+
export { type AuditRequest, auditHref, auditRequestOf, HISTORY_PARAM } from './audit-request';
|
|
94
57
|
// The audit table's DDL is `@ultimat3/admin/schema`'s alone — one import path for one statement.
|
|
95
58
|
export {
|
|
96
59
|
type AdminActor,
|
|
@@ -100,36 +63,23 @@ export {
|
|
|
100
63
|
type AdminSubject,
|
|
101
64
|
adminAllowed,
|
|
102
65
|
adminDenied,
|
|
103
|
-
anonymousAuthz,
|
|
104
66
|
decideAll,
|
|
105
67
|
expandPermissions,
|
|
106
|
-
isAllowed,
|
|
107
68
|
staticAuthz,
|
|
108
69
|
} from './authz';
|
|
109
70
|
export {
|
|
110
71
|
type AdminBatchAnswer,
|
|
111
72
|
type AdminBatchInput,
|
|
112
73
|
type AdminBatchResult,
|
|
113
|
-
BATCH_NOT_OFFERED_REASON,
|
|
114
74
|
type BatchEnqueue,
|
|
115
75
|
type BatchRow,
|
|
116
76
|
type BatchRowOutcome,
|
|
117
77
|
type BatchSelection,
|
|
118
|
-
batchConfirmationToken,
|
|
119
78
|
batchPlan,
|
|
120
79
|
MAX_BATCH_QUEUED_ROWS,
|
|
121
80
|
MAX_BATCH_ROWS,
|
|
122
81
|
runAdminBatch,
|
|
123
82
|
} from './batch';
|
|
124
|
-
export {
|
|
125
|
-
AdminBatchBar,
|
|
126
|
-
type AdminBatchBarProps,
|
|
127
|
-
} from './batch-bar';
|
|
128
|
-
export {
|
|
129
|
-
ADMIN_BATCH_JOB,
|
|
130
|
-
batchEnqueue,
|
|
131
|
-
} from './batch-job';
|
|
132
|
-
export { BATCH_MATCHING_REASON, matchingConfirmationToken } from './batch-matching';
|
|
133
83
|
export { BATCH_QUEUED_REASON } from './batch-queue';
|
|
134
84
|
export {
|
|
135
85
|
type AdminClaimData,
|
|
@@ -151,37 +101,26 @@ export {
|
|
|
151
101
|
type ListResult,
|
|
152
102
|
permissionsForOperation,
|
|
153
103
|
} from './crud';
|
|
154
|
-
export {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
describeAdmin,
|
|
104
|
+
export type {
|
|
105
|
+
AdminActionDescription,
|
|
106
|
+
AdminDescription,
|
|
107
|
+
AdminResourceDescription,
|
|
108
|
+
AdminRouteDescription,
|
|
109
|
+
AdminScopeDescription,
|
|
110
|
+
AdminSectionDescription,
|
|
162
111
|
} from './describe';
|
|
163
|
-
export {
|
|
164
|
-
|
|
165
|
-
type AdminDetailProps,
|
|
166
|
-
} from './detail';
|
|
167
|
-
export {
|
|
168
|
-
type AdminColumnFacts,
|
|
169
|
-
type AdminColumnReference,
|
|
170
|
-
adminColumnsOf,
|
|
171
|
-
adminSealedColumnsOf,
|
|
172
|
-
} from './entity-columns';
|
|
112
|
+
export { AdminDetail, type AdminDetailProps } from './detail';
|
|
113
|
+
export type { AdminColumnFacts, AdminColumnReference } from './entity-columns';
|
|
173
114
|
// The screens only. The sink and its stores are `@ultimat3/admin/error-groups`'s alone: every role
|
|
174
115
|
// installs the sink, and this barrel would carry every admin screen into a worker's graph.
|
|
175
116
|
export { ERRORS_READ, type ErrorGroupsPagesOptions, errorGroupsPages } from './error-groups-pages';
|
|
117
|
+
export type { AdminErrorCode } from './errors';
|
|
176
118
|
// The /_x dashboard is NOT re-exported here — it has its own door, `@ultimat3/admin/dev`, so a
|
|
177
119
|
// host that only mounts the dev panels never loads a production admin component.
|
|
178
120
|
export {
|
|
179
|
-
ADMIN_ERROR_CODES,
|
|
180
|
-
ADMIN_ERROR_TITLES,
|
|
181
121
|
AdminActionDuplicateError,
|
|
182
122
|
AdminActionNotApplicableError,
|
|
183
123
|
AdminEntityUnknownError,
|
|
184
|
-
type AdminErrorCode,
|
|
185
124
|
type AdminErrorParts,
|
|
186
125
|
AdminFieldUnsupportedError,
|
|
187
126
|
AdminFilterInvalidError,
|
|
@@ -190,7 +129,6 @@ export {
|
|
|
190
129
|
AdminPageUnguardedError,
|
|
191
130
|
AdminPolicyMissingError,
|
|
192
131
|
AdminRepoUnboundError,
|
|
193
|
-
adminErrorFrom,
|
|
194
132
|
DevDashboardInProdError,
|
|
195
133
|
DevSourceUnavailableError,
|
|
196
134
|
} from './errors';
|
|
@@ -199,59 +137,21 @@ export {
|
|
|
199
137
|
type AdminFieldType,
|
|
200
138
|
type AdminFilterKind,
|
|
201
139
|
type AdminWidget,
|
|
202
|
-
fieldTypeFromColumn,
|
|
203
140
|
filterable,
|
|
204
|
-
filterKindFor,
|
|
205
141
|
listable,
|
|
206
142
|
searchable,
|
|
207
143
|
sortable,
|
|
208
|
-
WIDGET_BY_FIELD_TYPE,
|
|
209
|
-
widgetFor,
|
|
210
144
|
} from './fields';
|
|
211
|
-
export { AdminForm, type AdminFormProps
|
|
212
|
-
export {
|
|
145
|
+
export { AdminForm, type AdminFormProps } from './form';
|
|
146
|
+
export { posted } from './form-decode';
|
|
213
147
|
export { JOB_MANAGE, JOB_READ } from './jobs/job-actions';
|
|
214
|
-
export {
|
|
215
|
-
export { JOBS_PATH, jobRowScope } from './jobs/job-resources';
|
|
148
|
+
export { jobRowScope } from './jobs/job-resources';
|
|
216
149
|
export { AdminLayout, type AdminLayoutProps, adminActorLabel } from './layout';
|
|
217
150
|
export { AdminList, type AdminListProps } from './list';
|
|
218
|
-
export { type
|
|
219
|
-
export {
|
|
220
|
-
export {
|
|
221
|
-
|
|
222
|
-
checkedFilter,
|
|
223
|
-
declaredFilters,
|
|
224
|
-
FILTER_PREFIX,
|
|
225
|
-
filterOpsOf,
|
|
226
|
-
filterParam,
|
|
227
|
-
knownFilterParams,
|
|
228
|
-
} from './list-filters';
|
|
229
|
-
export {
|
|
230
|
-
CURSOR_PARAM,
|
|
231
|
-
type ListLocation,
|
|
232
|
-
listHref,
|
|
233
|
-
pageRequestOf,
|
|
234
|
-
SCOPE_PARAM,
|
|
235
|
-
SORT_PARAM,
|
|
236
|
-
} from './list-request';
|
|
237
|
-
export {
|
|
238
|
-
type AdminListRequest,
|
|
239
|
-
findRow,
|
|
240
|
-
type ListWhere,
|
|
241
|
-
listWhere,
|
|
242
|
-
rowWhere,
|
|
243
|
-
scopeCounts,
|
|
244
|
-
scopeNamed,
|
|
245
|
-
} from './list-scope';
|
|
246
|
-
export { AdminScopeTabs, type AdminScopeTabsProps } from './list-scope-tabs';
|
|
247
|
-
export {
|
|
248
|
-
type AdminMcpActor,
|
|
249
|
-
type AdminMcpOptions,
|
|
250
|
-
type AdminToolResult,
|
|
251
|
-
adminMcp,
|
|
252
|
-
callAdminTool,
|
|
253
|
-
type McpInput,
|
|
254
|
-
} from './mcp';
|
|
151
|
+
export { type ListLocation, listHref, pageRequestOf } from './list-request';
|
|
152
|
+
export { type AdminListRequest, findRow, type ListWhere, listWhere } from './list-scope';
|
|
153
|
+
export { type AdminMcpActor, type AdminMcpOptions, adminMcp } from './mcp';
|
|
154
|
+
export { type AdminToolResult, callAdminTool, type McpInput } from './mcp-dispatch';
|
|
255
155
|
export { adminTokenScopes, adminToolScopes } from './mcp-scopes';
|
|
256
156
|
export {
|
|
257
157
|
type AdminMcpTool,
|
|
@@ -259,40 +159,21 @@ export {
|
|
|
259
159
|
type AdminToolKind,
|
|
260
160
|
adminMcpTools,
|
|
261
161
|
adminToolCatalog,
|
|
262
|
-
adminToolDecisions,
|
|
263
162
|
} from './mcp-tools';
|
|
264
|
-
export {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
adminMounts,
|
|
268
|
-
clearAdminMounts,
|
|
269
|
-
registerAdminMount,
|
|
270
|
-
} from './mounts';
|
|
271
|
-
export { adminNav, type NavGroup, type NavItem, type NavOptions, visibleNav } from './nav';
|
|
272
|
-
export { AdminPageDenied, auditRefusal } from './page-guard';
|
|
163
|
+
export { ADMIN_MOUNTS, adminMounts, clearAdminMounts } from './mounts';
|
|
164
|
+
export { type NavGroup, type NavItem, type NavOptions, visibleNav } from './nav';
|
|
165
|
+
export { AdminPageDenied } from './page-guard';
|
|
273
166
|
export {
|
|
274
167
|
type AdminCustomPage,
|
|
275
168
|
type AdminPageComponent,
|
|
276
169
|
type AdminPageProps,
|
|
277
|
-
pageNavItems,
|
|
278
170
|
pagePermissions,
|
|
279
171
|
pageRoutes,
|
|
280
172
|
} from './pages';
|
|
281
|
-
export {
|
|
282
|
-
type AdminCursor,
|
|
283
|
-
type AdminPage,
|
|
284
|
-
decodeAdminCursor,
|
|
285
|
-
encodeAdminCursor,
|
|
286
|
-
fetchPage,
|
|
287
|
-
listQuery,
|
|
288
|
-
type PageRequest,
|
|
289
|
-
pageFrom,
|
|
290
|
-
} from './pagination';
|
|
173
|
+
export { type AdminCursor, type AdminPage, type PageRequest, pageFrom } from './pagination';
|
|
291
174
|
export {
|
|
292
175
|
ADMIN_DESTROY,
|
|
293
|
-
ADMIN_IMPERSONATE,
|
|
294
176
|
ADMIN_OPERATION_RULES,
|
|
295
|
-
ADMIN_OPERATIONS,
|
|
296
177
|
ADMIN_PERMISSION_SPEC,
|
|
297
178
|
ADMIN_PERMISSIONS,
|
|
298
179
|
ADMIN_READ,
|
|
@@ -300,8 +181,6 @@ export {
|
|
|
300
181
|
type AdminOperation,
|
|
301
182
|
type AdminPermission,
|
|
302
183
|
type AdminPermissionRule,
|
|
303
|
-
adminPermissionFor,
|
|
304
|
-
CONFIRMATION_REQUIRED_REASON,
|
|
305
184
|
confirmationToken,
|
|
306
185
|
entityPermissionFor,
|
|
307
186
|
isDestructive,
|
|
@@ -333,64 +212,38 @@ export {
|
|
|
333
212
|
type AdminSort,
|
|
334
213
|
type AdminTable,
|
|
335
214
|
type AdminTableRead,
|
|
336
|
-
computedRow,
|
|
337
|
-
FILTER_OPS,
|
|
338
215
|
type FilterOp,
|
|
339
216
|
type KeysetBound,
|
|
340
|
-
type RegisteredEntity,
|
|
341
|
-
type RegisteredTable,
|
|
342
217
|
readField,
|
|
343
218
|
rowId,
|
|
344
219
|
} from './registry';
|
|
220
|
+
export { type AdminRelated, type AdminRelatedList, relatedLists, relatedOf } from './related';
|
|
345
221
|
export {
|
|
346
|
-
type AdminRelated,
|
|
347
|
-
type AdminRelatedList,
|
|
348
|
-
RELATED_PAGE_SIZE,
|
|
349
|
-
relatedLists,
|
|
350
|
-
relatedOf,
|
|
351
|
-
} from './related';
|
|
352
|
-
export {
|
|
353
|
-
type AdminLookupRequest,
|
|
354
|
-
type AdminLookupResult,
|
|
355
222
|
type AdminOption,
|
|
356
|
-
adminLookup,
|
|
357
|
-
LOOKUP_SELECT_MAX,
|
|
358
223
|
labelOf,
|
|
359
224
|
type RelationData,
|
|
360
225
|
type RelationNeed,
|
|
361
226
|
relationDataFor,
|
|
362
|
-
relationNeeds,
|
|
363
227
|
} from './relations';
|
|
364
|
-
export {
|
|
365
|
-
export {
|
|
366
|
-
export {
|
|
367
|
-
type AdminResource,
|
|
368
|
-
type AdminResourceOptions,
|
|
369
|
-
adminResource,
|
|
370
|
-
repoOf,
|
|
371
|
-
resourceFor,
|
|
372
|
-
} from './resource';
|
|
228
|
+
export { adminEntitiesOf, adminRepoFor } from './repo-entity';
|
|
229
|
+
export { type AdminResource, type AdminResourceOptions, adminResource, repoOf } from './resource';
|
|
373
230
|
export type { AdminFieldOverride } from './resource-fields';
|
|
374
|
-
export {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
DEFAULT_SECTION_KEY,
|
|
231
|
+
export type {
|
|
232
|
+
AdminFormGroupOptions,
|
|
233
|
+
AdminSection,
|
|
234
|
+
AdminSectionOptions,
|
|
379
235
|
} from './resource-layout';
|
|
380
|
-
export {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
type AdminScopeWhere,
|
|
391
|
-
LOOKUP_SEGMENT,
|
|
236
|
+
export type {
|
|
237
|
+
AdminColumnComponent,
|
|
238
|
+
AdminColumnRenderProps,
|
|
239
|
+
AdminComputedColumn,
|
|
240
|
+
AdminComputedColumnOptions,
|
|
241
|
+
AdminRenderer,
|
|
242
|
+
AdminRowScope,
|
|
243
|
+
AdminScope,
|
|
244
|
+
AdminScopeOptions,
|
|
245
|
+
AdminScopeWhere,
|
|
392
246
|
} from './resource-list';
|
|
393
|
-
export { AdminViewWriteError, VIEW_OPERATIONS } from './resource-operations';
|
|
394
247
|
export {
|
|
395
248
|
type AdminRouteConfig,
|
|
396
249
|
type AdminRouteMatch,
|
|
@@ -399,17 +252,13 @@ export {
|
|
|
399
252
|
adminRouteMatch,
|
|
400
253
|
adminRoutes,
|
|
401
254
|
} from './routes';
|
|
402
|
-
export { ROW_OUT_OF_SCOPE_REASON } from './row-scope-write';
|
|
403
255
|
export { ROW_CHANGED_REASON, rowVersion, VERSION_FIELD } from './row-version';
|
|
404
256
|
export {
|
|
405
257
|
type AdminRouteRequest,
|
|
406
258
|
type AdminRouteResponse,
|
|
407
259
|
type AdminScreen,
|
|
408
260
|
guardedScreen,
|
|
409
|
-
lookupHref,
|
|
410
|
-
widgetContextOf,
|
|
411
261
|
} from './screen-frame';
|
|
412
|
-
export { type OperationDecision, OperationMatrix, operationMatrix } from './screen-home';
|
|
413
262
|
export {
|
|
414
263
|
type AdminSearchHit,
|
|
415
264
|
type AdminSearchInput,
|
|
@@ -417,23 +266,12 @@ export {
|
|
|
417
266
|
adminSearch,
|
|
418
267
|
} from './search';
|
|
419
268
|
export { type AdminTestCtxInput, adminTestCtx } from './test-ctx';
|
|
420
|
-
export {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
type ThemeMode,
|
|
426
|
-
type ThemeTokenRef,
|
|
427
|
-
themeAttributes,
|
|
269
|
+
export type {
|
|
270
|
+
AdminBranding,
|
|
271
|
+
ThemeAttributes,
|
|
272
|
+
ThemeMode,
|
|
273
|
+
ThemeTokenRef,
|
|
428
274
|
} from './theme';
|
|
429
275
|
export { type ValidationIssue, type ValidationResult, validateInput } from './validate';
|
|
430
|
-
export {
|
|
431
|
-
assertMoney,
|
|
432
|
-
assertZone,
|
|
433
|
-
type GuardedField,
|
|
434
|
-
type SelectOption,
|
|
435
|
-
type WidgetContext,
|
|
436
|
-
type WidgetProps,
|
|
437
|
-
widgetProps,
|
|
438
|
-
} from './widget-value';
|
|
276
|
+
export type { SelectOption, WidgetContext, WidgetProps } from './widget-value';
|
|
439
277
|
export { optionLabel, Widget, type WidgetInput } from './widgets';
|
package/src/jobs/operator.ts
CHANGED
|
@@ -21,13 +21,13 @@ export function jobsOperator(): JobsOperator {
|
|
|
21
21
|
throw new DriverUnavailableError({
|
|
22
22
|
driver: 'none',
|
|
23
23
|
cause: 'the jobs dashboard was asked for rows and this process installed no queue',
|
|
24
|
-
fix: 'run the app through x dev or the container entry, which install the queue — or in a test:
|
|
24
|
+
fix: 'run the app through x dev or the container entry, which install the queue — or in a test: installJobDriver(memoryJobDriver()) from @ultimat3/jobs/adapter',
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
if (driver.introspect === undefined) {
|
|
28
28
|
throw new NotImplementedError({
|
|
29
29
|
cause: `the operator surface of the "${driver.name}" jobs driver is not implemented: the driver has no introspect`,
|
|
30
|
-
fix: '
|
|
30
|
+
fix: 'export const runtime = { jobs: postgresJobDriver() } # apps/<app>/runtime.ts — or delete that line so the boot installs it; the pg and memory drivers implement introspect',
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
return { driver, introspect: driver.introspect };
|
package/src/layout.tsx
CHANGED
|
@@ -128,7 +128,7 @@ export function AdminLayout(props: AdminLayoutProps): JSX.Element {
|
|
|
128
128
|
|
|
129
129
|
{/* The way out. The admin's route table never points back at the app, so without this an
|
|
130
130
|
operator who opened the dashboard has no link home and edits the URL bar. */}
|
|
131
|
-
<a class={styles['navLink']} href=
|
|
131
|
+
<a class={styles['navLink']} href={props.app.home ?? '/'}>
|
|
132
132
|
{t('admin.backToApp')}
|
|
133
133
|
</a>
|
|
134
134
|
</aside>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// The admin as one request reached it: the same declaration, its links rooted where the request
|
|
2
|
+
// was SERVED — behind an HTTP base path, under a routed locale, or both (`/myapp/es/admin`). Every
|
|
3
|
+
// link, form action and redirect a screen writes starts from `app.basePath` or `app.home`, so this
|
|
4
|
+
// one substitution keeps every click where the operator is instead of dropping the prefix.
|
|
5
|
+
|
|
6
|
+
import type { AdminApp } from './admin';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `app` itself when it was reached at its declared base path; otherwise a copy whose `basePath` is
|
|
10
|
+
* the pathname up to and including the declared base (`/myapp/es/admin`) and whose `home` is the
|
|
11
|
+
* part before it with a trailing slash (`/myapp/es/`). The first match from the left is the mount:
|
|
12
|
+
* a row id or a page path that repeats the base comes after it. The route table (`routes`) is left
|
|
13
|
+
* as declared: the host matches on the stripped route path, and the audit names the route.
|
|
14
|
+
*/
|
|
15
|
+
export function adminAppFor(app: AdminApp, url: string): AdminApp {
|
|
16
|
+
const segments = (URL.parse(url, 'http://localhost')?.pathname ?? '').split('/').slice(1);
|
|
17
|
+
const base = app.basePath.split('/').filter((segment) => segment !== '');
|
|
18
|
+
for (let at = 0; at + base.length <= segments.length; at += 1) {
|
|
19
|
+
if (!base.every((segment, i) => segments[at + i] === segment)) continue;
|
|
20
|
+
if (at === 0) return app;
|
|
21
|
+
const served = segments.slice(0, at);
|
|
22
|
+
return {
|
|
23
|
+
...app,
|
|
24
|
+
basePath: `/${[...served, ...base].join('/')}`,
|
|
25
|
+
home: `/${served.join('/')}/`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return app;
|
|
29
|
+
}
|