@enfyra/mcp-server 0.0.84 → 0.0.86
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 +4 -4
- package/package.json +1 -1
- package/src/lib/config-local.mjs +2 -4
- package/src/lib/mcp-examples.js +47 -47
- package/src/lib/mcp-instructions.js +64 -64
- package/src/lib/mutation-guards.js +18 -18
- package/src/lib/route-permission-tools.js +2 -2
- package/src/lib/table-tools.js +23 -23
- package/src/mcp-server-entry.mjs +143 -151
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npx @enfyra/mcp-server config
|
|
|
14
14
|
|
|
15
15
|
The config command writes project config for Codex, Claude Code, and Cursor. It preserves other MCP servers and replaces only the `enfyra` entry.
|
|
16
16
|
|
|
17
|
-
Interactive setup asks for
|
|
17
|
+
Interactive setup asks for your Enfyra app/admin URL, then guides you to the token page when needed and asks for `ENFYRA_API_TOKEN`.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# Non-interactive, all supported clients
|
|
@@ -142,7 +142,7 @@ npx @enfyra/mcp-server config [options]
|
|
|
142
142
|
|
|
143
143
|
| Option | Use |
|
|
144
144
|
|--------|-----|
|
|
145
|
-
| `--app-url` | Set the Enfyra app/admin URL
|
|
145
|
+
| `--app-url` | Set the Enfyra app/admin URL |
|
|
146
146
|
| `--api-token`, `-t` | Set `ENFYRA_API_TOKEN` |
|
|
147
147
|
| `--yes` | Non-interactive mode for CI/scripts |
|
|
148
148
|
| `--global` | Write global/user config instead of project config |
|
|
@@ -158,8 +158,8 @@ Without a target flag, interactive mode asks which client to configure. Non-inte
|
|
|
158
158
|
|
|
159
159
|
| Variable | Description | Default |
|
|
160
160
|
|----------|-------------|---------|
|
|
161
|
-
| `ENFYRA_APP_URL` | App/admin URL used by setup
|
|
162
|
-
| `ENFYRA_API_URL` | Runtime API base written into MCP client config |
|
|
161
|
+
| `ENFYRA_APP_URL` | App/admin URL used by setup | `http://localhost:3000` |
|
|
162
|
+
| `ENFYRA_API_URL` | Runtime API base written into MCP client config | Generated by setup |
|
|
163
163
|
| `ENFYRA_API_TOKEN` | Programmatic token from the Enfyra admin UI `/me` | Required |
|
|
164
164
|
|
|
165
165
|
For normal apps and demos, enter the app/admin URL such as `http://localhost:3000` or `https://demo.enfyra.io`. Treat the direct Enfyra backend host as private infrastructure unless you are debugging Enfyra core/server internals.
|
package/package.json
CHANGED
package/src/lib/config-local.mjs
CHANGED
|
@@ -85,7 +85,6 @@ ${style.bold('Client selection')}
|
|
|
85
85
|
|
|
86
86
|
${style.bold('Interactive mode')}
|
|
87
87
|
Choose Codex, Claude Code, Cursor, or all clients; then enter ENFYRA_APP_URL and ENFYRA_API_TOKEN.
|
|
88
|
-
The API base is inferred by appending /api to the app URL.
|
|
89
88
|
Existing Enfyra config and environment variables are used as defaults. Re-run anytime to update.
|
|
90
89
|
|
|
91
90
|
${style.bold('Examples')}
|
|
@@ -485,7 +484,7 @@ async function promptConfig(opts, existing) {
|
|
|
485
484
|
const defaultAppUrl = resolveDefaultAppUrl(opts, existing);
|
|
486
485
|
if (!appUrl) {
|
|
487
486
|
console.log(`${style.cyan('◆')} ${style.bold('Connect to Enfyra')}`);
|
|
488
|
-
console.log(`${style.dim('│')} Enter the Enfyra app URL
|
|
487
|
+
console.log(`${style.dim('│')} Enter the Enfyra app URL.`);
|
|
489
488
|
const line = (await q(`${style.dim('└')} ENFYRA_APP_URL ${style.dim(`[${defaultAppUrl}]`)}: `)).trim();
|
|
490
489
|
appUrl = normalizeAppUrl(line || defaultAppUrl);
|
|
491
490
|
}
|
|
@@ -497,8 +496,7 @@ async function promptConfig(opts, existing) {
|
|
|
497
496
|
const meUrl = deriveMeUrl(appUrl);
|
|
498
497
|
console.log('');
|
|
499
498
|
console.log(`${style.cyan('◆')} ${style.bold('API token')}`);
|
|
500
|
-
console.log(`${style.dim('│')} If you do not have a token yet,
|
|
501
|
-
console.log(`${style.dim('│')} In the Enfyra admin UI, open ${style.bold('/me')} and create/copy a programmatic API token.`);
|
|
499
|
+
console.log(`${style.dim('│')} If you do not have a token yet, create one here: ${style.cyan(meUrl)}`);
|
|
502
500
|
const line = (await q(`${style.dim('└')} ENFYRA_API_TOKEN${hint}: `)).trim();
|
|
503
501
|
apiToken = line !== '' ? line : defaultApiToken;
|
|
504
502
|
}
|
package/src/lib/mcp-examples.js
CHANGED
|
@@ -155,7 +155,7 @@ onUnmounted(() => {
|
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
name: 'OAuth provider setup values',
|
|
158
|
-
code: `// Enfyra OAuth config row, stored in
|
|
158
|
+
code: `// Enfyra OAuth config row, stored in enfyra_oauth_config.
|
|
159
159
|
{
|
|
160
160
|
"provider": "google",
|
|
161
161
|
"clientId": "<google-client-id>",
|
|
@@ -168,7 +168,7 @@ onUnmounted(() => {
|
|
|
168
168
|
// http://localhost:3000/api/auth/google/callback`,
|
|
169
169
|
notes: [
|
|
170
170
|
'redirectUri is the Enfyra callback URL: {ENFYRA_API_URL}/auth/google/callback.',
|
|
171
|
-
'The provider console callback URL and
|
|
171
|
+
'The provider console callback URL and enfyra_oauth_config.redirectUri must match exactly.',
|
|
172
172
|
'This callback URL is not the app return page; the app return page is sent as the redirect query when starting OAuth.',
|
|
173
173
|
'Use appCallbackUrl only for manual-token apps that intentionally read token query parameters.',
|
|
174
174
|
],
|
|
@@ -210,7 +210,7 @@ window.location.href = url.toString()`,
|
|
|
210
210
|
],
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
|
-
name: 'Create relations directly to
|
|
213
|
+
name: 'Create relations directly to enfyra_user',
|
|
214
214
|
code: `create_table({
|
|
215
215
|
name: "chat_message",
|
|
216
216
|
columns: JSON.stringify([
|
|
@@ -228,7 +228,7 @@ window.location.href = url.toString()`,
|
|
|
228
228
|
{
|
|
229
229
|
propertyName: "sender",
|
|
230
230
|
type: "many-to-one",
|
|
231
|
-
targetTable: { id: "<
|
|
231
|
+
targetTable: { id: "<enfyra_user_id>" },
|
|
232
232
|
isNullable: false,
|
|
233
233
|
onDelete: "CASCADE"
|
|
234
234
|
}
|
|
@@ -238,9 +238,9 @@ window.location.href = url.toString()`,
|
|
|
238
238
|
])
|
|
239
239
|
})`,
|
|
240
240
|
notes: [
|
|
241
|
-
'Use
|
|
242
|
-
'Use table ids for targetTable when already known; MCP can also resolve exact table names such as "
|
|
243
|
-
'Do not add inverse relations on
|
|
241
|
+
'Use enfyra_user as the user table.',
|
|
242
|
+
'Use table ids for targetTable when already known; MCP can also resolve exact table names such as "enfyra_user" before schema mutation.',
|
|
243
|
+
'Do not add inverse relations on enfyra_user unless the user explicitly asks.',
|
|
244
244
|
'createdAt, updatedAt, and custom date/datetime/timestamp fields already get auto-generated single-field indexes; add only compound indexes needed by hot filters.',
|
|
245
245
|
'Do not provide physical FK column names; Enfyra derives them.',
|
|
246
246
|
],
|
|
@@ -253,7 +253,7 @@ window.location.href = url.toString()`,
|
|
|
253
253
|
{
|
|
254
254
|
propertyName: "createdBy",
|
|
255
255
|
type: "many-to-one",
|
|
256
|
-
targetTable: { id: "<
|
|
256
|
+
targetTable: { id: "<enfyra_user_id>" },
|
|
257
257
|
isNullable: true,
|
|
258
258
|
onDelete: "CASCADE"
|
|
259
259
|
},
|
|
@@ -282,7 +282,7 @@ window.location.href = url.toString()`,
|
|
|
282
282
|
relations: JSON.stringify([
|
|
283
283
|
{ propertyName: "message", type: "many-to-one", targetTable: { id: "<chat_message_id>" }, onDelete: "CASCADE" },
|
|
284
284
|
{ propertyName: "conversation", type: "many-to-one", targetTable: { id: "<chat_conversation_id>" }, onDelete: "CASCADE" },
|
|
285
|
-
{ propertyName: "member", type: "many-to-one", targetTable: { id: "<
|
|
285
|
+
{ propertyName: "member", type: "many-to-one", targetTable: { id: "<enfyra_user_id>" }, onDelete: "CASCADE" }
|
|
286
286
|
]),
|
|
287
287
|
uniques: JSON.stringify([["message", "member"]]),
|
|
288
288
|
indexes: JSON.stringify([
|
|
@@ -299,7 +299,7 @@ window.location.href = url.toString()`,
|
|
|
299
299
|
{
|
|
300
300
|
name: 'Add server-owned user verification fields',
|
|
301
301
|
code: `create_column({
|
|
302
|
-
tableId: "<
|
|
302
|
+
tableId: "<enfyra_user_table_id>",
|
|
303
303
|
name: "emailVerifiedAt",
|
|
304
304
|
type: "datetime",
|
|
305
305
|
isNullable: true,
|
|
@@ -308,7 +308,7 @@ window.location.href = url.toString()`,
|
|
|
308
308
|
})
|
|
309
309
|
|
|
310
310
|
create_column({
|
|
311
|
-
tableId: "<
|
|
311
|
+
tableId: "<enfyra_user_table_id>",
|
|
312
312
|
name: "emailVerificationStatus",
|
|
313
313
|
type: "varchar",
|
|
314
314
|
isNullable: false,
|
|
@@ -328,7 +328,7 @@ create_column({
|
|
|
328
328
|
})`,
|
|
329
329
|
notes: [
|
|
330
330
|
'Run schema-changing calls sequentially. Do not parallelize create_column calls.',
|
|
331
|
-
'create_column fetches
|
|
331
|
+
'create_column fetches enfyra_table and patches only real persisted columns with id/_id; generated metadata projections such as createdAt, updatedAt, or relation FK display fields are skipped.',
|
|
332
332
|
'Use isEncrypted=true for encryption at rest. Add isUpdatable=false separately only when the field should be immutable.',
|
|
333
333
|
'Use hooks or field permissions to prevent clients from updating server-owned fields.',
|
|
334
334
|
],
|
|
@@ -339,7 +339,7 @@ create_column({
|
|
|
339
339
|
// 1. Read GET /metadata and find the target table.
|
|
340
340
|
// 2. Keep only persisted column rows with id/_id.
|
|
341
341
|
// 3. Add, change, or remove the intended column.
|
|
342
|
-
// 4. PATCH /
|
|
342
|
+
// 4. PATCH /enfyra_table/:id with the full preserved columns array.
|
|
343
343
|
// 5. If the backend returns requiredConfirmHash, resend with ?schemaConfirmHash=<hash>.
|
|
344
344
|
// 6. Re-read metadata and verify unrelated column ids still exist.
|
|
345
345
|
|
|
@@ -351,8 +351,8 @@ create_column({
|
|
|
351
351
|
isEncrypted: true
|
|
352
352
|
})`,
|
|
353
353
|
notes: [
|
|
354
|
-
'Do not rebuild schema cascade payloads from
|
|
355
|
-
'Generated projections such as createdAt, updatedAt, and relation FK display fields without id/_id are not valid
|
|
354
|
+
'Do not rebuild schema cascade payloads from enfyra_table?fields=columns.*; nested fields can be truncated or relation-derived.',
|
|
355
|
+
'Generated projections such as createdAt, updatedAt, and relation FK display fields without id/_id are not valid enfyra_column rows.',
|
|
356
356
|
'Never delete or omit unrelated persisted columns when adding one field.',
|
|
357
357
|
'Run schema-changing calls sequentially; migration locks are backend-owned.',
|
|
358
358
|
],
|
|
@@ -366,7 +366,7 @@ create_column({
|
|
|
366
366
|
{
|
|
367
367
|
name: 'Minimal MCP query then explicit detail query',
|
|
368
368
|
code: `query_table({
|
|
369
|
-
tableName: "
|
|
369
|
+
tableName: "enfyra_user",
|
|
370
370
|
fields: ["id", "email"],
|
|
371
371
|
filter: "{\\"email\\":{\\"_contains\\":\\"@example.com\\"}}",
|
|
372
372
|
limit: 10
|
|
@@ -443,7 +443,7 @@ GET /enfyra/post?filter={"<primaryKeyFromMetadata>":{"_eq":123}}&limit=1`,
|
|
|
443
443
|
{
|
|
444
444
|
name: 'Exclude large generated fields',
|
|
445
445
|
code: `query_table({
|
|
446
|
-
tableName: "
|
|
446
|
+
tableName: "enfyra_route_handler",
|
|
447
447
|
fields: ["-compiledCode"],
|
|
448
448
|
limit: 20
|
|
449
449
|
})
|
|
@@ -581,7 +581,7 @@ return { ok: true, email }\`
|
|
|
581
581
|
notes: [
|
|
582
582
|
'Use sourceCode, not logic. The server generates compiledCode.',
|
|
583
583
|
'Use method for one handler, or methods only when the same sourceCode should be saved for multiple methods.',
|
|
584
|
-
'Do not pass name to
|
|
584
|
+
'Do not pass name to enfyra_route_handler; one handler is identified by route + method.',
|
|
585
585
|
],
|
|
586
586
|
},
|
|
587
587
|
{
|
|
@@ -591,13 +591,13 @@ const password = @BODY.password
|
|
|
591
591
|
|
|
592
592
|
if (!email || !password) @THROW400("Email and password are required")
|
|
593
593
|
|
|
594
|
-
const existing = await #
|
|
594
|
+
const existing = await #enfyra_user.find({
|
|
595
595
|
filter: { email: { _eq: email } },
|
|
596
596
|
limit: 1
|
|
597
597
|
})
|
|
598
598
|
if (existing.data[0]) @THROW409("Email is already registered")
|
|
599
599
|
|
|
600
|
-
const result = await #
|
|
600
|
+
const result = await #enfyra_user.create({
|
|
601
601
|
data: {
|
|
602
602
|
email,
|
|
603
603
|
password: await @HELPERS.$bcrypt.hash(password)
|
|
@@ -656,7 +656,7 @@ const scope = {
|
|
|
656
656
|
{
|
|
657
657
|
name: 'Pre-hook strips protected body fields silently',
|
|
658
658
|
code: `create_pre_hook({
|
|
659
|
-
routeId: "<
|
|
659
|
+
routeId: "<enfyra_user_patch_route_id>",
|
|
660
660
|
name: "strip_email_verification_fields",
|
|
661
661
|
methods: ["PATCH"],
|
|
662
662
|
priority: -10,
|
|
@@ -665,7 +665,7 @@ delete @BODY.emailVerificationStatus
|
|
|
665
665
|
delete @BODY.emailVerificationSentAt\`
|
|
666
666
|
})`,
|
|
667
667
|
notes: [
|
|
668
|
-
'Use this pattern when clients may send protected user fields through /me or
|
|
668
|
+
'Use this pattern when clients may send protected user fields through /me or enfyra_user PATCH.',
|
|
669
669
|
'Strip fields instead of throwing when the product wants a permissive client contract with server-owned fields.',
|
|
670
670
|
'Use native macros such as @BODY instead of raw $ctx when a macro exists.',
|
|
671
671
|
],
|
|
@@ -724,7 +724,7 @@ ensure_route_access({
|
|
|
724
724
|
{
|
|
725
725
|
name: 'Publish read-only route',
|
|
726
726
|
code: `update_record({
|
|
727
|
-
tableName: "
|
|
727
|
+
tableName: "enfyra_route",
|
|
728
728
|
id: "<route_id>",
|
|
729
729
|
data: {
|
|
730
730
|
publicMethods: [{ id: "<GET_method_id_from_list_methods>" }]
|
|
@@ -739,7 +739,7 @@ ensure_route_access({
|
|
|
739
739
|
{
|
|
740
740
|
name: 'Column rule for email format',
|
|
741
741
|
code: `create_column_rule({
|
|
742
|
-
tableName: "
|
|
742
|
+
tableName: "enfyra_user",
|
|
743
743
|
columnName: "email",
|
|
744
744
|
ruleType: "format",
|
|
745
745
|
value: JSON.stringify({ v: "email" }),
|
|
@@ -810,25 +810,25 @@ const { checkPermissionCondition } = usePermissions()
|
|
|
810
810
|
const canReadReports = computed(() => checkPermissionCondition({
|
|
811
811
|
or: [
|
|
812
812
|
{ route: '/reports', methods: ['GET'] },
|
|
813
|
-
{ route: '/
|
|
813
|
+
{ route: '/report', methods: ['GET'] }
|
|
814
814
|
]
|
|
815
815
|
}))
|
|
816
816
|
|
|
817
817
|
const canCreateReport = computed(() => checkPermissionCondition({
|
|
818
|
-
or: [{ route: '/
|
|
818
|
+
or: [{ route: '/report', methods: ['POST'] }]
|
|
819
819
|
}))
|
|
820
820
|
|
|
821
821
|
const canUpdateReport = computed(() => checkPermissionCondition({
|
|
822
|
-
or: [{ route: '/
|
|
822
|
+
or: [{ route: '/report', methods: ['PATCH'] }]
|
|
823
823
|
}))
|
|
824
824
|
|
|
825
825
|
const canDeleteReport = computed(() => checkPermissionCondition({
|
|
826
|
-
or: [{ route: '/
|
|
826
|
+
or: [{ route: '/report', methods: ['DELETE'] }]
|
|
827
827
|
}))
|
|
828
828
|
</script>`,
|
|
829
829
|
notes: [
|
|
830
830
|
'This is menu/extension visibility, not row-level RLS.',
|
|
831
|
-
'Set
|
|
831
|
+
'Set enfyra_menu.permission on every sensitive admin menu. Example for /reports: { or: [{ route: "/reports", methods: ["GET"] }, { route: "/report", methods: ["GET"] }] }.',
|
|
832
832
|
'Admin pages are sensitive. Use permission gates by default, not as an optional polish step.',
|
|
833
833
|
'Menus should only be visible when the user has at least GET permission for the page route or backing data route.',
|
|
834
834
|
'Inside the extension, gate each action by its own route/method: GET for page visibility, POST for create/flow-trigger buttons, PATCH for normal record edits, DELETE for native delete routes.',
|
|
@@ -976,13 +976,13 @@ return order && order.total > 1000`,
|
|
|
976
976
|
notes: [
|
|
977
977
|
'Prefer operation-sized flow steps with clear keys over one large script that performs SSH, Docker, DB, API, email, and finalization work together.',
|
|
978
978
|
'Each step should return only ids, booleans, status keys, or small counters that later steps need.',
|
|
979
|
-
'When refactoring an existing flow, add or extract adjacent focused
|
|
979
|
+
'When refactoring an existing flow, add or extract adjacent focused enfyra_flow_step rows instead of making an oversized sourceCode block longer.',
|
|
980
980
|
],
|
|
981
981
|
},
|
|
982
982
|
{
|
|
983
983
|
name: 'Flow query step config',
|
|
984
984
|
code: `{
|
|
985
|
-
"table": "
|
|
985
|
+
"table": "enfyra_user",
|
|
986
986
|
"filter": { "email": { "_contains": "@example.com" } },
|
|
987
987
|
"limit": 50
|
|
988
988
|
}`,
|
|
@@ -1031,7 +1031,7 @@ return saved`,
|
|
|
1031
1031
|
notes: [
|
|
1032
1032
|
'Use file-specific context only in upload-capable routes.',
|
|
1033
1033
|
'For request uploads, pass file: @UPLOADED_FILE to @STORAGE.$upload/@STORAGE.$update so Enfyra streams from the temp file path.',
|
|
1034
|
-
'Use @STORAGE.$registerFile when an external process already uploaded the object and the script only needs to create the
|
|
1034
|
+
'Use @STORAGE.$registerFile when an external process already uploaded the object and the script only needs to create the enfyra_file record.',
|
|
1035
1035
|
'Do not read @UPLOADED_FILE.path into a Buffer and do not generate examples using @UPLOADED_FILE.buffer.',
|
|
1036
1036
|
'Use buffer only for small generated or transformed files, such as image thumbnails.',
|
|
1037
1037
|
],
|
|
@@ -1058,8 +1058,8 @@ update_method({
|
|
|
1058
1058
|
textColor: "#b45309"
|
|
1059
1059
|
})`,
|
|
1060
1060
|
notes: [
|
|
1061
|
-
'Use dedicated method tools instead of generic CRUD on
|
|
1062
|
-
'The backend stores the method label in
|
|
1061
|
+
'Use dedicated method tools instead of generic CRUD on enfyra_method.',
|
|
1062
|
+
'The backend stores the method label in enfyra_method.name; do not send or filter a `method` field on `enfyra_method`.',
|
|
1063
1063
|
'buttonColor is the badge background and textColor is the badge text color.',
|
|
1064
1064
|
'The Enfyra admin UI is /settings/methods.',
|
|
1065
1065
|
'delete_method is preview-first and should only be used for unused custom methods.',
|
|
@@ -1077,7 +1077,7 @@ update_method({
|
|
|
1077
1077
|
permission: JSON.stringify({
|
|
1078
1078
|
or: [
|
|
1079
1079
|
{ route: "/reports", methods: ["GET"] },
|
|
1080
|
-
{ route: "/
|
|
1080
|
+
{ route: "/report", methods: ["GET"] }
|
|
1081
1081
|
]
|
|
1082
1082
|
})
|
|
1083
1083
|
})
|
|
@@ -1093,7 +1093,7 @@ create_extension({
|
|
|
1093
1093
|
})`,
|
|
1094
1094
|
notes: [
|
|
1095
1095
|
'Menu provides navigation; extension provides content.',
|
|
1096
|
-
'Use
|
|
1096
|
+
'Use enfyra_menu.label, not title.',
|
|
1097
1097
|
'Sensitive admin menus should include a permission condition at creation time.',
|
|
1098
1098
|
'For page extensions, create the menu first and pass menuId to create_extension.',
|
|
1099
1099
|
'Page extensions must register the app-shell PageHeader with usePageHeaderRegistry instead of rendering a custom top header.',
|
|
@@ -1151,12 +1151,12 @@ create_extension({
|
|
|
1151
1151
|
type: "page",
|
|
1152
1152
|
name: "ReportsPage",
|
|
1153
1153
|
menuId: "<reports-menu-id>",
|
|
1154
|
-
code: "<template><section class=\\"min-h-full w-full space-y-4\\"><Widget :id=\\"<report-status-widget-id>\\" :total=\\"totalReports\\" :rows=\\"reportRows\\" :open-details=\\"openReportDetails\\" @refresh=\\"refresh\\" /><Widget :id=\\"<report-table-widget-id>\\" :rows=\\"reportRows\\" @refresh=\\"refresh\\" /></section></template><script setup>const { registerPageHeader } = usePageHeaderRegistry(); registerPageHeader({ title: 'Reports', description: 'Operational report overview.', leadingIcon: 'lucide:bar-chart-3', gradient: 'cyan', variant: 'minimal' }); const totalReports = ref(0); const reportRows = ref([]); function refresh() {} function openReportDetails(row) { navigateTo('/data/
|
|
1154
|
+
code: "<template><section class=\\"min-h-full w-full space-y-4\\"><Widget :id=\\"<report-status-widget-id>\\" :total=\\"totalReports\\" :rows=\\"reportRows\\" :open-details=\\"openReportDetails\\" @refresh=\\"refresh\\" /><Widget :id=\\"<report-table-widget-id>\\" :rows=\\"reportRows\\" @refresh=\\"refresh\\" /></section></template><script setup>const { registerPageHeader } = usePageHeaderRegistry(); registerPageHeader({ title: 'Reports', description: 'Operational report overview.', leadingIcon: 'lucide:bar-chart-3', gradient: 'cyan', variant: 'minimal' }); const totalReports = ref(0); const reportRows = ref([]); function refresh() {} function openReportDetails(row) { navigateTo('/data/report?filter=' + encodeURIComponent(JSON.stringify({ id: { _eq: row.id } }))) }</script>",
|
|
1155
1155
|
isEnabled: true
|
|
1156
1156
|
})`,
|
|
1157
1157
|
notes: [
|
|
1158
1158
|
'Use widgets for bulky or reusable sections such as operation panels, timelines, tables, sidebars, and status cards.',
|
|
1159
|
-
'Embed widgets by their numeric
|
|
1159
|
+
'Embed widgets by their numeric enfyra_extension id, not by extensionId/name.',
|
|
1160
1160
|
'Props and listeners pass through the Widget wrapper. Widget defineProps values update reactively when the parent refs/computed values change.',
|
|
1161
1161
|
'Use kebab-case in the parent template for camelCase widget props, for example :open-details maps to openDetails.',
|
|
1162
1162
|
'Do not mutate widget props. Use computed for derived display state, and use watch only when mirroring a prop into local editable draft state.',
|
|
@@ -1338,11 +1338,11 @@ registerHeaderActions([
|
|
|
1338
1338
|
},
|
|
1339
1339
|
{
|
|
1340
1340
|
name: 'Debug menu or extension changes that do not appear in open Enfyra admin tabs',
|
|
1341
|
-
code: `// Server side:
|
|
1341
|
+
code: `// Server side: enfyra_menu and enfyra_extension are runtime UI definitions.
|
|
1342
1342
|
// They must participate in partial reload, just like metadata/routes.
|
|
1343
1343
|
// Expected server contract:
|
|
1344
|
-
// - cache orchestrator maps
|
|
1345
|
-
// - cache orchestrator maps
|
|
1344
|
+
// - cache orchestrator maps enfyra_menu -> menu reload
|
|
1345
|
+
// - cache orchestrator maps enfyra_extension -> extension reload
|
|
1346
1346
|
// - successful writes emit $system:reload to the admin Socket.IO namespace
|
|
1347
1347
|
|
|
1348
1348
|
// Enfyra admin UI side expected listener behavior:
|
|
@@ -1379,7 +1379,7 @@ create_menu({
|
|
|
1379
1379
|
permission: JSON.stringify({
|
|
1380
1380
|
or: [
|
|
1381
1381
|
{ route: "/operations/jobs", methods: ["GET"] },
|
|
1382
|
-
{ route: "/
|
|
1382
|
+
{ route: "/enfyra_flow_execution", methods: ["GET"] }
|
|
1383
1383
|
]
|
|
1384
1384
|
})
|
|
1385
1385
|
})
|
|
@@ -1391,7 +1391,7 @@ create_menu({
|
|
|
1391
1391
|
// /operations/reports report configuration and delivery history
|
|
1392
1392
|
// /operations/settings system readiness and configuration
|
|
1393
1393
|
// Use UTabs inside large pages instead of placing every section in one dashboard.
|
|
1394
|
-
// For admin record management, link to /data/<table>, e.g. /data/
|
|
1394
|
+
// For admin record management, link to /data/<table>, e.g. /data/report, not public website paths.`,
|
|
1395
1395
|
notes: [
|
|
1396
1396
|
'Design the menu/page split before generating dashboard code.',
|
|
1397
1397
|
'Permission-gate sensitive parent dropdown menus too, using any child page route or backing route that represents read access.',
|
|
@@ -1408,7 +1408,7 @@ create_menu({
|
|
|
1408
1408
|
{
|
|
1409
1409
|
name: 'Extension fetches Enfyra data',
|
|
1410
1410
|
code: `<script setup>
|
|
1411
|
-
const { data, pending, execute: fetchOrders } = useApi('/
|
|
1411
|
+
const { data, pending, execute: fetchOrders } = useApi('/order', {
|
|
1412
1412
|
query: {
|
|
1413
1413
|
limit: 10,
|
|
1414
1414
|
sort: '-createdAt'
|
|
@@ -1510,7 +1510,7 @@ onMounted(async () => {
|
|
|
1510
1510
|
</template>`,
|
|
1511
1511
|
notes: [
|
|
1512
1512
|
'Install browser-side extension dependencies as type: "App".',
|
|
1513
|
-
'Do not use static import statements in
|
|
1513
|
+
'Do not use static import statements in enfyra_extension.code.',
|
|
1514
1514
|
'Load app packages with getPackages([...]) inside the extension runtime.',
|
|
1515
1515
|
'Use onMounted or an explicit action for package loading when the UI can render a loading state.',
|
|
1516
1516
|
],
|
|
@@ -1528,7 +1528,7 @@ const rangeStart = computed(() => {
|
|
|
1528
1528
|
return d.toISOString()
|
|
1529
1529
|
})
|
|
1530
1530
|
|
|
1531
|
-
const flowStats = useApi('/
|
|
1531
|
+
const flowStats = useApi('/enfyra_flow_execution', {
|
|
1532
1532
|
query: computed(() => ({
|
|
1533
1533
|
fields: 'id',
|
|
1534
1534
|
limit: 1,
|
|
@@ -1541,7 +1541,7 @@ const flowStats = useApi('/flow_execution_definition', {
|
|
|
1541
1541
|
}))
|
|
1542
1542
|
})
|
|
1543
1543
|
|
|
1544
|
-
const orderStats = useApi('/
|
|
1544
|
+
const orderStats = useApi('/order', {
|
|
1545
1545
|
query: computed(() => ({
|
|
1546
1546
|
fields: 'id',
|
|
1547
1547
|
limit: 1,
|