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,415 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ Card,
4
+ Space,
5
+ Alert,
6
+ Radio,
7
+ Select,
8
+ Input,
9
+ Button,
10
+ Popconfirm,
11
+ message,
12
+ Form,
13
+ Tag,
14
+ Typography,
15
+ Row,
16
+ Col,
17
+ } from 'antd';
18
+ import {
19
+ ReloadOutlined,
20
+ DeleteOutlined,
21
+ CheckCircleOutlined,
22
+ CloseCircleOutlined,
23
+ SendOutlined,
24
+ } from '@ant-design/icons';
25
+ import { useAPIClient } from '@nocobase/client';
26
+ import { useT } from './utils';
27
+
28
+ const { TextArea } = Input;
29
+ const { Title, Text } = Typography;
30
+
31
+ export function NginxCacheManager() {
32
+ const t = useT();
33
+ const api = useAPIClient();
34
+ const [loading, setLoading] = useState(false);
35
+ const [clearing, setClearing] = useState(false);
36
+ const [status, setStatus] = useState<any>(null);
37
+
38
+ const [method, setMethod] = useState<'directory' | 'purgeRequest'>('directory');
39
+ const [selectedPath, setSelectedPath] = useState<string>('custom');
40
+ const [customPath, setCustomPath] = useState<string>('');
41
+ const [purgeUrl, setPurgeUrl] = useState<string>('');
42
+ const [httpMethod, setHttpMethod] = useState<string>('PURGE');
43
+ const [headersStr, setHeadersStr] = useState<string>('{\n "X-Purge": "1"\n}');
44
+ const [logs, setLogs] = useState<string>('');
45
+
46
+ const fetchNginxStatus = React.useCallback(async () => {
47
+ setLoading(true);
48
+ try {
49
+ const res = await api.request({ url: 'clusterManagerCacheMgr:nginxCacheStatus' });
50
+ setStatus(res?.data?.data);
51
+ } catch {
52
+ message.error(t('Failed to load Nginx status'));
53
+ } finally {
54
+ setLoading(false);
55
+ }
56
+ }, [api, t]);
57
+
58
+ useEffect(() => {
59
+ fetchNginxStatus();
60
+ }, [fetchNginxStatus]);
61
+
62
+ useEffect(() => {
63
+ if (status?.detectedPaths?.length > 0) {
64
+ setSelectedPath(status.detectedPaths[0]);
65
+ } else {
66
+ setSelectedPath('custom');
67
+ }
68
+ }, [status]);
69
+
70
+ const handleClearCache = async () => {
71
+ setClearing(true);
72
+ setLogs('');
73
+ try {
74
+ const values: any = { method };
75
+
76
+ if (method === 'directory') {
77
+ const targetDir = selectedPath === 'custom' ? customPath : selectedPath;
78
+ if (!targetDir) {
79
+ message.error(t('Nginx cache path is required'));
80
+ setClearing(false);
81
+ return;
82
+ }
83
+ values.directory = targetDir;
84
+ setLogs(`[INFO] Clearing physical cache files in: ${targetDir}\n`);
85
+ } else {
86
+ if (!purgeUrl) {
87
+ message.error(t('Please enter a valid Purge URL'));
88
+ setClearing(false);
89
+ return;
90
+ }
91
+ values.url = purgeUrl;
92
+ values.httpMethod = httpMethod;
93
+
94
+ let headers = {};
95
+ try {
96
+ if (headersStr.trim()) {
97
+ headers = JSON.parse(headersStr);
98
+ }
99
+ } catch {
100
+ message.error(t('Invalid headers JSON structure'));
101
+ setClearing(false);
102
+ return;
103
+ }
104
+ values.headers = headers;
105
+ setLogs(
106
+ `[INFO] Sending HTTP Purge request:\n[INFO] Method: ${httpMethod}\n[INFO] URL: ${purgeUrl}\n[INFO] Headers: ${JSON.stringify(
107
+ headers,
108
+ null,
109
+ 2,
110
+ )}\n`,
111
+ );
112
+ }
113
+
114
+ const res = await api.request({
115
+ url: 'clusterManagerCacheMgr:clearNginxCache',
116
+ method: 'post',
117
+ data: { values },
118
+ });
119
+
120
+ const data = res?.data?.data;
121
+ if (method === 'directory') {
122
+ const msg = t('Cache cleared successfully. Cleared {count} items.').replace(
123
+ '{count}',
124
+ String(data?.clearedCount || 0),
125
+ );
126
+ message.success(msg);
127
+ setLogs((prev) => prev + `[SUCCESS] ${msg}\n`);
128
+ } else {
129
+ const msg = t('HTTP Purge request completed. Status: {status}').replace(
130
+ '{status}',
131
+ String(data?.status || 200),
132
+ );
133
+ message.success(msg);
134
+ setLogs(
135
+ (prev) =>
136
+ prev +
137
+ `[SUCCESS] ${msg}\n[RESPONSE BODY]\n${
138
+ typeof data?.data === 'object' ? JSON.stringify(data.data, null, 2) : String(data?.data || '')
139
+ }\n`,
140
+ );
141
+ }
142
+ } catch (err: any) {
143
+ const errMsg = err?.response?.data?.message || err?.message || String(err);
144
+ message.error(errMsg);
145
+ setLogs((prev) => prev + `[ERROR] Failed: ${errMsg}\n`);
146
+ } finally {
147
+ setClearing(false);
148
+ }
149
+ };
150
+
151
+ const detectedPaths = status?.detectedPaths || [];
152
+ const nginxInstalled = status?.nginxInstalled || false;
153
+
154
+ return (
155
+ <Card bordered={false} loading={loading}>
156
+ <Space direction="vertical" style={{ width: '100%' }} size="large">
157
+ {/* Nginx Detection Status */}
158
+ <Alert
159
+ type={nginxInstalled ? 'success' : 'warning'}
160
+ showIcon
161
+ icon={nginxInstalled ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
162
+ message={<span style={{ fontWeight: 600 }}>{t('Nginx Cache Status')}</span>}
163
+ description={
164
+ <div style={{ marginTop: 8 }}>
165
+ <div>
166
+ <span style={{ fontWeight: 500 }}>{t('Status')}:</span>{' '}
167
+ {nginxInstalled ? (
168
+ <Tag color="success">{t('Nginx is installed')}</Tag>
169
+ ) : (
170
+ <Tag color="warning">{t('Nginx is NOT installed')}</Tag>
171
+ )}
172
+ </div>
173
+ {status?.mainConfigPath && (
174
+ <div style={{ marginTop: 4 }}>
175
+ <span style={{ fontWeight: 500 }}>{t('Nginx configuration file found at')}:</span>{' '}
176
+ <code style={{ fontSize: 12 }}>{status.mainConfigPath}</code>
177
+ </div>
178
+ )}
179
+ {detectedPaths.length > 0 && (
180
+ <div style={{ marginTop: 4 }}>
181
+ <span style={{ fontWeight: 500 }}>{t('Nginx cache paths detected')}:</span>
182
+ <div style={{ marginTop: 4, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
183
+ {detectedPaths.map((p: string) => (
184
+ <Tag key={p} color="blue" style={{ fontSize: 11 }}>
185
+ {p}
186
+ </Tag>
187
+ ))}
188
+ </div>
189
+ </div>
190
+ )}
191
+ {!nginxInstalled && (
192
+ <div style={{ marginTop: 4, fontStyle: 'italic', opacity: 0.8 }}>
193
+ {t('Nginx is not detected on this node. You can still input a custom cache directory.')}
194
+ </div>
195
+ )}
196
+ </div>
197
+ }
198
+ action={
199
+ <Button size="small" icon={<ReloadOutlined />} onClick={fetchNginxStatus}>
200
+ {t('Refresh')}
201
+ </Button>
202
+ }
203
+ />
204
+
205
+ {/* Method selection */}
206
+ <div>
207
+ <Title level={5} style={{ fontSize: 14 }}>
208
+ {t('Nginx cache clearing method')}
209
+ </Title>
210
+ <Radio.Group
211
+ value={method}
212
+ onChange={(e) => setMethod(e.target.value)}
213
+ style={{ width: '100%', marginTop: 8 }}
214
+ >
215
+ <Row gutter={16}>
216
+ <Col span={12}>
217
+ <Radio.Button
218
+ value="directory"
219
+ style={{
220
+ width: '100%',
221
+ height: 'auto',
222
+ padding: '16px 24px',
223
+ display: 'flex',
224
+ flexDirection: 'column',
225
+ borderRadius: 8,
226
+ textAlign: 'left',
227
+ boxShadow: 'none',
228
+ lineHeight: 'normal',
229
+ border: method === 'directory' ? '2px solid #1677ff' : '1px solid #d9d9d9',
230
+ }}
231
+ >
232
+ <Space align="start" size="middle">
233
+ <DeleteOutlined
234
+ style={{ fontSize: 24, color: method === 'directory' ? '#1677ff' : 'inherit', marginTop: 4 }}
235
+ />
236
+ <div>
237
+ <div
238
+ style={{ fontWeight: 600, fontSize: 15, color: method === 'directory' ? '#1677ff' : 'inherit' }}
239
+ >
240
+ {t('Physical Files')}
241
+ </div>
242
+ <div style={{ fontSize: 12, opacity: 0.65, marginTop: 4 }}>
243
+ {t('Directly deletes all cache files inside the nginx cache directory on this server node.')}
244
+ </div>
245
+ </div>
246
+ </Space>
247
+ </Radio.Button>
248
+ </Col>
249
+ <Col span={12}>
250
+ <Radio.Button
251
+ value="purgeRequest"
252
+ style={{
253
+ width: '100%',
254
+ height: 'auto',
255
+ padding: '16px 24px',
256
+ display: 'flex',
257
+ flexDirection: 'column',
258
+ borderRadius: 8,
259
+ textAlign: 'left',
260
+ boxShadow: 'none',
261
+ lineHeight: 'normal',
262
+ border: method === 'purgeRequest' ? '2px solid #1677ff' : '1px solid #d9d9d9',
263
+ }}
264
+ >
265
+ <Space align="start" size="middle">
266
+ <SendOutlined
267
+ style={{ fontSize: 24, color: method === 'purgeRequest' ? '#1677ff' : 'inherit', marginTop: 4 }}
268
+ />
269
+ <div>
270
+ <div
271
+ style={{
272
+ fontWeight: 600,
273
+ fontSize: 15,
274
+ color: method === 'purgeRequest' ? '#1677ff' : 'inherit',
275
+ }}
276
+ >
277
+ {t('HTTP Purge Request')}
278
+ </div>
279
+ <div style={{ fontSize: 12, opacity: 0.65, marginTop: 4 }}>
280
+ {t(
281
+ 'Sends a custom HTTP request (like PURGE or bypass headers) to let Nginx clear specific URLs.',
282
+ )}
283
+ </div>
284
+ </div>
285
+ </Space>
286
+ </Radio.Button>
287
+ </Col>
288
+ </Row>
289
+ </Radio.Group>
290
+ </div>
291
+
292
+ {/* Input fields based on method */}
293
+ <Card bordered size="small" style={{ backgroundColor: '#fafafa' }}>
294
+ {method === 'directory' ? (
295
+ <Form layout="vertical">
296
+ <Form.Item label={<span style={{ fontWeight: 500 }}>{t('Select Cache Path')}</span>} required>
297
+ <Select
298
+ value={selectedPath}
299
+ onChange={(val) => setSelectedPath(val)}
300
+ style={{ width: '100%' }}
301
+ options={[
302
+ ...detectedPaths.map((p: string) => ({ label: p, value: p })),
303
+ { label: t('Custom Path'), value: 'custom' },
304
+ ]}
305
+ />
306
+ </Form.Item>
307
+
308
+ {selectedPath === 'custom' && (
309
+ <Form.Item label={<span style={{ fontWeight: 500 }}>{t('Custom Cache Path')}</span>} required>
310
+ <Input
311
+ placeholder="/var/cache/nginx"
312
+ value={customPath}
313
+ onChange={(e) => setCustomPath(e.target.value)}
314
+ />
315
+ <Text type="secondary" style={{ fontSize: 11, display: 'block', marginTop: 4 }}>
316
+ {t(
317
+ 'Provide the full absolute path of the directory. Restricted system paths will be rejected for safety.',
318
+ )}
319
+ </Text>
320
+ </Form.Item>
321
+ )}
322
+ </Form>
323
+ ) : (
324
+ <Form layout="vertical">
325
+ <Row gutter={16}>
326
+ <Col span={18}>
327
+ <Form.Item label={<span style={{ fontWeight: 500 }}>{t('Purge URL')}</span>} required>
328
+ <Input
329
+ placeholder="http://127.0.0.1/purge/*"
330
+ value={purgeUrl}
331
+ onChange={(e) => setPurgeUrl(e.target.value)}
332
+ />
333
+ </Form.Item>
334
+ </Col>
335
+ <Col span={6}>
336
+ <Form.Item label={<span style={{ fontWeight: 500 }}>{t('HTTP Method')}</span>} required>
337
+ <Select
338
+ value={httpMethod}
339
+ onChange={(val) => setHttpMethod(val)}
340
+ options={[
341
+ { label: 'PURGE', value: 'PURGE' },
342
+ { label: 'GET', value: 'GET' },
343
+ { label: 'POST', value: 'POST' },
344
+ { label: 'DELETE', value: 'DELETE' },
345
+ ]}
346
+ />
347
+ </Form.Item>
348
+ </Col>
349
+ </Row>
350
+ <Form.Item label={<span style={{ fontWeight: 500 }}>{t('Headers (JSON)')}</span>}>
351
+ <TextArea
352
+ rows={4}
353
+ value={headersStr}
354
+ onChange={(e) => setHeadersStr(e.target.value)}
355
+ placeholder={'{\n "X-Purge": "1"\n}'}
356
+ style={{ fontFamily: 'monospace', fontSize: 12 }}
357
+ />
358
+ </Form.Item>
359
+ </Form>
360
+ )}
361
+ </Card>
362
+
363
+ {/* Big Action Button */}
364
+ <div>
365
+ <Popconfirm
366
+ title={t('Clear Nginx Cache?')}
367
+ description={
368
+ method === 'directory'
369
+ ? t(
370
+ 'Are you sure you want to clear Nginx cache? This will permanently delete all files in this directory.',
371
+ )
372
+ : t('Are you sure you want to trigger this HTTP Purge request?')
373
+ }
374
+ onConfirm={handleClearCache}
375
+ okButtonProps={{ loading: clearing }}
376
+ >
377
+ <Button
378
+ type="primary"
379
+ danger={method === 'directory'}
380
+ size="large"
381
+ icon={method === 'directory' ? <DeleteOutlined /> : <SendOutlined />}
382
+ loading={clearing}
383
+ >
384
+ {method === 'directory' ? t('Clear Nginx Cache') : t('Send Purge Request')}
385
+ </Button>
386
+ </Popconfirm>
387
+ </div>
388
+
389
+ {/* Execution Logs */}
390
+ {logs && (
391
+ <div>
392
+ <Title level={5} style={{ fontSize: 13, marginBottom: 8 }}>
393
+ {t('Execution Logs')}
394
+ </Title>
395
+ <pre
396
+ style={{
397
+ backgroundColor: '#1e1e1e',
398
+ color: '#d4d4d4',
399
+ padding: '12px 16px',
400
+ borderRadius: 6,
401
+ fontFamily: 'monospace',
402
+ fontSize: 12,
403
+ overflowX: 'auto',
404
+ whiteSpace: 'pre-wrap',
405
+ maxHeight: '300px',
406
+ }}
407
+ >
408
+ {logs}
409
+ </pre>
410
+ </div>
411
+ )}
412
+ </Space>
413
+ </Card>
414
+ );
415
+ }