@gadmin2n/schematics 0.0.149 → 0.0.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.md +125 -36
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +42 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/gitignore +2 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.seed-timestamp +0 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +3 -1
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/check-dev-env.js +218 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/page-manage.ts +78 -12
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +37 -67
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +0 -17
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +46 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +60 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.ts +3 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.controller.ts +9 -1
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +55 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +2 -0
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/page-transfer.service.ts +491 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/perm-transfer.dto.ts +213 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/perm-transfer.module.ts +28 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/resource-transfer.service.ts +137 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/role-transfer.service.ts +248 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/transfer-token.service.ts +65 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/permTransfer/user-transfer.service.ts +146 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +55 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +2 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +55 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +2 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user-group.provider.spec.ts +46 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user-group.provider.ts +3 -2
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +55 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +2 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +1 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +96 -14
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +41 -41
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-database-function.spec.ts +36 -0
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-database-function.ts +90 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +4 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +1 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +115 -63
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-outbox.service.spec.ts +166 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-outbox.service.ts +217 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.spec.ts +33 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.ts +57 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.spec.ts +196 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.ts +125 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.controller.ts +9 -3
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +148 -4
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.module.ts +12 -1
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +14 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +75 -45
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +25 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/TransferExportModal.tsx +115 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/TransferImportModal.tsx +257 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +0 -2
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +40 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +15 -13
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +40 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +15 -13
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/plugins/devShellPlugin.ts +2 -1
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +89 -65
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +51 -127
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permissionReadme/index.tsx +53 -109
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +45 -41
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +51 -72
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +49 -40
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +16 -5
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ReadonlyNodePanel.test.tsx +28 -10
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +60 -1
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +94 -12
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +13 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/default-widgets.tsx +3 -3
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +8 -1
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +167 -94
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +95 -95
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerLifecycleConfigEditor.test.tsx +81 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerLifecycleConfigEditor.tsx +126 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +48 -52
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/forEachConnection.ts +2 -2
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +1 -3
  79. package/package.json +1 -1
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/ac.config.ts +0 -24
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +0 -495
@@ -540,6 +540,12 @@
540
540
  "conditionField": "Field for condition {{number}}",
541
541
  "conditionOperator": "Comparison for condition {{number}}",
542
542
  "conditionValue": "Value for condition {{number}}",
543
+ "failureCallbackTitle": "Workflow failure notification",
544
+ "failureCallbackDescription": "Reliably notify the business system after the Workflow finally fails. The business API must handle the event ID idempotently.",
545
+ "failureCallbackEnabled": "Notify business system",
546
+ "failureCallbackConnection": "Business system connection",
547
+ "failureCallbackPath": "Failure API path",
548
+ "failureCallbackLoadFailed": "Failed to load business system connections",
543
549
  "selectConditionField": "Select a field",
544
550
  "newDataField": "New data · {{field}}",
545
551
  "oldDataField": "Previous data · {{field}}",
@@ -575,19 +581,13 @@
575
581
  }
576
582
  },
577
583
  "database": {
578
- "sql": "SQL statement",
579
- "readOnly": "Read-only SQL",
580
- "writeEnabled": "Writes enabled",
581
- "allowWrite": "Allow writes",
582
- "readOnlyDescription": "When off, this node can only query data.",
583
- "readWriteDescription": "When on, this node can query, insert, update, or delete data.",
584
- "readOnlyError": "This node only allows one read-only query.",
585
- "readWriteError": "Only one query, insert, update, or delete statement is allowed.",
586
- "parameters": "Parameter values",
587
- "parameter": "Parameter",
588
- "value": "Value",
589
- "parametersHint": "Values are used by $1, $2, and so on in order.",
590
- "addParameter": "Add parameter value",
584
+ "function": "Database Function",
585
+ "functionHint": "Only Functions in workflow_api in the current GAdmin database are shown.",
586
+ "selectFunction": "Select a Function",
587
+ "loadFailed": "Could not load the Function catalog.",
588
+ "functionMissing": "This Function no longer exists. Select another Function.",
589
+ "returnType": "Return type: {{type}}",
590
+ "noArguments": "This Function has no arguments.",
591
591
  "timeout": "Timeout",
592
592
  "seconds": "seconds"
593
593
  },
