@gadmin2n/schematics 0.0.136 → 0.0.138

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 (94) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +5 -5
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +2 -1
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
  54. package/package.json +1 -1
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
@@ -0,0 +1,84 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { Button, Input, Modal, Space, message } from 'antd';
3
+ import { CopyOutlined, LoadingOutlined } from '@ant-design/icons';
4
+ import { generateExportToken } from '../canvasApi';
5
+
6
+ interface ExportModalProps {
7
+ open: boolean;
8
+ canvasId: string | null;
9
+ onClose: () => void;
10
+ }
11
+
12
+ export function ExportModal({ open, canvasId, onClose }: ExportModalProps) {
13
+ const [url, setUrl] = useState<string>('');
14
+ const [loading, setLoading] = useState(false);
15
+
16
+ useEffect(() => {
17
+ if (open && canvasId) {
18
+ setUrl('');
19
+ setLoading(true);
20
+ generateExportToken(canvasId)
21
+ .then((res) => setUrl(res.url))
22
+ .catch((e: any) =>
23
+ message.error(e?.message ?? 'Failed to generate export URL'),
24
+ )
25
+ .finally(() => setLoading(false));
26
+ }
27
+ }, [open, canvasId]);
28
+
29
+ const handleCopy = useCallback(async () => {
30
+ try {
31
+ await navigator.clipboard.writeText(url);
32
+ message.success('URL copied to clipboard');
33
+ } catch {
34
+ // Fallback for older browsers
35
+ const textarea = document.getElementById(
36
+ 'canvas-export-url-input',
37
+ ) as HTMLTextAreaElement;
38
+ textarea?.select();
39
+ document.execCommand('copy');
40
+ message.success('URL copied to clipboard');
41
+ }
42
+ }, [url]);
43
+
44
+ return (
45
+ <Modal
46
+ title="Export Canvas"
47
+ open={open}
48
+ onCancel={onClose}
49
+ footer={
50
+ <Space>
51
+ <Button onClick={onClose}>Close</Button>
52
+ <Button
53
+ type="primary"
54
+ icon={<CopyOutlined />}
55
+ onClick={handleCopy}
56
+ disabled={!url}
57
+ >
58
+ Copy URL
59
+ </Button>
60
+ </Space>
61
+ }
62
+ >
63
+ {loading ? (
64
+ <div style={{ textAlign: 'center', padding: 24 }}>
65
+ <LoadingOutlined style={{ fontSize: 24 }} />
66
+ <p>Generating export URL...</p>
67
+ </div>
68
+ ) : (
69
+ <>
70
+ <p style={{ marginBottom: 8, color: '#666' }}>
71
+ Export URL (valid for 30 minutes):
72
+ </p>
73
+ <Input.TextArea
74
+ id="canvas-export-url-input"
75
+ value={url}
76
+ readOnly
77
+ autoSize={{ minRows: 3, maxRows: 5 }}
78
+ onFocus={(e) => e.target.select()}
79
+ />
80
+ </>
81
+ )}
82
+ </Modal>
83
+ );
84
+ }
@@ -0,0 +1,166 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { Button, Input, Modal, Space, Spin, message } from 'antd';
3
+ import { CloudDownloadOutlined, CheckOutlined } from '@ant-design/icons';
4
+ import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued';
5
+ import { importConfirm, importPreview } from '../canvasApi';
6
+
7
+ interface ImportModalProps {
8
+ open: boolean;
9
+ onClose: () => void;
10
+ onSuccess: () => void;
11
+ }
12
+
13
+ type Phase = 'input' | 'preview' | 'confirming';
14
+
15
+ export function ImportModal({ open, onClose, onSuccess }: ImportModalProps) {
16
+ const [sourceUrl, setSourceUrl] = useState('');
17
+ const [phase, setPhase] = useState<Phase>('input');
18
+ const [loading, setLoading] = useState(false);
19
+ const [currentJson, setCurrentJson] = useState('');
20
+ const [incomingJson, setIncomingJson] = useState('');
21
+ const [payload, setPayload] = useState<any>(null);
22
+
23
+ const reset = useCallback(() => {
24
+ setSourceUrl('');
25
+ setPhase('input');
26
+ setLoading(false);
27
+ setCurrentJson('');
28
+ setIncomingJson('');
29
+ setPayload(null);
30
+ }, []);
31
+
32
+ const handleClose = useCallback(() => {
33
+ reset();
34
+ onClose();
35
+ }, [reset, onClose]);
36
+
37
+ const handleFetchPreview = useCallback(async () => {
38
+ if (!sourceUrl.trim()) {
39
+ message.warning('Please paste the export URL');
40
+ return;
41
+ }
42
+
43
+ setLoading(true);
44
+ try {
45
+ // Step 1: Fetch from source URL (cross-origin)
46
+ const response = await fetch(sourceUrl.trim());
47
+ if (!response.ok) {
48
+ const errorData = await response.json().catch(() => null);
49
+ throw new Error(
50
+ errorData?.message || `Failed to fetch: ${response.status}`,
51
+ );
52
+ }
53
+ const exportData = await response.json();
54
+
55
+ // Step 2: Send to our import-preview endpoint
56
+ const preview = await importPreview(exportData);
57
+
58
+ // Step 3: Format for diff viewer
59
+ setCurrentJson(
60
+ preview.current ? JSON.stringify(preview.current, null, 2) : '',
61
+ );
62
+ setIncomingJson(JSON.stringify(preview.incoming, null, 2));
63
+ setPayload(exportData);
64
+ setPhase('preview');
65
+ } catch (e: any) {
66
+ if (e.message?.includes('expired') || e.message?.includes('invalid')) {
67
+ message.error(
68
+ 'Export link has expired or is invalid. Please generate a new one from the source environment.',
69
+ );
70
+ } else {
71
+ message.error(
72
+ e?.message ||
73
+ 'Cannot reach the source environment. Please check the URL and try again.',
74
+ );
75
+ }
76
+ } finally {
77
+ setLoading(false);
78
+ }
79
+ }, [sourceUrl]);
80
+
81
+ const handleConfirm = useCallback(async () => {
82
+ if (!payload) return;
83
+ setPhase('confirming');
84
+ try {
85
+ const result = await importConfirm(payload);
86
+
87
+ message.success(`Canvas "${result.canvasName}" imported successfully!`);
88
+ reset();
89
+ onClose();
90
+ onSuccess();
91
+ } catch (e: any) {
92
+ message.error(e?.message ?? 'Import failed');
93
+ setPhase('preview');
94
+ }
95
+ }, [payload, reset, onClose, onSuccess]);
96
+
97
+ return (
98
+ <Modal
99
+ title="Import Canvas"
100
+ open={open}
101
+ onCancel={handleClose}
102
+ width={phase === 'preview' ? '90vw' : 520}
103
+ footer={
104
+ phase === 'input' ? (
105
+ <Space>
106
+ <Button onClick={handleClose}>Cancel</Button>
107
+ <Button
108
+ type="primary"
109
+ icon={<CloudDownloadOutlined />}
110
+ onClick={handleFetchPreview}
111
+ loading={loading}
112
+ >
113
+ Fetch &amp; Preview
114
+ </Button>
115
+ </Space>
116
+ ) : (
117
+ <Space>
118
+ <Button onClick={handleClose}>Cancel</Button>
119
+ <Button
120
+ type="primary"
121
+ icon={<CheckOutlined />}
122
+ onClick={handleConfirm}
123
+ loading={phase === 'confirming'}
124
+ >
125
+ Confirm Import
126
+ </Button>
127
+ </Space>
128
+ )
129
+ }
130
+ >
131
+ {phase === 'input' && (
132
+ <>
133
+ <p style={{ marginBottom: 8 }}>
134
+ Paste the export URL from the source environment:
135
+ </p>
136
+ <Input.TextArea
137
+ value={sourceUrl}
138
+ onChange={(e) => setSourceUrl(e.target.value)}
139
+ placeholder="https://dev.example.com/api/canvas/export/abc123?token=..."
140
+ autoSize={{ minRows: 3, maxRows: 5 }}
141
+ />
142
+ </>
143
+ )}
144
+
145
+ {phase === 'preview' && (
146
+ <div style={{ maxHeight: '70vh', overflow: 'auto' }}>
147
+ <ReactDiffViewer
148
+ oldValue={currentJson}
149
+ newValue={incomingJson}
150
+ splitView
151
+ compareMethod={DiffMethod.LINES}
152
+ leftTitle="Current (this environment)"
153
+ rightTitle="Incoming (source environment)"
154
+ />
155
+ </div>
156
+ )}
157
+
158
+ {phase === 'confirming' && (
159
+ <div style={{ textAlign: 'center', padding: 40 }}>
160
+ <Spin size="large" />
161
+ <p style={{ marginTop: 16 }}>Importing canvas...</p>
162
+ </div>
163
+ )}
164
+ </Modal>
165
+ );
166
+ }
@@ -7,7 +7,7 @@ export interface CanvasSnapshot {
7
7
  layout: LayoutItem[];
8
8
  }
