@gadmin2n/schematics 0.0.140 → 0.0.142

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 (46) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +1 -1
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/gen-jwt.ts +8 -1
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +32 -4
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +39 -33
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +34 -1
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +26 -0
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +149 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +20 -6
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.spec.ts +85 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.ts +157 -0
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +59 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -4
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +125 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +109 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/PageHeader.tsx +46 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +6 -4
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +30 -2
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +20 -1
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +20 -1
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +9 -20
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +141 -138
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +8 -19
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/inbox.tsx +12 -19
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +47 -150
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permissionReadme/index.tsx +13 -12
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +110 -113
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +61 -64
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +187 -200
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/index.tsx +46 -44
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NewNodeDefaults.test.tsx +6 -2
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +50 -41
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +9 -22
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +4 -8
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/index.tsx +9 -22
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.test.tsx +42 -0
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.tsx +77 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.test.tsx +29 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.tsx +245 -55
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +152 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +417 -161
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +5 -1
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +20 -0
  46. package/package.json +1 -1
@@ -1,20 +1,37 @@
1
- import { ArrowLeftOutlined } from '@ant-design/icons';
1
+ import {
2
+ ArrowLeftOutlined,
3
+ CheckCircleFilled,
4
+ EditOutlined,
5
+ EyeInvisibleOutlined,
6
+ EyeOutlined,
7
+ PlusOutlined,
8
+ ReloadOutlined,
9
+ } from '@ant-design/icons';
2
10
  import { useGetIdentity } from '@refinedev/core';
3
11
  import {
4
12
  Alert,
5
13
  Button,
6
14
  Card,
15
+ Descriptions,
16
+ Divider,
17
+ Drawer,
18
+ Empty,
7
19
  Form,
8
20
  Input,
21
+ Result,
22
+ Space,
9
23
  Spin,
24
+ Tag,
10
25
  Typography,
11
26
  message,
27
+ theme,
12
28
  } from 'antd';
13
29
  import { customRequest } from 'helpers/http';
14
- import { useEffect, useState } from 'react';
30
+ import { HEADER_CARD_PROPS, PAGE_WRAPPER_STYLE } from 'constants/layout';
31
+ import { useCallback, useEffect, useState } from 'react';
15
32
  import { useNavigate } from 'react-router-dom';
16
33
 
17
- const { Title } = Typography;
34
+ const { Paragraph, Text, Title } = Typography;
18
35
 