@@ -648,6 +648,7 @@
648
648
  "noDescription": "No description",
649
649
  "noConfiguration": "This instance has no configuration",
650
650
  "configurationDescription": "Review the saved {{name}} execution parameters in read-only mode.",
651
+ "rawConfiguration": "Raw node configuration",
651
652
  "referencedBy": "Referenced by",
652
653
  "referencesDescription": "These Workflows currently use this reusable configuration.",
653
654
  "noReferences": "No Workflows reference this instance",
@@ -1081,6 +1082,7 @@
1081
1082
  "openTemporal": "Open in Temporal",
1082
1083
  "cancel": "Cancel run",
1083
1084
  "executionDetails": "Run information",
1085
+ "failedNode": "Failed node",
1084
1086
  "cancellationRequestedBy": "Cancelled by",
1085
1087
  "cancellationRequestedAt": "Cancelled at",
1086
1088
  "cancellationReason": "Cancellation reason",
@@ -374,5 +374,45 @@
374
374
  "renameDuplicate": "名称已存在,请换一个",
375
375
  "renameFailed": "重命名失败"
376
376
  }
377
+ },
378
+ "transfer": {
379
+ "import": {
380
+ "title": "导入",
381
+ "fetchPreview": "获取并预览",
382
+ "confirm": "确认导入",
383
+ "urlHint": "粘贴源环境生成的导出 URL:",
384
+ "current": "当前环境",
385
+ "incoming": "源环境",
386
+ "importing": "正在导入…",
387
+ "pasteUrl": "请粘贴导出 URL",
388
+ "fetchFailed": "获取数据失败:{{status}}",
389
+ "expired": "导出链接已过期或无效,请在源环境重新生成。",
390
+ "unreachable": "无法访问源环境,请检查 URL 后重试。",
391
+ "success": "“{{name}}”导入成功!",
392
+ "failed": "导入失败",
393
+ "missingRefsTitle": "目标环境缺少以下引用,请先导入对应数据,否则无法确认导入:",
394
+ "missingRefType": {
395
+ "page": "页面",
396
+ "resource": "资源",
397
+ "parentPage": "父页面"
398
+ },
399
+ "conflictsTitle": "存在以下冲突,无法导入:",
400
+ "pageRoleHint": "页面已导入,但尚未授权给任何角色,菜单不会显示。请再导入对应角色,或在「角色管理」中为角色勾选该页面。"
401
+ },
402
+ "export": {
403
+ "title": "导出",
404
+ "copyUrl": "复制 URL",
405
+ "generating": "正在生成导出 URL…",
406
+ "urlHint": "导出 URL(30 分钟内有效),粘贴到目标环境的导入对话框:",
407
+ "failed": "生成导出 URL 失败",
408
+ "copied": "URL 已复制到剪贴板",
409
+ "scopeTitle": "本次导出的内容",
410
+ "scope": {
411
+ "user": "包含:账号、名称、所属角色、启用状态。不包含:API Key 等凭据(出于安全考虑不跨环境传输)。",
412
+ "role": "包含:角色基本信息、可访问页面列表(rolePages)、资源权限(roleResource)。注意:导入时引用的页面与资源必须已存在于目标环境,否则会被拦截。",
413
+ "page": "包含:该页面及其全部子页面、各页面与资源的绑定关系(pageResource)。不包含:角色对页面的授权(rolePages)—— 导入后需再导入对应角色,或在「角色管理」中授权,菜单才会显示。",
414
+ "resource": "包含:资源 code、名称、描述、公共资源标记。"
415
+ }
416
+ }
377
417
  }
378
418
  }
@@ -540,6 +540,12 @@
540
540
  "conditionField": "条件 {{number}} 的字段",
541
541
  "conditionOperator": "条件 {{number}} 的判断方式",
542
542
  "conditionValue": "条件 {{number}} 的比较值",