9
9
 
10
- /** Max number of undo steps retained before the oldest is dropped. */
10
+ /** Max number of undo/redo steps retained before the oldest is dropped. */
11
11
  const MAX_HISTORY_SIZE = 50;
12
12
 
13
13
  /**
@@ -19,12 +19,14 @@ const MAX_HISTORY_SIZE = 50;
19
19
  const COALESCE_WINDOW_MS = 5000;
20
20
 
21
21
  export function useCanvasHistory() {
22
- const stackRef = useRef<CanvasSnapshot[]>([]);
22
+ const undoStackRef = useRef<CanvasSnapshot[]>([]);
23
+ const redoStackRef = useRef<CanvasSnapshot[]>([]);
23
24
  // Timestamp of the last coalesce=true push that extended the current
24
25
  // batch; null when the last push was discrete (coalesce=false) or no
25
26
  // batch is in progress.
26
27
  const lastCoalescedAtRef = useRef<number | null>(null);
27
28
  const [canUndo, setCanUndo] = useState(false);
29
+ const [canRedo, setCanRedo] = useState(false);
28
30
 
29
31
  const push = useCallback((snapshot: CanvasSnapshot, coalesce: boolean) => {
30
32
  const now = Date.now();
@@ -40,26 +42,63 @@ export function useCanvasHistory() {
40
42
  return;
41
43
  }
42
44
 
43
- stackRef.current.push(snapshot);
44
- if (stackRef.current.length > MAX_HISTORY_SIZE) {
45
- stackRef.current.shift();
45
+ undoStackRef.current.push(snapshot);
46
+ if (undoStackRef.current.length > MAX_HISTORY_SIZE) {
47
+ undoStackRef.current.shift();
46
48
  }
47
49
  lastCoalescedAtRef.current = coalesce ? now : null;
48
50
  setCanUndo(true);
49
- }, []);
50
51
 
51
- const pop = useCallback((): CanvasSnapshot | undefined => {
52
- const snapshot = stackRef.current.pop();
53
- lastCoalescedAtRef.current = null;
54
- setCanUndo(stackRef.current.length > 0);
55
- return snapshot;
52
+ // A new edit invalidates whatever was undone before it.
53
+ if (redoStackRef.current.length > 0) {
54
+ redoStackRef.current = [];
55
+ setCanRedo(false);
56
+ }
56
57
  }, []);
57
58
 
59
+ const undo = useCallback(
60
+ (current: CanvasSnapshot): CanvasSnapshot | undefined => {
61
+ const snapshot = undoStackRef.current.pop();
62
+ if (!snapshot) return undefined;
63
+
64
+ redoStackRef.current.push(current);
65
+ if (redoStackRef.current.length > MAX_HISTORY_SIZE) {
66
+ redoStackRef.current.shift();
67
+ }
68
+
69
+ lastCoalescedAtRef.current = null;
70
+ setCanUndo(undoStackRef.current.length > 0);
71
+ setCanRedo(true);
72
+ return snapshot;
73
+ },
74
+ [],
75
+ );
76
+
77
+ const redo = useCallback(
78
+ (current: CanvasSnapshot): CanvasSnapshot | undefined => {
79
+ const snapshot = redoStackRef.current.pop();
80
+ if (!snapshot) return undefined;
81
+
82
+ undoStackRef.current.push(current);
83
+ if (undoStackRef.current.length > MAX_HISTORY_SIZE) {
84
+ undoStackRef.current.shift();
85
+ }
86
+
87
+ lastCoalescedAtRef.current = null;
88
+ setCanRedo(redoStackRef.current.length > 0);
89
+ setCanUndo(true);
90
+ return snapshot;
91
+ },
92
+ [],
93
+ );
94
+
58
95
  const clear = useCallback(() => {
59
- stackRef.current = [];
96
+ undoStackRef.current = [];
97
+ redoStackRef.current = [];
60
98
  lastCoalescedAtRef.current = null;
61
99
  setCanUndo(false);
100
+ setCanRedo(false);
62
101
  }, []);
63
102
 
64
- return { push, pop, clear, canUndo };
103
+ return { push, undo, redo, clear, canUndo, canRedo };
65
104
  }
@@ -24,6 +24,8 @@ export interface DslViewHandle {
24
24
  copy: () => Promise<void>;
25
25
  /** Whether textarea content differs from last applied DSL. */
