plugin-cluster-manager 1.1.10 → 1.1.13

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 (119) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/client.js +1 -0
  4. package/dist/client/index.js +1 -1
  5. package/dist/client-v2/914.5dc1105cf3ada6a6.js +10 -0
  6. package/dist/client-v2/index.js +10 -0
  7. package/dist/externalVersion.js +6 -5
  8. package/dist/locale/en-US.json +138 -28
  9. package/dist/locale/vi-VN.json +139 -28
  10. package/dist/locale/zh-CN.json +140 -28
  11. package/dist/server/actions/cache-monitor.js +301 -0
  12. package/dist/server/actions/cluster-nodes.js +391 -11
  13. package/dist/server/actions/doctor.js +1246 -0
  14. package/dist/server/actions/orchestrator.js +37 -0
  15. package/dist/server/actions/queue-mappings.js +107 -0
  16. package/dist/server/collections/cluster-manager-doctor-runs.js +52 -0
  17. package/dist/server/collections/cluster-manager-doctor.js +44 -0
  18. package/dist/server/collections/worker-queue-mappings.js +106 -0
  19. package/dist/server/hooks/cacheInvalidationHooks.js +81 -0
  20. package/dist/server/middlewares/listMetaCacheMiddleware.js +79 -0
  21. package/dist/server/orchestrator/PackageManager.js +21 -24
  22. package/dist/server/orchestrator/docker-adapter.js +49 -27
  23. package/dist/server/plugin.js +71 -16
  24. package/dist/server/queue-scanner.js +141 -0
  25. package/dist/server/utils/node.js +30 -2
  26. package/dist/server/utils/versionManager.js +91 -0
  27. package/package.json +9 -5
  28. package/server.js +1 -0
  29. package/src/client/AclCacheManager.tsx +292 -287
  30. package/src/client/CacheMonitor.tsx +166 -179
  31. package/src/client/ClusterManagerLayout.tsx +54 -42
  32. package/src/client/ClusterNodes.tsx +698 -418
  33. package/src/client/ContainerOrchestrator.tsx +184 -102
  34. package/src/client/Doctor.tsx +559 -0
  35. package/src/client/NginxCacheManager.tsx +415 -0
  36. package/src/client/PluginOperations.tsx +234 -234
  37. package/src/client/QueueAssignment.tsx +355 -0
  38. package/src/client/TaskManager.tsx +194 -187
  39. package/src/client/WorkflowExecutions.tsx +243 -238
  40. package/src/client/index.tsx +22 -14
  41. package/src/client/utils/clientSafeCache.ts +41 -0
  42. package/src/client/utils/requestDedupInterceptor.ts +213 -0
  43. package/src/client-v2/plugin.tsx +24 -0
  44. package/src/locale/en-US.json +138 -28
  45. package/src/locale/vi-VN.json +139 -28
  46. package/src/locale/zh-CN.json +140 -28
  47. package/src/server/__tests__/doctor.test.ts +53 -0
  48. package/src/server/actions/acl-cache.ts +272 -272
  49. package/src/server/actions/cache-monitor.ts +453 -116
  50. package/src/server/actions/cluster-nodes.ts +878 -378
  51. package/src/server/actions/doctor.ts +1536 -0
  52. package/src/server/actions/orchestrator.ts +54 -2
  53. package/src/server/actions/queue-mappings.ts +94 -0
  54. package/src/server/collections/cluster-manager-doctor-runs.ts +23 -0
  55. package/src/server/collections/cluster-manager-doctor.ts +19 -0
  56. package/src/server/collections/worker-queue-mappings.ts +85 -0
  57. package/src/server/hooks/cacheInvalidationHooks.ts +58 -0
  58. package/src/server/middlewares/listMetaCacheMiddleware.ts +55 -0
  59. package/src/server/orchestrator/PackageManager.ts +20 -24
  60. package/src/server/orchestrator/docker-adapter.ts +74 -37
  61. package/src/server/plugin.ts +347 -270
  62. package/src/server/queue-scanner.ts +154 -0
  63. package/src/server/utils/node.ts +48 -0
  64. package/src/server/utils/versionManager.ts +69 -0
  65. package/dist/client/AclCacheManager.d.ts +0 -2
  66. package/dist/client/CacheMonitor.d.ts +0 -2
  67. package/dist/client/ClusterManagerLayout.d.ts +0 -2
  68. package/dist/client/ClusterNodes.d.ts +0 -2
  69. package/dist/client/ContainerOrchestrator.d.ts +0 -2
  70. package/dist/client/EventQueueMonitor.d.ts +0 -2
  71. package/dist/client/LockMonitor.d.ts +0 -2
  72. package/dist/client/PackageInstaller.d.ts +0 -2
  73. package/dist/client/PluginOperations.d.ts +0 -2
  74. package/dist/client/RedisMonitor.d.ts +0 -2
  75. package/dist/client/TaskManager.d.ts +0 -2
  76. package/dist/client/WorkflowExecutions.d.ts +0 -2
  77. package/dist/client/index.d.ts +0 -5
  78. package/dist/client/utils.d.ts +0 -12
  79. package/dist/index.d.ts +0 -2
  80. package/dist/server/actions/acl-cache.d.ts +0 -53
  81. package/dist/server/actions/cache-monitor.d.ts +0 -23
  82. package/dist/server/actions/cluster-nodes.d.ts +0 -49
  83. package/dist/server/actions/event-queue-monitor.d.ts +0 -13
  84. package/dist/server/actions/lock-monitor.d.ts +0 -19
  85. package/dist/server/actions/orchestrator.d.ts +0 -58
  86. package/dist/server/actions/package-manager.d.ts +0 -6
  87. package/dist/server/actions/plugin-operations.d.ts +0 -6
  88. package/dist/server/actions/redis-monitor.d.ts +0 -12
  89. package/dist/server/actions/tasks.d.ts +0 -7
  90. package/dist/server/actions/workflow-executions.d.ts +0 -7
  91. package/dist/server/adapters/redis-lock-adapter.d.ts +0 -15
  92. package/dist/server/adapters/redis-node-registry.d.ts +0 -12
  93. package/dist/server/adapters/redis-pubsub-adapter.d.ts +0 -16
  94. package/dist/server/collections/app.d.ts +0 -8
  95. package/dist/server/collections/cluster-manager-acl-cache.d.ts +0 -22
  96. package/dist/server/collections/cluster-manager-cache-mgr.d.ts +0 -22
  97. package/dist/server/collections/cluster-manager-cluster.d.ts +0 -22
  98. package/dist/server/collections/cluster-manager-lock.d.ts +0 -22
  99. package/dist/server/collections/cluster-manager-plugins.d.ts +0 -18
  100. package/dist/server/collections/cluster-manager-queue.d.ts +0 -22
  101. package/dist/server/collections/cluster-manager-redis.d.ts +0 -22
  102. package/dist/server/collections/cluster-manager-workflow.d.ts +0 -22
  103. package/dist/server/collections/cluster-manager.d.ts +0 -22
  104. package/dist/server/collections/orchestrator-settings.d.ts +0 -59
  105. package/dist/server/collections/orchestrator-stacks.d.ts +0 -102
  106. package/dist/server/collections/worker-orchestrator.d.ts +0 -22
  107. package/dist/server/collections/worker-packages-configs.d.ts +0 -3
  108. package/dist/server/collections/worker-packages.d.ts +0 -22
  109. package/dist/server/orchestrator/PackageManager.d.ts +0 -39
  110. package/dist/server/orchestrator/docker-adapter.d.ts +0 -41
  111. package/dist/server/orchestrator/index.d.ts +0 -4
  112. package/dist/server/orchestrator/k8s-adapter.d.ts +0 -50
  113. package/dist/server/orchestrator/leader-election.d.ts +0 -48
  114. package/dist/server/orchestrator/types.d.ts +0 -84
  115. package/dist/server/plugin.d.ts +0 -26
  116. package/dist/server/utils/node.d.ts +0 -6
  117. package/dist/server/utils/redis.d.ts +0 -29
  118. package/dist/shared/packages.d.ts +0 -23
  119. /package/{dist/server/index.d.ts → src/client-v2/index.tsx} +0 -0