19
36
  interface TenantConfig {
20
37
  id: number;
@@ -23,84 +40,169 @@ interface TenantConfig {
23
40
  namespace: string;
24
41
  dbUrl: string;
25
42
  displayName: string;
26
- runtimeBaseUrl: string;
27
- runtimeToken: string;
43
+ runtimeBaseUrl?: string | null;
44
+ runtimeToken?: string | null;
45
+ }
46
+
47
+ interface TenantConfigListResponse {
48
+ data: TenantConfig[];
49
+ }
50
+
51
+ type TenantConfigFormValues = Omit<TenantConfig, 'id'>;
52
+
53
+ const configSelect = {
54
+ id: true,
55
+ adminUrl: true,
56
+ adminToken: true,
57
+ namespace: true,
58
+ dbUrl: true,
59
+ displayName: true,
60
+ runtimeBaseUrl: true,
61
+ runtimeToken: true,
62
+ };
63
+
64
+ function maskSecret(value?: string | null) {
65
+ if (!value) return '未配置';
66
+ if (value.length <= 4) return '••••••••';
67
+ return `••••••••${value.slice(-4)}`;
68
+ }
69
+
70
+ function maskDatabaseUrl(value: string) {
71
+ return value.replace(/(\/\/[^:/?#]+:)[^@/?#]+(@)/, '$1••••••$2');
72
+ }
73
+
74
+ function SensitiveValue({
75
+ label,
76
+ value,
77
+ maskedValue,
78
+ }: {
79
+ label: string;
80
+ value?: string | null;
81
+ maskedValue?: string;
82
+ }) {
83
+ const [visible, setVisible] = useState(false);
84
+
85
+ if (!value) {
86
+ return <Text type="secondary">未配置</Text>;
87
+ }
88
+
89
+ return (
90
+ <Space size={6} style={{ maxWidth: '100%' }}>
91
+ <Text
92
+ code
93
+ style={{
94
+ overflowWrap: 'anywhere',
95
+ whiteSpace: 'normal',
96
+ }}
97
+ >
98
+ {visible ? value : (maskedValue ?? maskSecret(value))}
99
+ </Text>
100
+ <Button
101
+ type="text"
102
+ size="small"
103
+ aria-label={`${visible ? '隐藏' : '显示'} ${label}`}
104
+ icon={visible ? <EyeInvisibleOutlined /> : <EyeOutlined />}
105
+ onClick={() => setVisible((current) => !current)}
106
+ />
107
+ </Space>
108
+ );
28
109
  }
29
110
 
30
111
  export default function TemporalTenantPage() {
31
112
  const navigate = useNavigate();
32
- const [form] = Form.useForm();
113
+ const [form] = Form.useForm<TenantConfigFormValues>();
114
+ const { token } = theme.useToken();
33
115
  const { data: identity } = useGetIdentity<any>();
34
116
  const isAdmin = (identity?.configPerms?.roles || []).includes('SYSTEM_ADMIN');
35
117
 
36
118
  const [loading, setLoading] = useState(true);
37
119
  const [saving, setSaving] = useState(false);
120
+ const [editorOpen, setEditorOpen] = useState(false);
38
121
  const [existing, setExisting] = useState<TenantConfig | null>(null);
122
+ const [loadError, setLoadError] = useState(false);
39
123
 
40
- useEffect(() => {
41
- customRequest('temporalTenantConfig/findMany', 'POST', {
42
- take: 1,
43
- skip: 0,
44
- select: {
45
- id: true,
46
- adminUrl: true,
47
- adminToken: true,
48
- namespace: true,
49
- dbUrl: true,
50
- displayName: true,
51
- runtimeBaseUrl: true,
52
- runtimeToken: true,
124
+ const loadConfig = useCallback(async () => {
125
+ const res = await customRequest<TenantConfigListResponse>(
126
+ 'temporalTenantConfig/findMany',
127
+ 'POST',
128
+ {
129
+ take: 1,
130
+ skip: 0,
131
+ select: configSelect,
53
132
  },
54
- })
55
- .then((res: any) => {
56
- const item = res?.data?.data?.[0] ?? null;
57
- setExisting(item);
58
- if (item) form.setFieldsValue(item);
133
+ );
134
+ const item = res.data[0] ?? null;
135
+ setExisting(item);
136
+ setLoadError(false);
137
+ return item;
138
+ }, []);
139
+
140
+ useEffect(() => {
141
+ loadConfig()
142
+ .catch(() => {
143
+ setLoadError(true);
144
+ message.error('加载配置失败');
59
145
  })
60
- .catch(() => message.error('加载配置失败'))
61
146
  .finally(() => setLoading(false));
62
- }, [form]);
147
+ }, [loadConfig]);
148
+
149
+ const retryLoadConfig = async () => {
150
+ setLoading(true);
151
+ try {
152
+ await loadConfig();
153
+ } catch {
154
+ setLoadError(true);
155
+ message.error('加载配置失败');
156
+ } finally {
157
+ setLoading(false);
158
+ }
159
+ };
63
160
 
64
- const handleSave = async (values: Omit<TenantConfig, 'id'>) => {
161
+ const openEditor = () => {
162
+ form.resetFields();
163
+ if (existing) {
164
+ form.setFieldsValue(existing);
165
+ }
166
+ setEditorOpen(true);
167
+ };
168
+
169
+ const handleSave = async (values: TenantConfigFormValues) => {
65
170
  setSaving(true);
66
171
  try {
172
+ const payload = existing
173
+ ? { ...values, namespace: existing.namespace }
174
+ : values;
175
+
67
176
  if (existing) {
68
177
  await customRequest(
69
178
  `temporalTenantConfig/updateUnique/${existing.id}`,
70
179
  'PATCH',
71
- values,
180
+ payload,
72
181
  );
73
- message.success('更新成功,Temporal worker 已热重载连接');
182
+ message.success('配置已更新,Temporal Worker 将自动加载最新连接');
74
183
  } else {
75
- await customRequest('temporalTenantConfig/createOne', 'POST', values);
76
- message.success('注册成功');
184
+ await customRequest('temporalTenantConfig/createOne', 'POST', payload);
185
+ message.success('本 GAdmin 已注册到 Temporal');
77
186
  }
78
- const res: any = await customRequest(
79
- 'temporalTenantConfig/findMany',
80
- 'POST',
81
- {
82
- take: 1,
83
- skip: 0,
84
- select: {
85
- id: true,
86
- adminUrl: true,
87
- adminToken: true,
88
- namespace: true,
89
- dbUrl: true,
90
- displayName: true,
91
- runtimeBaseUrl: true,
92
- runtimeToken: true,
93
- },
94
- },
95
- );
96
- setExisting(res?.data?.data?.[0] ?? null);
187
+
188
+ setEditorOpen(false);
189
+ await loadConfig();
97
190
  } catch (e: any) {
98
- message.error(e?.response?.data?.message || '操作失败');
191
+ message.error(e?.response?.data?.message || '保存配置失败');
99
192
  } finally {
100
193
  setSaving(false);
101
194
  }
102
195
  };
103
196
 
197
+ const submitEditor = async () => {
198
+ try {
199
+ const values = await form.validateFields();
200
+ await handleSave(values);
201
+ } catch {
202
+ // Field-level validation messages are rendered by Ant Design.
203
+ }
204
+ };
205
+
104
206
  if (!isAdmin) {
105
207
  return (
106
208
  <div style={{ padding: 24 }}>
@@ -109,158 +211,312 @@ export default function TemporalTenantPage() {
109
211
  );
110
212
  }
111
213
 
112
- if (loading) {
113
- return (
114
- <div
115
- style={{ display: 'flex', justifyContent: 'center', paddingTop: 80 }}
116
- >
117
- <Spin size="large" />
118
- </div>
119
- );
120
- }
121
-
122
214
  return (
123
- <div
124
- style={{
125
- background: 'var(--ant-color-bg-layout)',
126
- minHeight: 'calc(100vh - 64px)',
127
- }}
128
- >
129
- <Card
130
- style={{ marginBottom: 16 }}
131
- styles={{ body: { padding: '16px 24px' } }}
132
- >
133
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
134
- <Button
135
- type="text"
136
- icon={<ArrowLeftOutlined />}
137
- onClick={() => navigate(-1)}
138
- />
139
- <div>
140
- <Title level={4} style={{ margin: 0 }}>
141
- 服务注册
142
- </Title>
215
+ <div style={PAGE_WRAPPER_STYLE}>
216
+ <Card {...HEADER_CARD_PROPS}>
217
+ <div
218
+ style={{
219
+ display: 'flex',
220
+ alignItems: 'center',
221
+ justifyContent: 'space-between',
222
+ gap: 16,
223
+ flexWrap: 'wrap',
224
+ }}
225
+ >
226
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
227
+ <Button
228
+ type="text"
229
+ icon={<ArrowLeftOutlined />}
230
+ onClick={() => navigate(-1)}
231
+ />
232
+ <div>
233
+ <Title level={4} style={{ margin: 0 }}>
234
+ 服务注册
235
+ </Title>
236
+ <Text type="secondary" style={{ fontSize: 12 }}>
237
+ 管理本 GAdmin 与 Temporal Worker 的连接配置
238
+ </Text>
239
+ </div>
143
240
  </div>
241
+
242
+ {!loading && !loadError && existing && (
243
+ <Button
244
+ type="primary"
245
+ aria-label="修改配置"
246
+ icon={<EditOutlined />}
247
+ onClick={openEditor}
248
+ >
249
+ 修改配置
250
+ </Button>
251
+ )}
144
252
  </div>
145
253
  </Card>
146
254
 
147
- <Card styles={{ body: { padding: '24px 32px' } }}>
148
- {existing && (
149
- <Alert
150
- type="info"
151
- showIcon
152
- message="修改 dbUrl 后,Temporal worker 会立即重载数据库连接"
153
- style={{ marginBottom: 24 }}
255
+ <Card>
256
+ {loading ? (
257
+ <div
258
+ style={{ display: 'grid', placeItems: 'center', minHeight: 320 }}
259
+ >
260
+ <Spin size="large" />
261
+ </div>
262
+ ) : loadError ? (
263
+ <Result
264
+ status="error"
265
+ title="无法确认注册状态"
266
+ subTitle="未能读取本 GAdmin 的 Temporal 配置。请检查网络或权限后重试。"
267
+ extra={
268
+ <Button
269
+ type="primary"
270
+ icon={<ReloadOutlined />}
271
+ onClick={retryLoadConfig}
272
+ >
273
+ 重新加载
274
+ </Button>
275
+ }
154
276
  />
277
+ ) : existing ? (
278
+ <>
279
+ <div
280
+ style={{
281
+ display: 'flex',
282
+ alignItems: 'center',
283
+ justifyContent: 'space-between',
284
+ gap: 16,
285
+ flexWrap: 'wrap',
286
+ padding: '16px 20px',
287
+ background: 'var(--ant-color-fill-secondary)',
288
+ border: '1px solid var(--ant-color-border-secondary)',
289
+ borderRadius: 8,
290
+ }}
291
+ >
292
+ <Space size={12}>
293
+ <CheckCircleFilled
294
+ style={{
295
+ color: token.colorSuccess,
296
+ fontSize: 22,
297
+ }}
298
+ />
299
+ <div>
300
+ <Text strong style={{ display: 'block' }}>
301
+ 已注册到 Temporal
302
+ </Text>
303
+ <Text type="secondary">{existing.displayName}</Text>
304
+ </div>
305
+ </Space>
306
+ <Tag color="success">{existing.namespace}</Tag>
307
+ </div>
308
+
309
+ <Divider orientation="left">租户身份</Divider>
310
+ <Descriptions
311
+ bordered
312
+ column={{ xs: 1, sm: 1, md: 2, lg: 2, xl: 2, xxl: 2 }}
313
+ styles={{ label: { width: 180 } }}
314
+ >
315
+ <Descriptions.Item label="显示名称">
316
+ {existing.displayName}
317
+ </Descriptions.Item>
318
+ <Descriptions.Item label="Namespace">
319
+ <Text code>{existing.namespace}</Text>
320
+ </Descriptions.Item>
321
+ </Descriptions>
322
+
323
+ <Divider orientation="left">Temporal 管理连接</Divider>
324
+ <Descriptions
325
+ bordered
326
+ column={1}
327
+ styles={{ label: { width: 180 } }}
328
+ >
329
+ <Descriptions.Item label="Admin URL">
330
+ <Text code>{existing.adminUrl}</Text>
331
+ </Descriptions.Item>
332
+ <Descriptions.Item label="Admin Token">
333
+ <SensitiveValue
334
+ label="Admin Token"
335
+ value={existing.adminToken}
336
+ />
337
+ </Descriptions.Item>
338
+ </Descriptions>
339
+
340
+ <Divider orientation="left">Worker 运行连接</Divider>
341
+ <Descriptions
342
+ bordered
343
+ column={1}
344
+ styles={{ label: { width: 180 } }}
345
+ >
346
+ <Descriptions.Item label="数据库连接地址">
347
+ <SensitiveValue
348
+ label="数据库连接地址"
349
+ value={existing.dbUrl}
350
+ maskedValue={maskDatabaseUrl(existing.dbUrl)}
351
+ />
352
+ </Descriptions.Item>
353
+ <Descriptions.Item label="Runtime API 地址">
354
+ {existing.runtimeBaseUrl ? (
355
+ <Text code>{existing.runtimeBaseUrl}</Text>
356
+ ) : (
357
+ <Text type="secondary">未配置</Text>
358
+ )}
359
+ </Descriptions.Item>
360
+ <Descriptions.Item label="Runtime Token">
361
+ <SensitiveValue
362
+ label="Runtime Token"
363
+ value={existing.runtimeToken}
364
+ />
365
+ </Descriptions.Item>
366
+ </Descriptions>
367
+
368
+ <Alert
369
+ type="info"
370
+ showIcon
371
+ message="保存修改后,Temporal Worker 会自动加载本 GAdmin 的最新连接配置。"
372
+ style={{ marginTop: 20 }}
373
+ />
374
+ </>
375
+ ) : (
376
+ <Empty
377
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
378
+ description={
379
+ <Space direction="vertical" size={4}>
380
+ <Text strong>本 GAdmin 尚未注册</Text>
381
+ <Text type="secondary">
382
+ 注册后,Temporal Worker 才能执行本项目的 Workflow。
383
+ </Text>
384
+ </Space>
385
+ }
386
+ >
387
+ <Button type="primary" icon={<PlusOutlined />} onClick={openEditor}>
388
+ 注册服务
389
+ </Button>
390
+ </Empty>
155
391
  )}
392
+ </Card>
393
+
394
+ <Drawer
395
+ title={existing ? '修改服务配置' : '注册本 GAdmin'}
396
+ width={600}
397
+ open={editorOpen}
398
+ destroyOnClose
399
+ maskClosable={!saving}
400
+ closable={!saving}
401
+ onClose={() => setEditorOpen(false)}
402
+ footer={
403
+ <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
404
+ <Button disabled={saving} onClick={() => setEditorOpen(false)}>
405
+ 取消
406
+ </Button>
407
+ <Button type="primary" loading={saving} onClick={submitEditor}>
408
+ {existing ? '保存修改' : '注册服务'}
409
+ </Button>
410
+ </div>
411
+ }
412
+ >
413
+ <Paragraph type="secondary" style={{ marginBottom: 24 }}>
414
+ 这些地址都需要从 Temporal Worker
415
+ 所在网络访问。敏感信息保存后默认遮挡。
416
+ </Paragraph>
417
+
156
418
  <Form
419
+ id="temporal-tenant-form"
157
420
  form={form}
158
421
  layout="vertical"
159
422
  onFinish={handleSave}
160
- style={{ maxWidth: 600 }}
423
+ requiredMark="optional"
161
424
  >
162
- <Title level={5} style={{ color: 'var(--ant-color-text-secondary)' }}>
163
- Admin API 连接
164
- </Title>
165
- <div
166
- style={{
167
- display: 'grid',
168
- gridTemplateColumns: '1fr 1fr',
169
- gap: '0 16px',
170
- }}
425
+ <Title level={5}>Temporal 管理连接</Title>
426
+ <Form.Item
427
+ label="Admin URL"
428
+ name="adminUrl"
429
+ rules={[{ required: true, message: '请输入 Admin URL' }]}
171
430
  >
172
- <Form.Item
173
- label="Admin URL"
174
- name="adminUrl"
175
- rules={[{ required: true, message: '请输入 Admin URL' }]}
176
- >
177
- <Input placeholder="http://localhost:8081" />
178
- </Form.Item>
179
- <Form.Item
180
- label="Admin Token"
181
- name="adminToken"
182
- rules={[{ required: true, message: '请输入 Admin Token' }]}
183
- >
184
- <Input.Password placeholder="32 位 hex token" />
185
- </Form.Item>
186
- </div>
187
-
188
- <Title
189
- level={5}
190
- style={{ color: 'var(--ant-color-text-secondary)', marginTop: 8 }}
431
+ <Input placeholder="http://localhost:8081" />
432
+ </Form.Item>
433
+ <Form.Item
434
+ label="Admin Token"
435
+ name="adminToken"
436
+ rules={[{ required: true, message: '请输入 Admin Token' }]}
191
437
  >
192
- 租户信息
438
+ <Input.Password
439
+ placeholder="Temporal Admin API Token"
440
+ autoComplete="new-password"
441
+ />
442
+ </Form.Item>
443
+
444
+ <Title level={5} style={{ marginTop: 28 }}>
445
+ 本 GAdmin
193
446
  </Title>
194
- <div
195
- style={{
196
- display: 'grid',
197
- gridTemplateColumns: '1fr 1fr',
198
- gap: '0 16px',
199
- }}
447
+ <Form.Item
448
+ label="Namespace"
449
+ name="namespace"
450
+ extra={
451
+ existing
452
+ ? 'Namespace 是远端租户标识,注册后不能在这里修改。'
453
+ : '用于隔离本 GAdmin 的 Workflow。'
454
+ }
455
+ rules={[{ required: true, message: '请输入 Namespace' }]}
200
456
  >
201
- <Form.Item
202
- label="Namespace"
203
- name="namespace"
204
- rules={[{ required: true, message: '请输入 namespace' }]}
205
- >
206
- <Input placeholder="gadmin-test" />
207
- </Form.Item>
208
- <Form.Item
209
- label="Display Name"
210
- name="displayName"
211
- rules={[{ required: true, message: '请输入展示名' }]}
212
- >
213
- <Input placeholder="Gadmin Test" />
214
- </Form.Item>
215
- </div>
216
-
457
+ <Input disabled={Boolean(existing)} placeholder="gadmin-test" />
458
+ </Form.Item>
459
+ <Form.Item
460
+ label="显示名称"
461
+ name="displayName"
462
+ rules={[{ required: true, message: '请输入显示名称' }]}
463
+ >
464
+ <Input placeholder="GAdmin Test" />
465
+ </Form.Item>
217
466
  <Form.Item
218
- label="DB URL(Temporal worker 视角)"
467
+ label="数据库连接地址"
219
468
  name="dbUrl"
220
- rules={[{ required: true, message: '请输入 DB URL' }]}
469
+ extra="填写 Temporal Worker 能访问到的 PostgreSQL 地址。"
470
+ rules={[{ required: true, message: '请输入数据库连接地址' }]}
221
471
  >
222
- <Input placeholder="postgresql://user:pw@host.containers.internal:5432/gadmin_demo" />
472
+ <Input.Password
473
+ placeholder="postgresql://user:password@host:5432/database"
474
+ autoComplete="new-password"
475
+ />
223
476
  </Form.Item>
224
477
 
225
- <Title
226
- level={5}
227
- style={{ color: 'var(--ant-color-text-secondary)', marginTop: 24 }}
228
- >
229
- Runtime API 连接
478
+ <Title level={5} style={{ marginTop: 28 }}>
479
+ Runtime 连接
230
480
  </Title>
231
481
  <Form.Item
232
- label="Runtime Base URL(Temporal worker 视角)"
482
+ label="Runtime API 地址"
233
483
  name="runtimeBaseUrl"
484
+ extra="填写 Temporal Worker 能访问到的 GAdmin API 地址,并以 /api 结尾。"
234
485
  rules={[
235
- { required: true, message: '请输入 Runtime Base URL' },
236
- { type: 'url', message: '请输入完整的 HTTP 或 HTTPS 地址' },
486
+ { required: true, message: '请输入 Runtime API 地址' },
487
+ {
488
+ validator: async (_, value?: string) => {
489
+ if (!value) return;
490
+ try {
491
+ const url = new URL(value);
492
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
493
+ throw new Error();
494
+ }
495
+ } catch {
496
+ throw new Error('请输入完整的 HTTP 或 HTTPS 地址');
497
+ }
498
+ },
499
+ },
237
500
  ]}
238
- extra="填写 Temporal worker 能访问到的 Gadmin API 地址,并以 /api 结尾"
239
501
  >
240
502
  <Input placeholder="http://gadmin-server:8000/api" />
241
503
  </Form.Item>
242
504
  <Form.Item
243
505
  label="Runtime Token"
244
506
  name="runtimeToken"
507
+ extra="必须与本 GAdmin 的 WORKFLOW_RUNTIME_TOKEN 完全一致。"
245
508
  rules={[
246
509
  { required: true, message: '请输入 Runtime Token' },
247
510
  { min: 32, message: 'Runtime Token 至少需要 32 个字符' },
248
511
  ]}
249
- extra="必须与 Gadmin 的 WORKFLOW_RUNTIME_TOKEN 完全一致"
250
512
  >
251
513
  <Input.Password
252
- placeholder="与 WORKFLOW_RUNTIME_TOKEN 相同"
514
+ placeholder="WORKFLOW_RUNTIME_TOKEN"
253
515
  autoComplete="new-password"
254
516
  />
255
517
  </Form.Item>
256
-
257
- <Form.Item style={{ marginTop: 8 }}>
258
- <Button type="primary" htmlType="submit" loading={saving}>
259
- {existing ? '更新并同步到 Temporal' : '注册到 Temporal'}
260
- </Button>
261
- </Form.Item>
262
518
  </Form>
263
- </Card>
519
+ </Drawer>
264
520
  </div>
265
521
  );
266
522
  }