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
@@ -1,187 +1,194 @@
1
- import React, { useEffect, useState, useCallback } from 'react';
2
- import { Table, Tag, Button, Progress, Space, Popconfirm, message, Select, Dropdown } from 'antd';
3
- import { ReloadOutlined, StopOutlined, RedoOutlined } from '@ant-design/icons';
4
- import { useAPIClient } from '@nocobase/client';
5
- import dayjs from 'dayjs';
6
-
7
- const STATUS_MAP: Record<number | string, { label: string; color: string }> = {
8
- null: { label: 'Pending', color: 'default' },
9
- 0: { label: 'Running', color: 'processing' },
10
- 1: { label: 'Succeeded', color: 'success' },
11
- '-1': { label: 'Failed', color: 'error' },
12
- '-2': { label: 'Canceled', color: 'warning' },
13
- };
14
-
15
- export function TaskManager() {
16
- const api = useAPIClient();
17
- const [data, setData] = useState<any[]>([]);
18
- const [loading, setLoading] = useState(false);
19
- const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 });
20
- const [statusFilter, setStatusFilter] = useState<string | undefined>(undefined);
21
-
22
- const fetchData = useCallback(
23
- async (page = pagination.current, pageSize = pagination.pageSize) => {
24
- setLoading(true);
25
- try {
26
- const res = await api.request({
27
- url: 'clusterManager:list',
28
- params: { page, pageSize, statusFilter: statusFilter === undefined ? '' : statusFilter },
29
- });
30
- const body = res.data;
31
- const rows = Array.isArray(body?.data?.data) ? body.data.data : Array.isArray(body?.data) ? body.data : Array.isArray(body) ? body : [];
32
- setData(rows);
33
- setPagination((prev) => ({
34
- ...prev,
35
- current: body.meta?.page || page,
36
- total: body.meta?.count || 0,
37
- }));
38
- } catch (err) {
39
- message.error('Failed to load tasks');
40
- } finally {
41
- setLoading(false);
42
- }
43
- },
44
- [api, pagination.current, pagination.pageSize, statusFilter],
45
- );
46
-
47
- useEffect(() => {
48
- fetchData();
49
- }, [statusFilter]);
50
-
51
- const handleCancel = async (id: string) => {
52
- await api.request({ url: `clusterManager:cancel`, params: { filterByTk: id } });
53
- message.success('Task canceled');
54
- fetchData();
55
- };
56
-
57
- const handleRetry = async (id: string) => {
58
- await api.request({ url: `clusterManager:retry`, params: { filterByTk: id } });
59
- message.success('Task re-queued');
60
- fetchData();
61
- };
62
-
63
- const handlePurge = async (days: number) => {
64
- try {
65
- const res = await api.request({ url: `clusterManager:purge`, method: 'post', data: { days } });
66
- message.success(`Purged ${res?.data?.deletedCount || 0} tasks`);
67
- fetchData();
68
- } catch {
69
- message.error('Failed to purge tasks');
70
- }
71
- };
72
-
73
- const purgeItems = [
74
- { key: '7', label: 'Older than 7 days', onClick: () => handlePurge(7) },
75
- { key: '30', label: 'Older than 30 days', onClick: () => handlePurge(30) },
76
- { key: '0', label: 'All completed/failed', danger: true, onClick: () => handlePurge(0) },
77
- ];
78
-
79
- const columns = [
80
- {
81
- title: 'Title',
82
- dataIndex: 'title',
83
- width: 200,
84
- ellipsis: true,
85
- },
86
- {
87
- title: 'Type',
88
- dataIndex: 'type',
89
- width: 120,
90
- },
91
- {
92
- title: 'Status',
93
- dataIndex: 'status',
94
- width: 100,
95
- render: (val: number | null) => {
96
- const s = STATUS_MAP[String(val)] || { label: String(val), color: 'default' };
97
- return <Tag color={s.color}>{s.label}</Tag>;
98
- },
99
- },
100
- {
101
- title: 'Progress',
102
- key: 'progress',
103
- width: 150,
104
- render: (_: any, record: any) => {
105
- if (record.status !== 0 || !record.progressTotal) return '-';
106
- const pct = Math.round((record.progressCurrent / record.progressTotal) * 100);
107
- return <Progress percent={pct} size="small" />;
108
- },
109
- },
110
- {
111
- title: 'User',
112
- key: 'user',
113
- width: 120,
114
- render: (_: any, record: any) => record.createdBy?.nickname || '-',
115
- },
116
- {
117
- title: 'Created',
118
- dataIndex: 'createdAt',
119
- width: 160,
120
- render: (val: string) => (val ? dayjs(val).format('YYYY-MM-DD HH:mm:ss') : '-'),
121
- },
122
- {
123
- title: 'Duration',
124
- key: 'duration',
125
- width: 100,
126
- render: (_: any, record: any) => {
127
- if (!record.startedAt) return '-';
128
- const end = record.doneAt || new Date().toISOString();
129
- const sec = dayjs(end).diff(dayjs(record.startedAt), 'second');
130
- return sec < 60 ? `${sec}s` : `${Math.floor(sec / 60)}m ${sec % 60}s`;
131
- },
132
- },
133
- {
134
- title: 'Actions',
135
- key: 'actions',
136
- width: 120,
137
- render: (_: any, record: any) => (
138
- <Space>
139
- {(record.status === 0 || record.status === null) && (
140
- <Popconfirm title="Cancel this task?" onConfirm={() => handleCancel(record.id)}>
141
- <Button type="link" size="small" icon={<StopOutlined />} danger />
142
- </Popconfirm>
143
- )}
144
- {(record.status === -1 || record.status === -2) && (
145
- <Popconfirm title="Retry this task?" onConfirm={() => handleRetry(record.id)}>
146
- <Button type="link" size="small" icon={<RedoOutlined />} />
147
- </Popconfirm>
148
- )}
149
- </Space>
150
- ),
151
- },
152
- ];
153
-
154
- return (
155
- <div>
156
- <Space style={{ marginBottom: 16 }}>
157
- <Select
158
- placeholder="Filter by status"
159
- allowClear
160
- style={{ width: 160 }}
161
- value={statusFilter}
162
- onChange={setStatusFilter}
163
- options={Object.entries(STATUS_MAP).map(([k, v]) => ({ value: k, label: v.label }))}
164
- />
165
- <Button icon={<ReloadOutlined />} onClick={() => fetchData()}>
166
- Refresh
167
- </Button>
168
- <Dropdown menu={{ items: purgeItems }} trigger={['click']}>
169
- <Button danger>Purge Tasks</Button>
170
- </Dropdown>
171
- </Space>
172
- <Table
173
- rowKey="id"
174
- columns={columns}
175
- dataSource={data}
176
- loading={loading}
177
- size="small"
178
- pagination={{
179
- ...pagination,
180
- showSizeChanger: true,
181
- showTotal: (total) => `Total ${total}`,
182
- onChange: (page, pageSize) => fetchData(page, pageSize),
183
- }}
184
- />
185
- </div>
186
- );
187
- }
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+ import { Table, Tag, Button, Progress, Space, Popconfirm, message, Select, Dropdown } from 'antd';
3
+ import { ReloadOutlined, StopOutlined, RedoOutlined } from '@ant-design/icons';
4
+ import { useAPIClient } from '@nocobase/client';
5
+ import dayjs from 'dayjs';
6
+
7
+ const STATUS_MAP: Record<number | string, { label: string; color: string }> = {
8
+ null: { label: 'Pending', color: 'default' },
9
+ 0: { label: 'Running', color: 'processing' },
10
+ 1: { label: 'Succeeded', color: 'success' },
11
+ '-1': { label: 'Failed', color: 'error' },
12
+ '-2': { label: 'Canceled', color: 'warning' },
13
+ };
14
+
15
+ export function TaskManager() {
16
+ const api = useAPIClient();
17
+ const [data, setData] = useState<any[]>([]);
18
+ const [loading, setLoading] = useState(false);
19
+ const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 });
20
+ const [statusFilter, setStatusFilter] = useState<string | undefined>(undefined);
21
+
22
+ const fetchData = useCallback(
23
+ async (page = pagination.current, pageSize = pagination.pageSize) => {
24
+ setLoading(true);
25
+ try {
26
+ const res = await api.request({
27
+ url: 'clusterManager:list',
28
+ params: { page, pageSize, statusFilter: statusFilter === undefined ? '' : statusFilter },
29
+ });
30
+ const body = res.data;
31
+ const rows = Array.isArray(body?.data?.data)
32
+ ? body.data.data
33
+ : Array.isArray(body?.data)
34
+ ? body.data
35
+ : Array.isArray(body)
36
+ ? body
37
+ : [];
38
+ setData(rows);
39
+ setPagination((prev) => ({
40
+ ...prev,
41
+ current: body.meta?.page || page,
42
+ total: body.meta?.count || 0,
43
+ }));
44
+ } catch (err) {
45
+ message.error('Failed to load tasks');
46
+ } finally {
47
+ setLoading(false);
48
+ }
49
+ },
50
+ [api, pagination.current, pagination.pageSize, statusFilter],
51
+ );
52
+
53
+ useEffect(() => {
54
+ fetchData();
55
+ }, [statusFilter]);
56
+
57
+ const handleCancel = async (id: string) => {
58
+ await api.request({ url: `clusterManager:cancel`, params: { filterByTk: id } });
59
+ message.success('Task canceled');
60
+ fetchData();
61
+ };
62
+
63
+ const handleRetry = async (id: string) => {
64
+ await api.request({ url: `clusterManager:retry`, params: { filterByTk: id } });
65
+ message.success('Task re-queued');
66
+ fetchData();
67
+ };
68
+
69
+ const handlePurge = async (days: number) => {
70
+ try {
71
+ const res = await api.request({ url: `clusterManager:purge`, method: 'post', data: { days } });
72
+ message.success(`Purged ${res?.data?.deletedCount || 0} tasks`);
73
+ fetchData();
74
+ } catch {
75
+ message.error('Failed to purge tasks');
76
+ }
77
+ };
78
+
79
+ const purgeItems = [
80
+ { key: '7', label: 'Older than 7 days', onClick: () => handlePurge(7) },
81
+ { key: '30', label: 'Older than 30 days', onClick: () => handlePurge(30) },
82
+ { key: '0', label: 'All completed/failed', danger: true, onClick: () => handlePurge(0) },
83
+ ];
84
+
85
+ const columns = [
86
+ {
87
+ title: 'Title',
88
+ dataIndex: 'title',
89
+ width: 200,
90
+ ellipsis: true,
91
+ },
92
+ {
93
+ title: 'Type',
94
+ dataIndex: 'type',
95
+ width: 120,
96
+ },
97
+ {
98
+ title: 'Status',
99
+ dataIndex: 'status',
100
+ width: 100,
101
+ render: (val: number | null) => {
102
+ const s = STATUS_MAP[String(val)] || { label: String(val), color: 'default' };
103
+ return <Tag color={s.color}>{s.label}</Tag>;
104
+ },
105
+ },
106
+ {
107
+ title: 'Progress',
108
+ key: 'progress',
109
+ width: 150,
110
+ render: (_: any, record: any) => {
111
+ if (record.status !== 0 || !record.progressTotal) return '-';
112
+ const pct = Math.round((record.progressCurrent / record.progressTotal) * 100);
113
+ return <Progress percent={pct} size="small" />;
114
+ },
115
+ },
116
+ {
117
+ title: 'User',
118
+ key: 'user',
119
+ width: 120,
120
+ render: (_: any, record: any) => record.createdBy?.nickname || '-',
121
+ },
122
+ {
123
+ title: 'Created',
124
+ dataIndex: 'createdAt',
125
+ width: 160,
126
+ render: (val: string) => (val ? dayjs(val).format('YYYY-MM-DD HH:mm:ss') : '-'),
127
+ },
128
+ {
129
+ title: 'Duration',
130
+ key: 'duration',
131
+ width: 100,
132
+ render: (_: any, record: any) => {
133
+ if (!record.startedAt) return '-';
134
+ const end = record.doneAt || new Date().toISOString();
135
+ const sec = dayjs(end).diff(dayjs(record.startedAt), 'second');
136
+ return sec < 60 ? `${sec}s` : `${Math.floor(sec / 60)}m ${sec % 60}s`;
137
+ },
138
+ },
139
+ {
140
+ title: 'Actions',
141
+ key: 'actions',
142
+ width: 120,
143
+ render: (_: any, record: any) => (
144
+ <Space>
145
+ {(record.status === 0 || record.status === null) && (
146
+ <Popconfirm title="Cancel this task?" onConfirm={() => handleCancel(record.id)}>
147
+ <Button type="link" size="small" icon={<StopOutlined />} danger />
148
+ </Popconfirm>
149
+ )}
150
+ {(record.status === -1 || record.status === -2) && (
151
+ <Popconfirm title="Retry this task?" onConfirm={() => handleRetry(record.id)}>
152
+ <Button type="link" size="small" icon={<RedoOutlined />} />
153
+ </Popconfirm>
154
+ )}
155
+ </Space>
156
+ ),
157
+ },
158
+ ];
159
+
160
+ return (
161
+ <div>
162
+ <Space style={{ marginBottom: 16 }}>
163
+ <Select
164
+ placeholder="Filter by status"
165
+ allowClear
166
+ style={{ width: 160 }}
167
+ value={statusFilter}
168
+ onChange={setStatusFilter}
169
+ options={Object.entries(STATUS_MAP).map(([k, v]) => ({ value: k, label: v.label }))}
170
+ />
171
+ <Button icon={<ReloadOutlined />} onClick={() => fetchData()}>
172
+ Refresh
173
+ </Button>
174
+ <Dropdown menu={{ items: purgeItems }} trigger={['click']}>
175
+ <Button danger>Purge Tasks</Button>
176
+ </Dropdown>
177
+ </Space>
178
+ <Table
179
+ rowKey="id"
180
+ columns={columns}
181
+ dataSource={data}
182
+ loading={loading}
183
+ size="small"
184
+ scroll={{ x: 'max-content' }}
185
+ pagination={{
186
+ ...pagination,
187
+ showSizeChanger: true,
188
+ showTotal: (total) => `Total ${total}`,
189
+ onChange: (page, pageSize) => fetchData(page, pageSize),
190
+ }}
191
+ />
192
+ </div>
193
+ );
194
+ }