@@ -0,0 +1,355 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ Card,
4
+ Table,
5
+ Tag,
6
+ Button,
7
+ Space,
8
+ Select,
9
+ message,
10
+ Modal,
11
+ Typography,
12
+ Alert,
13
+ Switch,
14
+ Popconfirm,
15
+ Row,
16
+ Col,
17
+ Tooltip,
18
+ } from 'antd';
19
+ import {
20
+ ReloadOutlined,
21
+ ScanOutlined,
22
+ BranchesOutlined,
23
+ CheckCircleOutlined,
24
+ CloseCircleOutlined,
25
+ MinusCircleOutlined,
26
+ } from '@ant-design/icons';
27
+ import { useAPIClient } from '@nocobase/client';
28
+ import { useT } from './utils';
29
+
30
+ const { Text } = Typography;
31
+
32
+ interface DiscoveredQueue {
33
+ name: string;
34
+ label: string;
35
+ description: string;
36
+ type: 'event-queue' | 'redis-list';
37
+ pending: number | null;
38
+ }
39
+
40
+ interface RegisteredMapping {
41
+ id: number;
42
+ queueName: string;
43
+ label: string;
44
+ stackId: number | null;
45
+ enabled: boolean;
46
+ type: string;
47
+ }
48
+
49
+ interface StackInfo {
50
+ id: number;
51
+ name: string;
52
+ adapter: string;
53
+ }
54
+
55
+ export function QueueAssignment() {
56
+ const t = useT();
57
+ const api = useAPIClient();
58
+ const [loading, setLoading] = useState(false);
59
+ const [scanning, setScanning] = useState(false);
60
+ const [discovered, setDiscovered] = useState<DiscoveredQueue[]>([]);
61
+ const [mappings, setMappings] = useState<RegisteredMapping[]>([]);
62
+ const [unmapped, setUnmapped] = useState<DiscoveredQueue[]>([]);
63
+ const [stacks, setStacks] = useState<StackInfo[]>([]);
64
+
65
+ const fetchStacks = useCallback(async () => {
66
+ try {
67
+ const res = await api.request({ url: '/orchestratorStacks:list', params: { pageSize: 50 } });
68
+ const data = res.data?.data || [];
69
+ setStacks(data);
70
+ } catch {
71
+ // Collection not available
72
+ }
73
+ }, [api]);
74
+
75
+ const fetchMappings = useCallback(async () => {
76
+ try {
77
+ const res = await api.request({ url: '/workerQueueMappings:list', params: { pageSize: 200 } });
78
+ const data = res.data?.data || [];
79
+ setMappings(data);
80
+ } catch {
81
+ // Collection may not exist yet
82
+ }
83
+ }, [api]);
84
+
85
+ const scanQueues = useCallback(async () => {
86
+ setScanning(true);
87
+ try {
88
+ const res = await api.request({ url: '/workerQueueMappings:scanQueues' });
89
+ const data = res.data?.data || res.data;
90
+ setDiscovered(data.discovered || []);
91
+ setMappings(data.registered || []);
92
+ setUnmapped(data.unmapped || []);
93
+ } catch (err: any) {
94
+ message.error(`Scan failed: ${err.message}`);
95
+ } finally {
96
+ setScanning(false);
97
+ }
98
+ }, [api]);
99
+
100
+ const handleAutoMap = async () => {
101
+ setLoading(true);
102
+ try {
103
+ const res = await api.request({
104
+ url: '/workerQueueMappings:autoMap',
105
+ method: 'POST',
106
+ data: {},
107
+ });
108
+ const { created } = res.data?.data || res.data;
109
+ message.success(`Auto-mapped ${created.length} queue(s)`);
110
+ await scanQueues();
111
+ } catch (err: any) {
112
+ message.error(`Auto-map failed: ${err.message}`);
113
+ } finally {
114
+ setLoading(false);
115
+ }
116
+ };
117
+
118
+ const handleUpdateMapping = async (id: number, values: Partial<RegisteredMapping>) => {
119
+ setLoading(true);
120
+ try {
121
+ await api.request({
122
+ url: '/workerQueueMappings:update',
123
+ method: 'PUT',
124
+ params: { filterByTk: id },
125
+ data: values,
126
+ });
127
+ await fetchMappings();
128
+ } catch (err: any) {
129
+ message.error(`Update failed: ${err.message}`);
130
+ } finally {
131
+ setLoading(false);
132
+ }
133
+ };
134
+
135
+ const handleCreateMapping = async (queue: DiscoveredQueue) => {
136
+ setLoading(true);
137
+ try {
138
+ await api.request({
139
+ url: '/workerQueueMappings:create',
140
+ method: 'POST',
141
+ data: {
142
+ queueName: queue.name,
143
+ label: queue.label,
144
+ description: queue.description,
145
+ type: queue.type,
146
+ stackId: null,
147
+ enabled: true,
148
+ },
149
+ });
150
+ message.success(`Mapping created for "${queue.name}"`);
151
+ await scanQueues();
152
+ } catch (err: any) {
153
+ message.error(`Create failed: ${err.message}`);
154
+ } finally {
155
+ setLoading(false);
156
+ }
157
+ };
158
+
159
+ const handleDeleteMapping = async (id: number) => {
160
+ setLoading(true);
161
+ try {
162
+ await api.request({
163
+ url: '/workerQueueMappings:destroy',
164
+ method: 'POST',
165
+ params: { filterByTk: id },
166
+ });
167
+ message.success('Mapping deleted');
168
+ await scanQueues();
169
+ } catch (err: any) {
170
+ message.error(`Delete failed: ${err.message}`);
171
+ } finally {
172
+ setLoading(false);
173
+ }
174
+ };
175
+
176
+ useEffect(() => {
177
+ scanQueues();
178
+ fetchStacks();
179
+ }, [scanQueues, fetchStacks]);
180
+
181
+ const stackOptions = stacks.map((s) => ({
182
+ value: s.id,
183
+ label: s.name,
184
+ }));
185
+
186
+ const mappingMap = new Map(mappings.map((m) => [m.queueName, m]));
187
+
188
+ const allRows = discovered.map((q) => {
189
+ const mapping = mappingMap.get(q.name);
190
+ return {
191
+ key: q.name,
192
+ ...q,
193
+ mappingId: mapping?.id ?? null,
194
+ registered: Boolean(mapping),
195
+ stackId: mapping?.stackId ?? null,
196
+ enabled: mapping?.enabled ?? true,
197
+ };
198
+ });
199
+
200
+ const columns = [
201
+ {
202
+ title: t('Queue Name'),
203
+ dataIndex: 'name',
204
+ render: (name: string, record: any) => (
205
+ <Space>
206
+ {record.type === 'event-queue' ? (
207
+ <BranchesOutlined style={{ color: '#1890ff' }} />
208
+ ) : (
209
+ <MinusCircleOutlined style={{ color: '#52c41a' }} />
210
+ )}
211
+ <Text code>{name}</Text>
212
+ </Space>
213
+ ),
214
+ },
215
+ {
216
+ title: t('Label'),
217
+ dataIndex: 'label',
218
+ render: (label: string) => <Text>{label}</Text>,
219
+ },
220
+ {
221
+ title: t('Type'),
222
+ dataIndex: 'type',
223
+ width: 110,
224
+ render: (type: string) => (
225
+ <Tag color={type === 'event-queue' ? 'blue' : 'green'}>
226
+ {type === 'event-queue' ? 'EventQueue' : 'Redis List'}
227
+ </Tag>
228
+ ),
229
+ },
230
+ {
231
+ title: t('Pending'),
232
+ dataIndex: 'pending',
233
+ width: 80,
234
+ render: (val: number | null) => (val !== null ? <Text>{val}</Text> : <Text type="secondary">-</Text>),
235
+ },
236
+ {
237
+ title: t('Assigned Stack'),
238
+ dataIndex: 'stackId',
239
+ width: 160,
240
+ render: (stackId: number | null, record: any) => {
241
+ if (!record.registered) {
242
+ return <Text type="secondary">—</Text>;
243
+ }
244
+ return (
245
+ <Select
246
+ style={{ width: 150 }}
247
+ size="small"
248
+ allowClear
249
+ placeholder={t('All (default)')}
250
+ value={stackId}
251
+ options={stackOptions}
252
+ onChange={(value) => handleUpdateMapping(record.mappingId, { stackId: value ?? null })}
253
+ />
254
+ );
255
+ },
256
+ },
257
+ {
258
+ title: t('Enabled'),
259
+ dataIndex: 'enabled',
260
+ width: 80,
261
+ render: (enabled: boolean, record: any) => {
262
+ if (!record.registered) {
263
+ return <Text type="secondary">—</Text>;
264
+ }
265
+ return (
266
+ <Switch
267
+ size="small"
268
+ checked={enabled}
269
+ onChange={(checked) => handleUpdateMapping(record.mappingId, { enabled: checked })}
270
+ />
271
+ );
272
+ },
273
+ },
274
+ {
275
+ title: t('Actions'),
276
+ key: 'actions',
277
+ width: 200,
278
+ render: (_: any, record: any) => {
279
+ if (!record.registered) {
280
+ return (
281
+ <Button
282
+ size="small"
283
+ type="link"
284
+ icon={<CheckCircleOutlined style={{ color: '#52c41a' }} />}
285
+ onClick={() => handleCreateMapping(record)}
286
+ >
287
+ {t('Register')}
288
+ </Button>
289
+ );
290
+ }
291
+ return (
292
+ <Popconfirm title={t('Delete this mapping?')} onConfirm={() => handleDeleteMapping(record.mappingId)}>
293
+ <Button size="small" type="link" danger>
294
+ {t('Unregister')}
295
+ </Button>
296
+ </Popconfirm>
297
+ );
298
+ },
299
+ },
300
+ ];
301
+
302
+ return (
303
+ <div>
304
+ <Card size="small" style={{ marginBottom: 16 }}>
305
+ <Row justify="space-between" align="middle">
306
+ <Col>
307
+ <Space>
308
+ <Text strong>{t('Queue Assignment')}</Text>
309
+ <Text type="secondary">{t('Map queues to worker stacks. Unassigned queues run on all workers.')}</Text>
310
+ </Space>
311
+ </Col>
312
+ <Col>
313
+ <Space>
314
+ <Button icon={<ScanOutlined />} onClick={scanQueues} loading={scanning}>
315
+ {t('Scan Queues')}
316
+ </Button>
317
+ {unmapped.length > 0 && (
318
+ <Button onClick={handleAutoMap} loading={loading}>
319
+ {t('Auto-map ({count})', { count: unmapped.length })}
320
+ </Button>
321
+ )}
322
+ <Button
323
+ icon={<ReloadOutlined />}
324
+ onClick={() => {
325
+ scanQueues();
326
+ fetchStacks();
327
+ }}
328
+ >
329
+ {t('Refresh')}
330
+ </Button>
331
+ </Space>
332
+ </Col>
333
+ </Row>
334
+ </Card>
335
+
336
+ {unmapped.length > 0 && (
337
+ <Alert
338
+ type="info"
339
+ showIcon
340
+ style={{ marginBottom: 16 }}
341
+ message={`${unmapped.length} queue(s) discovered but not yet registered. Click "Register" on each row or use "Auto-map" to create mappings.`}
342
+ />
343
+ )}
344
+
345
+ <Table
346
+ dataSource={allRows}
347
+ columns={columns}
348
+ rowKey="key"
349
+ size="small"
350
+ pagination={false}
351
+ locale={{ emptyText: t('No queues discovered. Click "Scan Queues" to detect registered queues.') }}
352
+ />
353
+ </div>
354
+ );
355
+ }