543
+ "failureCallbackTitle": "Workflow 失败通知",
544
+ "failureCallbackDescription": "Workflow 最终失败后,可靠通知业务系统收敛申请状态。业务接口必须按事件 ID 幂等处理。",
545
+ "failureCallbackEnabled": "通知业务系统",
546
+ "failureCallbackConnection": "业务系统连接",
547
+ "failureCallbackPath": "失败处理 API 路径",
548
+ "failureCallbackLoadFailed": "加载业务系统连接失败",
543
549
  "selectConditionField": "选择字段",
544
550
  "newDataField": "新数据 · {{field}}",
545
551
  "oldDataField": "原数据 · {{field}}",
@@ -575,19 +581,13 @@
575
581
  }
576
582
  },
577
583
  "database": {
578
- "sql": "SQL 语句",
579
- "readOnly": "只读 SQL",
580
- "writeEnabled": "允许写入",
581
- "allowWrite": "允许写入",
582
- "readOnlyDescription": "关闭时只能查询数据。",
583
- "readWriteDescription": "开启后可以查询、新增、更新或删除数据。",
584
- "readOnlyError": "当前节点只允许单条只读查询。",
585
- "readWriteError": "只允许单条查询、新增、更新或删除语句。",
586
- "parameters": "参数值",
587
- "parameter": "参数",
588
- "value": "值",
589
- "parametersHint": "SQL 中的 $1、$2… 会依次使用这里的值。",
590
- "addParameter": "添加参数值",
584
+ "function": "数据库 Function",
585
+ "functionHint": "只显示当前 GAdmin 数据库 workflow_api 中的 Function。",
586
+ "selectFunction": "选择 Function",
587
+ "loadFailed": "Function 列表加载失败。",
588
+ "functionMissing": "当前 Function 已不存在,请重新选择。",
589
+ "returnType": "返回类型:{{type}}",
590
+ "noArguments": "这个 Function 不需要参数。",
591
591
  "timeout": "超时时间",
592
592
  "seconds": "秒"
593
593
  },
@@ -648,6 +648,7 @@
648
648
  "noDescription": "暂无描述",
649
649
  "noConfiguration": "此实例没有配置项",
650
650
  "configurationDescription": "只读查看 {{name}} 当前保存的执行参数。",
651
+ "rawConfiguration": "原始节点配置",
651
652
  "referencedBy": "引用此实例的工作流",
652
653
  "referencesDescription": "以下 Workflow 当前正在使用这份可复用配置。",
653
654
  "noReferences": "暂无工作流引用此实例",
@@ -1081,6 +1082,7 @@
1081
1082
  "openTemporal": "在 Temporal 中查看",
1082
1083
  "cancel": "取消运行",
1083
1084
  "executionDetails": "运行信息",
1085
+ "failedNode": "失败节点",
1084
1086
  "cancellationRequestedBy": "取消人",
1085
1087
  "cancellationRequestedAt": "取消时间",
1086
1088
  "cancellationReason": "取消原因",