26
26
  isDirty: () => boolean;
27
+ /** Reset textarea to empty DSL template. */
28
+ clear: () => void;
27
29
  }
28
30
 
29
31
  function stringifyDsl(dsl: WorkflowDSL | null): string {
@@ -158,6 +160,10 @@ export const DslView = forwardRef<DslViewHandle, DslViewProps>(function DslView(
158
160
  apply: handleApply,
159
161
  copy: handleCopy,
160
162
  isDirty: () => text !== initial,
163
+ clear: () => {
164
+ setText(EMPTY_TEMPLATE);
165
+ setError(null);
166
+ },
161
167
  }),
162
168
  // eslint-disable-next-line react-hooks/exhaustive-deps
163
169
  [text, initial, onApply],
@@ -257,7 +257,12 @@ export function NodePropertyPanel({
257
257
  for (const [key, prop] of Object.entries<any>(properties)) {
258
258
  const existingValue = node.config?.[key];
259
259
  if (existingValue !== undefined) {
260
- mergedConfig[key] = existingValue;
260
+ mergedConfig[key] =
261
+ key === 'approvers' && Array.isArray(existingValue)
262
+ ? existingValue.map((a: any) =>
263
+ typeof a === 'string' ? { type: 'user', id: a } : a,
264
+ )
265
+ : existingValue;
261
266
  } else if (prop.type === 'string') {
262
267
  mergedConfig[key] = '';
263
268
  } else if (prop.type === 'number') {
@@ -526,6 +526,7 @@ export default function WorkflowEditorPage() {
526
526
  flexShrink: 0,
527
527
  display: 'flex',
528
528
  alignItems: 'center',
529
+ justifyContent: 'space-between',
529
530
  }}
530
531
  >
531
532
  <Segmented
@@ -536,6 +537,22 @@ export default function WorkflowEditorPage() {
536
537
  { label: 'DSL', value: 'dsl' },
537
538
  ]}
538
539
  />
540
+ <Button
541
+ danger
542
+ size="small"
543
+ style={{ marginRight: 8 }}
544
+ onClick={() => {
545
+ if (viewMode === 'dsl') {
546
+ dslViewRef.current?.clear();
547
+ } else {
548
+ setDsl({ nodes: [], edges: [] });
549
+ setHasUnsavedChanges(true);
550
+ setSelectedNodeId(null);
551
+ }
552
+ }}
553
+ >
554
+ 清空
555
+ </Button>
539
556
  </div>
540
557
 
541
558
  {/* Main area: Flow + Property Panel */}
@@ -23,6 +23,7 @@ import {
23
23
  StopOutlined,
24
24
  } from '@ant-design/icons';
25
25
  import { useNavigate, useParams } from 'react-router-dom';
26
+ import { useGetIdentity } from '@refinedev/core';
26
27
  import { customRequest } from 'helpers/http';
27
28
  import { EnhancedFlowRenderer } from './components/EnhancedFlowRenderer';
28
29
  import type { WorkflowDSL } from './types';
@@ -103,6 +104,7 @@ function getStepStatus(
103
104
  export default function WorkflowInstanceDetailPage() {
104
105
  const { instanceId } = useParams<{ instanceId: string }>();
105
106
  const navigate = useNavigate();
107
+ const { data: identity } = useGetIdentity<{ id: string }>();
106
108
  const [data, setData] = useState<InstanceDetail | null>(null);
107
109
  const [loading, setLoading] = useState(true);
108
110
  const [actionLoading, setActionLoading] = useState(false);
@@ -175,14 +177,14 @@ export default function WorkflowInstanceDetailPage() {
175
177
  const handleApprove = async (nodeId: string, approved: boolean) => {
176
178
  setActionLoading(true);
177
179
  try {
178
- await customRequest(`workflow/instance/${instanceId}/signal`, 'POST', {
179
- signalName: 'approval',
180
- payload: { nodeId, approved, approvedBy: 'current_user', comment: '' },
180
+ await customRequest(`workflow/instance/${instanceId}/approve`, 'POST', {
181
+ nodeId,
182
+ approved,
181
183
  });
182
184
  message.success(approved ? 'Approved' : 'Rejected');
183
185
  fetchData();
184
186
  } catch (e: any) {
185
- message.error(e?.message || 'Failed to send signal');
187
+ message.error(e?.message || 'Failed to submit approval');
186
188
  } finally {
187
189
  setActionLoading(false);
188
190
  }
@@ -370,8 +372,14 @@ export default function WorkflowInstanceDetailPage() {
370
372
  size="small"
371
373
  current={-1}
372
374
  items={data.nodeExecutions.map((ne) => {
375
+ const approverIds: string[] =
376
+ (ne.input as any)?.approvers || [];
377
+ const canApprove =
378
+ !!identity?.id && approverIds.includes(identity.id);
373
379
  const isApprovalPending =
374
- ne.nodeType === 'approval' && ne.status === 'RUNNING';
380
+ ne.nodeType === 'approval' &&
381
+ ne.status === 'RUNNING' &&
382
+ canApprove;
375
383
  const isSelected = selectedNodeId === ne.nodeId;
376
384
  return {
377
385
  title: (
@@ -724,7 +732,11 @@ export default function WorkflowInstanceDetailPage() {
724
732
 
725
733
  {/* Approval actions */}
726
734
  {selectedExecution.nodeType === 'approval' &&
727
- selectedExecution.status === 'RUNNING' && (
735
+ selectedExecution.status === 'RUNNING' &&
736
+ !!identity?.id &&
737
+ (
738
+ (selectedExecution.input as any)?.approvers || []
739
+ ).includes(identity.id) && (
728
740
  <Space style={{ width: '100%' }}>
729
741
  <Button
730
742
  type="primary"