@@ -192,10 +192,11 @@ export function devShellPlugin(): Plugin {
192
192
  let total = 0;
193
193
  let tooLarge = false;
194
194
  req.on('data', (chunk: Uint8Array) => {
195
+ if (tooLarge) return;
195
196
  total += chunk.length;
196
197
  if (total > UPLOAD_MAX_BYTES) {
197
198
  tooLarge = true;
198
- req.destroy();
199
+ chunks.length = 0;
199
200
  return;
200
201
  }
201
202
  chunks.push(chunk);
@@ -12,8 +12,9 @@
12
12
  * See: examples/ListPageExamples.tsx for all patterns
13
13
  */
14
14
  import React, { useMemo } from 'react';
15
- import { Table } from 'antd';
16
- import { List, useTable, useImport } from '@refinedev/antd';
15
+ import { Button, Card, Space, Table } from 'antd';
16
+ import { ArrowLeftOutlined } from '@ant-design/icons';
17
+ import { useTable, useImport } from '@refinedev/antd';
17
18
  import {
18
19
  HttpError,
19
20
  IResourceComponentsProps,
@@ -41,7 +42,14 @@ import { agendaJobModel, useRelation } from 'generated/props/agendaJob/model';
41
42
  import type { AgendaJob } from 'generated/types/prisma.types';
42
43
 
43
44
  // Local imports
44
- import { BulkActions, ListPageHeader, RowActions, SearchBar } from 'components';
45
+ import {
46
+ BulkActions,
47
+ ListPageHeader,
48
+ PageHeader,
49
+ RowActions,
50
+ SearchBar,
51
+ } from 'components';
52
+ import { PAGE_WRAPPER_STYLE, SEARCH_CARD_PROPS } from 'constants/layout';
45
53
  import { getRowActionsColumnProps } from 'components/agentPanel';
46
54
  import { requestHeaders } from 'helpers/login';
47
55
  import { useBatchOperations } from 'hooks';
@@ -183,10 +191,53 @@ export const AgendaJobList: React.FC<IResourceComponentsProps> = () => {
183
191
  };
184
192
 
185
193
  return (
186
- <List
187
- headerProps={{
188
- onBack: () => navigate('/admin/agenda-jobs'),
189
- subTitle: (
194
+ <div style={PAGE_WRAPPER_STYLE}>
195
+ {/* Header Card(带返回按钮,回到 Cron Jobs 页) */}
196
+ <PageHeader
197
+ title={
198
+ <Space size={4}>
199
+ <Button
200
+ type="text"
201
+ size="small"
202
+ icon={<ArrowLeftOutlined />}
203
+ onClick={() => navigate('/admin/agenda-jobs')}
204
+ />
205
+ {t('agendaJob.titles.list', 'AgendaJobs')}
206
+ </Space>
207
+ }
208
+ extra={
209
+ <ListPageHeader
210
+ actions={[
211
+ ...tableConfig.toolbar.actions,
212
+ ...tableConfig.rowSelection.actions,
213
+ ]}
214
+ importProps={importProps}
215
+ onExport={triggerExport}
216
+ exportLoading={exportLoading}
217
+ onRefresh={() => tableQuery.refetch()}
218
+ resourceName={resourceName}
219
+ />
220
+ }
221
+ />
222
+
223
+ {/* Search Card */}
224
+ <Card {...SEARCH_CARD_PROPS}>
225
+ <SearchBar
226
+ formProps={searchFormProps}
227
+ t={t}
228
+ resourceName={resourceName}
229
+ searchPlaceholder={searchPlaceholder}
230
+ // Render props examples (uncomment to customize):
231
+ // renderKeywordInput={(field, defaultInput) => <Input.Search placeholder={field.placeholder} />}
232
+ // renderSubmitButton={(defaultButton) => <Space>{defaultButton}<Button>Reset</Button></Space>}
233
+ // extraActions={<Button type="link">Advanced</Button>}
234
+ />
235
+ </Card>
236
+
237
+ {/* Table Card */}
238
+ <Card className="card-table">
239
+ {/* BulkActions 无选中且非检查模式时返回 null,配合 .card-table > div:empty 隐藏空隙 */}
240
+ <div style={{ marginBottom: 16 }}>
190
241
  <BulkActions
191
242
  selectedRowKeys={batchOps.selectedRowKeys}
192
243
  actions={tableConfig.rowSelection.actions}
@@ -203,65 +254,38 @@ export const AgendaJobList: React.FC<IResourceComponentsProps> = () => {
203
254
  // renderSelectionCount={(count) => <Badge count={count}><Tag>Selected</Tag></Badge>}
204
255
  // extraActions={<Button>Custom Action</Button>}
205
256
  />
206
- ),
207
- extra: (
208
- <ListPageHeader
209
- actions={[
210
- ...tableConfig.toolbar.actions,
211
- ...tableConfig.rowSelection.actions,
212
- ]}
213
- importProps={importProps}
214
- onExport={triggerExport}
215
- exportLoading={exportLoading}
216
- onRefresh={() => tableQuery.refetch()}
217
- resourceName={resourceName}
218
- />
219
- ),
220
- }}
221
- >
222
- {/* Search Bar */}
223
- <SearchBar
224
- formProps={searchFormProps}
225
- t={t}
226
- resourceName={resourceName}
227
- searchPlaceholder={searchPlaceholder}
228
- // Render props examples (uncomment to customize):
229
- // renderKeywordInput={(field, defaultInput) => <Input.Search placeholder={field.placeholder} />}
230
- // renderSubmitButton={(defaultButton) => <Space>{defaultButton}<Button>Reset</Button></Space>}
231
- // extraActions={<Button type="link">Advanced</Button>}
232
- />
233
-
234
- {/* Data Table */}
235
- <Table {...tableProps} rowKey="id" rowSelection={batchOps.rowSelection}>
236
- {columns.map((col) =>
237
- getTableColumnV2({ col, context: columnContext }),
238
- )}
239
-
240
- {/* Actions Column */}
241
- <Table.Column<AgendaJob>
242
- title={t('table.actions')}
243
- dataIndex="actions"
244
- {...getRowActionsColumnProps(resourceName)}
245
- render={(_, record) => (
246
- <RowActions
247
- record={record}
248
- actions={tableConfig.rowActions.actions}
249
- visibleNum={tableConfig.rowActions.visibleNum}
250
- onUpdate={batchOps.updateOne}
251
- updateLoading={batchOps.updateManyIsLoading}
252
- t={t}
253
- resourceName={resourceName}
254
- // Render props examples (uncomment to customize):
255
- // showClone={false}
256
- // showDelete={false}
257
- // renderEditButton={(id) => <Button type="link" size="small">Quick Edit</Button>}
258
- // renderDeleteButton={(id) => <Popconfirm title="Sure?"><Button danger size="small">Remove</Button></Popconfirm>}
259
- // extraPrimaryActions={<Button type="text" size="small">Quick View</Button>}
260
- />
257
+ </div>
258
+ <Table {...tableProps} rowKey="id" rowSelection={batchOps.rowSelection}>
259
+ {columns.map((col) =>
260
+ getTableColumnV2({ col, context: columnContext }),
261
261
  )}
262
- />
263
- </Table>
264
- </List>
262
+
263
+ {/* Actions Column */}
264
+ <Table.Column<AgendaJob>
265
+ title={t('table.actions')}
266
+ dataIndex="actions"
267
+ {...getRowActionsColumnProps(resourceName)}
268
+ render={(_, record) => (
269
+ <RowActions
270
+ record={record}
271
+ actions={tableConfig.rowActions.actions}
272
+ visibleNum={tableConfig.rowActions.visibleNum}
273
+ onUpdate={batchOps.updateOne}
274
+ updateLoading={batchOps.updateManyIsLoading}
275
+ t={t}
276
+ resourceName={resourceName}
277
+ // Render props examples (uncomment to customize):
278
+ // showClone={false}
279
+ // showDelete={false}
280
+ // renderEditButton={(id) => <Button type="link" size="small">Quick Edit</Button>}
281
+ // renderDeleteButton={(id) => <Popconfirm title="Sure?"><Button danger size="small">Remove</Button></Popconfirm>}
282
+ // extraPrimaryActions={<Button type="text" size="small">Quick View</Button>}
283
+ />
284
+ )}
285
+ />
286
+ </Table>
287
+ </Card>
288
+ </div>
265
289
  );
266
290
  };
267
291
 
@@ -6,7 +6,6 @@ import React, {
6
6
  useState,
7
7
  } from 'react';
8
8
  import { DeleteButton, EditButton, useTable } from '@refinedev/antd';
9
- import { useGetIdentity } from '@refinedev/core';
10
9
  import {
11
10
  Table,
12
11
  Button,
@@ -28,7 +27,8 @@ import {
28
27
  PlusOutlined,
29
28
  EyeOutlined,
30
29
  TeamOutlined,
31
- CodeOutlined,
30
+ ExportOutlined,
31
+ ImportOutlined,
32
32
  } from '@ant-design/icons';
33
33
  import {
34
34
  DndContext,
@@ -63,7 +63,8 @@ import type {
63
63
  } from './types';
64
64
  import { usePageManagement } from './hooks/usePageManagement';
65
65
  import { useTranslation } from 'react-i18next';
66
- import { SqlModal, generatePageSql } from '../../components/SqlModal';
66
+ import { TransferExportModal } from '@/components/TransferExportModal';
67
+ import { TransferImportModal } from '@/components/TransferImportModal';
67
68
  import { PageHeader } from 'components';
68
69
  import { PAGE_WRAPPER_STYLE } from 'constants/layout';
69
70
 
@@ -176,8 +177,7 @@ const DraggableRow: React.FC<DraggableRowProps> = ({ children, ...props }) => {
176
177
  };
177
178
 
178
179
  export const PageManagementListPage: React.FC = () => {
179
- const { i18n } = useTranslation();
180
- const { data: identity } = useGetIdentity<any>();
180
+ const { t, i18n } = useTranslation();
181
181
  const { tableProps } = useTable({
182
182
  resource: 'page',
183
183
  pagination: {
@@ -382,109 +382,8 @@ export const PageManagementListPage: React.FC = () => {
382
382
  const [activeTab, setActiveTab] = useState<string>('regular');
383
383
 
384
384
  // State for SQL modal
385
- const [sqlModal, setSqlModal] = useState<{
386
- open: boolean;
387
- title: string;
388
- sql: string;
389
- }>({ open: false, title: '', sql: '' });
390
-
391
- const handleShowSql = useCallback(
392
- (record: any, hasChildren: boolean) => {
393
- const pagesArray: any[] = Array.isArray(pageListData?.data)
394
- ? pageListData.data
395
- : [];
396
-
397
- // Build pageResources for the given page
398
- const buildPageResources = (pageId: number) => {
399
- const associationsArray: any[] = Array.isArray(
400
- pageResourceAssociationsData?.data,
401
- )
402
- ? pageResourceAssociationsData.data
403
- : [];
404
- return associationsArray
405
- .filter(
406
- (assoc: any) => safelyConvertToNumber(assoc?.pageId) === pageId,
407
- )
408
- .map((assoc: any) => {
409
- const resourceId = safelyConvertToNumber(assoc?.resourceId);
410
- // Prefer nested resource object, then fall back to the id-keyed lookup map (which includes code).
411
- // The final fallback `String(resourceId)` should never be reached if availableResourcesData is loaded.
412
- const resourceRecord =
413
- assoc?.resource ??
414
- (resourceId !== null ? resourceLookupByIdMap[resourceId] : null);
415
- const resourceCode =
416
- resourceRecord?.code ??
417
- (resourceId !== null ? String(resourceId) : 'unknown');
418
- const resourceName =
419
- resourceRecord?.name ||
420
- resourceRecord?.code ||
421
- `Resource ${resourceId}`;
422
- const actions = extractPermissionActionsFromAssociation(
423
- assoc,
424
- resourceRecord,
425
- );
426
- return { resourceCode, resourceName, actions };
427
- });
428
- };
429
-
430
- const pageResources = buildPageResources(record.id);
431
-
432
- // Collect child pages if this is a parent
433
- const children: Array<{
434
- page: any;
435
- pageResources: Array<{
436
- resourceCode: string;
437
- resourceName: string;
438
- actions: string[];
439
- }>;
440
- }> = [];
441
- if (hasChildren) {
442
- const childPages = pagesArray.filter(
443
- (p: any) =>
444
- safelyConvertToNumber(p?.parentId ?? p?.parent_id) === record.id,
445
- );
446
- for (const child of childPages) {
447
- const childId = safelyConvertToNumber(child?.id);
448
- if (childId === null) continue;
449
- children.push({
450
- page: child,
451
- pageResources: buildPageResources(childId),
452
- });
453
- }
454
- }
455
-
456
- // Resolve parent's code from the in-memory page list so the SQL uses a
457
- // portable subquery (SELECT id … WHERE code = '…') instead of a raw
458
- // numeric parentId that differs between environments.
459
- const parentId = safelyConvertToNumber(
460
- record?.parentId ?? record?.parent_id,
461
- );
462
- const parentPage =
463
- parentId !== null
464
- ? pagesArray.find(
465
- (p: any) => safelyConvertToNumber(p?.id) === parentId,
466
- )
467
- : null;
468
- const recordWithParentCode = {
469
- ...record,
470
- parentCode: parentPage?.code ?? null,
471
- };
472
-
473
- const sql = generatePageSql(
474
- recordWithParentCode,
475
- pageResources,
476
- children,
477
- { authorId: identity?.userid, authorName: identity?.username },
478
- );
479
- setSqlModal({ open: true, title: `SQL — Page: ${record.name}`, sql });
480
- },
481
- [
482
- pageListData,
483
- pageResourceAssociationsData,
484
- resourceLookupByIdMap,
485
- identity,
486
- ],
487
- );
385
+ const [exportEndpoint, setExportEndpoint] = useState<string | null>(null);
386
+ const [importOpen, setImportOpen] = useState(false);
488
387
 
489
388
  // Ref for expand button click/double-click disambiguation
490
389
  const expandClickTimer = useRef<number | null>(null);
@@ -973,12 +872,16 @@ export const PageManagementListPage: React.FC = () => {
973
872
  setActiveModalState({ modalType: 'edit', targetPageId: record.id })
974
873
  }
975
874
  />
976
- <Tooltip title="Generate SQL">
875
+ <Tooltip title="Export">
977
876
  <Button
978
877
  type="default"
979
878
  size="small"
980
- icon={<CodeOutlined />}
981
- onClick={() => handleShowSql(record, hasChildren)}
879
+ icon={<ExportOutlined />}
880
+ onClick={() =>
881
+ setExportEndpoint(
882
+ `page/${encodeURIComponent(record.code)}/export-token`,
883
+ )
884
+ }
982
885
  />
983
886
  </Tooltip>
984
887
  {/* Only show delete button for leaf nodes (pages without children) */}
@@ -1186,17 +1089,25 @@ export const PageManagementListPage: React.FC = () => {
1186
1089
  <PageHeader
1187
1090
  title="Page mgnts"
1188
1091
  extra={
1189
- <Button
1190
- type="primary"
1191
- onClick={() =>
1192
- setActiveModalState({
1193
- modalType: 'create',
1194
- targetPageId: null,
1195
- })
1196
- }
1197
- >
1198
- Create Page
1199
- </Button>
1092
+ <Space>
1093
+ <Button
1094
+ icon={<ImportOutlined />}
1095
+ onClick={() => setImportOpen(true)}
1096
+ >
1097
+ Import
1098
+ </Button>
1099
+ <Button
1100
+ type="primary"
1101
+ onClick={() =>
1102
+ setActiveModalState({
1103
+ modalType: 'create',
1104
+ targetPageId: null,
1105
+ })
1106
+ }
1107
+ >
1108
+ Create Page
1109
+ </Button>
1110
+ </Space>
1200
1111
  }
1201
1112
  />
1202
1113
 
@@ -1267,11 +1178,24 @@ export const PageManagementListPage: React.FC = () => {
1267
1178
  />
1268
1179
  )}
1269
1180
 
1270
- <SqlModal
1271
- open={sqlModal.open}
1272
- onClose={() => setSqlModal({ open: false, title: '', sql: '' })}
1273
- title={sqlModal.title}
1274
- sql={sqlModal.sql}
1181
+ <TransferExportModal
1182
+ open={!!exportEndpoint}
1183
+ exportTokenEndpoint={exportEndpoint}
1184
+ entity="page"
1185
+ onClose={() => setExportEndpoint(null)}
1186
+ />
1187
+ <TransferImportModal
1188
+ open={importOpen}
1189
+ onClose={() => setImportOpen(false)}
1190
+ onSuccess={() => {
1191
+ refetchPageList(pageListRequestQuery ?? {});
1192
+ refetchPageResourceAssociations(
1193
+ pageResourceAssociationRequestQuery ?? {},
1194
+ );
1195
+ }}
1196
+ previewEndpoint="page/import-preview"
1197
+ confirmEndpoint="page/import-confirm"
1198
+ successHint={t('transfer.import.pageRoleHint')}
1275
1199
  />
1276
1200
  </div>
1277
1201
  );