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.
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/client.js +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/914.5dc1105cf3ada6a6.js +10 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/externalVersion.js +6 -5
- package/dist/locale/en-US.json +138 -28
- package/dist/locale/vi-VN.json +139 -28
- package/dist/locale/zh-CN.json +140 -28
- package/dist/server/actions/cache-monitor.js +301 -0
- package/dist/server/actions/cluster-nodes.js +391 -11
- package/dist/server/actions/doctor.js +1246 -0
- package/dist/server/actions/orchestrator.js +37 -0
- package/dist/server/actions/queue-mappings.js +107 -0
- package/dist/server/collections/cluster-manager-doctor-runs.js +52 -0
- package/dist/server/collections/cluster-manager-doctor.js +44 -0
- package/dist/server/collections/worker-queue-mappings.js +106 -0
- package/dist/server/hooks/cacheInvalidationHooks.js +81 -0
- package/dist/server/middlewares/listMetaCacheMiddleware.js +79 -0
- package/dist/server/orchestrator/PackageManager.js +21 -24
- package/dist/server/orchestrator/docker-adapter.js +49 -27
- package/dist/server/plugin.js +71 -16
- package/dist/server/queue-scanner.js +141 -0
- package/dist/server/utils/node.js +30 -2
- package/dist/server/utils/versionManager.js +91 -0
- package/package.json +9 -5
- package/server.js +1 -0
- package/src/client/AclCacheManager.tsx +292 -287
- package/src/client/CacheMonitor.tsx +166 -179
- package/src/client/ClusterManagerLayout.tsx +54 -42
- package/src/client/ClusterNodes.tsx +698 -418
- package/src/client/ContainerOrchestrator.tsx +184 -102
- package/src/client/Doctor.tsx +559 -0
- package/src/client/NginxCacheManager.tsx +415 -0
- package/src/client/PluginOperations.tsx +234 -234
- package/src/client/QueueAssignment.tsx +355 -0
- package/src/client/TaskManager.tsx +194 -187
- package/src/client/WorkflowExecutions.tsx +243 -238
- package/src/client/index.tsx +22 -14
- package/src/client/utils/clientSafeCache.ts +41 -0
- package/src/client/utils/requestDedupInterceptor.ts +213 -0
- package/src/client-v2/plugin.tsx +24 -0
- package/src/locale/en-US.json +138 -28
- package/src/locale/vi-VN.json +139 -28
- package/src/locale/zh-CN.json +140 -28
- package/src/server/__tests__/doctor.test.ts +53 -0
- package/src/server/actions/acl-cache.ts +272 -272
- package/src/server/actions/cache-monitor.ts +453 -116
- package/src/server/actions/cluster-nodes.ts +878 -378
- package/src/server/actions/doctor.ts +1536 -0
- package/src/server/actions/orchestrator.ts +54 -2
- package/src/server/actions/queue-mappings.ts +94 -0
- package/src/server/collections/cluster-manager-doctor-runs.ts +23 -0
- package/src/server/collections/cluster-manager-doctor.ts +19 -0
- package/src/server/collections/worker-queue-mappings.ts +85 -0
- package/src/server/hooks/cacheInvalidationHooks.ts +58 -0
- package/src/server/middlewares/listMetaCacheMiddleware.ts +55 -0
- package/src/server/orchestrator/PackageManager.ts +20 -24
- package/src/server/orchestrator/docker-adapter.ts +74 -37
- package/src/server/plugin.ts +347 -270
- package/src/server/queue-scanner.ts +154 -0
- package/src/server/utils/node.ts +48 -0
- package/src/server/utils/versionManager.ts +69 -0
- package/dist/client/AclCacheManager.d.ts +0 -2
- package/dist/client/CacheMonitor.d.ts +0 -2
- package/dist/client/ClusterManagerLayout.d.ts +0 -2
- package/dist/client/ClusterNodes.d.ts +0 -2
- package/dist/client/ContainerOrchestrator.d.ts +0 -2
- package/dist/client/EventQueueMonitor.d.ts +0 -2
- package/dist/client/LockMonitor.d.ts +0 -2
- package/dist/client/PackageInstaller.d.ts +0 -2
- package/dist/client/PluginOperations.d.ts +0 -2
- package/dist/client/RedisMonitor.d.ts +0 -2
- package/dist/client/TaskManager.d.ts +0 -2
- package/dist/client/WorkflowExecutions.d.ts +0 -2
- package/dist/client/index.d.ts +0 -5
- package/dist/client/utils.d.ts +0 -12
- package/dist/index.d.ts +0 -2
- package/dist/server/actions/acl-cache.d.ts +0 -53
- package/dist/server/actions/cache-monitor.d.ts +0 -23
- package/dist/server/actions/cluster-nodes.d.ts +0 -49
- package/dist/server/actions/event-queue-monitor.d.ts +0 -13
- package/dist/server/actions/lock-monitor.d.ts +0 -19
- package/dist/server/actions/orchestrator.d.ts +0 -58
- package/dist/server/actions/package-manager.d.ts +0 -6
- package/dist/server/actions/plugin-operations.d.ts +0 -6
- package/dist/server/actions/redis-monitor.d.ts +0 -12
- package/dist/server/actions/tasks.d.ts +0 -7
- package/dist/server/actions/workflow-executions.d.ts +0 -7
- package/dist/server/adapters/redis-lock-adapter.d.ts +0 -15
- package/dist/server/adapters/redis-node-registry.d.ts +0 -12
- package/dist/server/adapters/redis-pubsub-adapter.d.ts +0 -16
- package/dist/server/collections/app.d.ts +0 -8
- package/dist/server/collections/cluster-manager-acl-cache.d.ts +0 -22
- package/dist/server/collections/cluster-manager-cache-mgr.d.ts +0 -22
- package/dist/server/collections/cluster-manager-cluster.d.ts +0 -22
- package/dist/server/collections/cluster-manager-lock.d.ts +0 -22
- package/dist/server/collections/cluster-manager-plugins.d.ts +0 -18
- package/dist/server/collections/cluster-manager-queue.d.ts +0 -22
- package/dist/server/collections/cluster-manager-redis.d.ts +0 -22
- package/dist/server/collections/cluster-manager-workflow.d.ts +0 -22
- package/dist/server/collections/cluster-manager.d.ts +0 -22
- package/dist/server/collections/orchestrator-settings.d.ts +0 -59
- package/dist/server/collections/orchestrator-stacks.d.ts +0 -102
- package/dist/server/collections/worker-orchestrator.d.ts +0 -22
- package/dist/server/collections/worker-packages-configs.d.ts +0 -3
- package/dist/server/collections/worker-packages.d.ts +0 -22
- package/dist/server/orchestrator/PackageManager.d.ts +0 -39
- package/dist/server/orchestrator/docker-adapter.d.ts +0 -41
- package/dist/server/orchestrator/index.d.ts +0 -4
- package/dist/server/orchestrator/k8s-adapter.d.ts +0 -50
- package/dist/server/orchestrator/leader-election.d.ts +0 -48
- package/dist/server/orchestrator/types.d.ts +0 -84
- package/dist/server/plugin.d.ts +0 -26
- package/dist/server/utils/node.d.ts +0 -6
- package/dist/server/utils/redis.d.ts +0 -29
- package/dist/shared/packages.d.ts +0 -23
- /package/{dist/server/index.d.ts → src/client-v2/index.tsx} +0 -0
|
@@ -35,9 +35,7 @@ function getDockerode() {
|
|
|
35
35
|
try {
|
|
36
36
|
Dockerode = require("dockerode");
|
|
37
37
|
} catch {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'[DockerAdapter] "dockerode" package not found. Install it: yarn add dockerode'
|
|
40
|
-
);
|
|
38
|
+
throw new Error('[DockerAdapter] "dockerode" package not found. Install it: yarn add dockerode');
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
return Dockerode;
|
|
@@ -71,11 +69,7 @@ class DockerAdapter {
|
|
|
71
69
|
const containers = await this.docker.listContainers({
|
|
72
70
|
all: true,
|
|
73
71
|
filters: {
|
|
74
|
-
label: [
|
|
75
|
-
`${LABEL_STACK}=${stack.name}`,
|
|
76
|
-
`${LABEL_MANAGED}=true`,
|
|
77
|
-
...this.buildLabelFilters(this.workerLabels)
|
|
78
|
-
]
|
|
72
|
+
label: [`${LABEL_STACK}=${stack.name}`, `${LABEL_MANAGED}=true`, ...this.buildLabelFilters(this.workerLabels)]
|
|
79
73
|
}
|
|
80
74
|
});
|
|
81
75
|
return containers.map((c) => ({
|
|
@@ -100,7 +94,7 @@ class DockerAdapter {
|
|
|
100
94
|
}
|
|
101
95
|
}
|
|
102
96
|
async scale(stack, replicas) {
|
|
103
|
-
var _a, _b, _c, _d;
|
|
97
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
104
98
|
const current = await this.listContainers(stack);
|
|
105
99
|
const running = current.filter((c) => c.status === "running");
|
|
106
100
|
const diff = replicas - running.length;
|
|
@@ -112,20 +106,27 @@ class DockerAdapter {
|
|
|
112
106
|
};
|
|
113
107
|
if (diff > 0) {
|
|
114
108
|
let targetNetworks = stack.networks && stack.networks.length > 0 ? stack.networks : [];
|
|
115
|
-
|
|
109
|
+
const targetNetworkMode = stack.networkMode;
|
|
116
110
|
let targetEnvVars = this.buildEnvArray(stack.envVars);
|
|
117
111
|
let targetVolumes = stack.volumes || [];
|
|
112
|
+
let targetImage = stack.image;
|
|
113
|
+
let inheritedCmd;
|
|
114
|
+
let inheritedEntrypoint;
|
|
118
115
|
try {
|
|
119
116
|
const os = require("os");
|
|
120
117
|
const myContainerId = os.hostname();
|
|
121
118
|
const myContainer = this.docker.getContainer(myContainerId);
|
|
122
119
|
const myInfo = await myContainer.inspect();
|
|
123
|
-
if ((_a = myInfo == null ? void 0 : myInfo.
|
|
120
|
+
if (!targetImage && ((_a = myInfo == null ? void 0 : myInfo.Config) == null ? void 0 : _a.Image)) {
|
|
121
|
+
targetImage = myInfo.Config.Image;
|
|
122
|
+
console.log("[DockerAdapter] Inherited image from app container:", targetImage);
|
|
123
|
+
}
|
|
124
|
+
if ((_b = myInfo == null ? void 0 : myInfo.NetworkSettings) == null ? void 0 : _b.Networks) {
|
|
124
125
|
const inheritedNetworks = Object.keys(myInfo.NetworkSettings.Networks);
|
|
125
126
|
targetNetworks = Array.from(/* @__PURE__ */ new Set([...inheritedNetworks, ...targetNetworks]));
|
|
126
127
|
console.log("[DockerAdapter] Inherited networks:", targetNetworks);
|
|
127
128
|
}
|
|
128
|
-
if ((
|
|
129
|
+
if ((_c = myInfo == null ? void 0 : myInfo.Config) == null ? void 0 : _c.Env) {
|
|
129
130
|
const envDict = {};
|
|
130
131
|
myInfo.Config.Env.forEach((e) => {
|
|
131
132
|
const idx = e.indexOf("=");
|
|
@@ -136,10 +137,16 @@ class DockerAdapter {
|
|
|
136
137
|
Object.assign(envDict, stack.envVars || {});
|
|
137
138
|
targetEnvVars = Object.entries(envDict).map(([k, v]) => `${k}=${v}`);
|
|
138
139
|
}
|
|
139
|
-
if ((
|
|
140
|
+
if ((_d = myInfo == null ? void 0 : myInfo.HostConfig) == null ? void 0 : _d.Binds) {
|
|
140
141
|
const inheritedBinds = myInfo.HostConfig.Binds;
|
|
141
142
|
targetVolumes = Array.from(/* @__PURE__ */ new Set([...inheritedBinds, ...targetVolumes]));
|
|
142
143
|
}
|
|
144
|
+
if (Array.isArray((_e = myInfo == null ? void 0 : myInfo.Config) == null ? void 0 : _e.Cmd) && myInfo.Config.Cmd.length > 0) {
|
|
145
|
+
inheritedCmd = myInfo.Config.Cmd;
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray((_f = myInfo == null ? void 0 : myInfo.Config) == null ? void 0 : _f.Entrypoint) && myInfo.Config.Entrypoint.length > 0) {
|
|
148
|
+
inheritedEntrypoint = myInfo.Config.Entrypoint;
|
|
149
|
+
}
|
|
143
150
|
} catch (e) {
|
|
144
151
|
console.error("[DockerAdapter] Failed to inherit container config:", e.message);
|
|
145
152
|
}
|
|
@@ -147,11 +154,16 @@ class DockerAdapter {
|
|
|
147
154
|
if (!hasLoggerBase) {
|
|
148
155
|
targetEnvVars.push(`LOGGER_BASE_PATH=/app/nocobase/storage/logs/${stack.name}`);
|
|
149
156
|
}
|
|
157
|
+
if (!targetImage) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`[DockerAdapter] No image configured for stack "${stack.name}" and the app container image could not be determined.`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
150
162
|
for (let i = 0; i < diff; i++) {
|
|
151
163
|
const suffix = `${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
|
|
152
164
|
const containerName = `${stack.name}-${suffix}`;
|
|
153
165
|
const createOpts = {
|
|
154
|
-
Image:
|
|
166
|
+
Image: targetImage,
|
|
155
167
|
name: containerName,
|
|
156
168
|
Env: targetEnvVars,
|
|
157
169
|
Labels: {
|
|
@@ -166,8 +178,15 @@ class DockerAdapter {
|
|
|
166
178
|
};
|
|
167
179
|
if (stack.command) {
|
|
168
180
|
createOpts.Cmd = ["/bin/sh", "-c", stack.command];
|
|
181
|
+
} else {
|
|
182
|
+
if (inheritedEntrypoint) {
|
|
183
|
+
createOpts.Entrypoint = inheritedEntrypoint;
|
|
184
|
+
}
|
|
185
|
+
if (inheritedCmd) {
|
|
186
|
+
createOpts.Cmd = inheritedCmd;
|
|
187
|
+
}
|
|
169
188
|
}
|
|
170
|
-
if ((
|
|
189
|
+
if ((_g = stack.resourceLimits) == null ? void 0 : _g.memory) {
|
|
171
190
|
createOpts.HostConfig.Memory = this.parseMemory(stack.resourceLimits.memory);
|
|
172
191
|
}
|
|
173
192
|
if (targetNetworkMode) {
|
|
@@ -184,7 +203,9 @@ class DockerAdapter {
|
|
|
184
203
|
const net = this.docker.getNetwork(targetNetworks[i2]);
|
|
185
204
|
await net.connect({ Container: container.id });
|
|
186
205
|
} catch (err) {
|
|
187
|
-
console.warn(
|
|
206
|
+
console.warn(
|
|
207
|
+
`[DockerAdapter] Failed to connect container ${container.id} to network ${targetNetworks[i2]}: ${err.message}`
|
|
208
|
+
);
|
|
188
209
|
}
|
|
189
210
|
}
|
|
190
211
|
}
|
|
@@ -192,9 +213,7 @@ class DockerAdapter {
|
|
|
192
213
|
result.containersCreated.push(container.id.substring(0, 12));
|
|
193
214
|
}
|
|
194
215
|
} else if (diff < 0) {
|
|
195
|
-
const sorted = running.sort(
|
|
196
|
-
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
197
|
-
);
|
|
216
|
+
const sorted = running.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
198
217
|
const toRemove = sorted.slice(0, Math.abs(diff));
|
|
199
218
|
for (const c of toRemove) {
|
|
200
219
|
try {
|
|
@@ -293,14 +312,17 @@ class DockerAdapter {
|
|
|
293
312
|
}
|
|
294
313
|
parseLabelSelector(selector) {
|
|
295
314
|
if (!(selector == null ? void 0 : selector.trim())) return {};
|
|
296
|
-
return selector.split(",").map((part) => part.trim()).filter(Boolean).reduce(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
315
|
+
return selector.split(",").map((part) => part.trim()).filter(Boolean).reduce(
|
|
316
|
+
(acc, part) => {
|
|
317
|
+
const [key, ...valueParts] = part.split("=");
|
|
318
|
+
const value = valueParts.join("=");
|
|
319
|
+
if ((key == null ? void 0 : key.trim()) && (value == null ? void 0 : value.trim())) {
|
|
320
|
+
acc[key.trim()] = value.trim();
|
|
321
|
+
}
|
|
322
|
+
return acc;
|
|
323
|
+
},
|
|
324
|
+
{}
|
|
325
|
+
);
|
|
304
326
|
}
|
|
305
327
|
labelsMatch(labels, expected) {
|
|
306
328
|
return Object.entries(expected).every(([k, v]) => labels[k] === v);
|
package/dist/server/plugin.js
CHANGED
|
@@ -58,11 +58,15 @@ var import_redis_node_registry = require("./adapters/redis-node-registry");
|
|
|
58
58
|
var import_redis_lock_adapter = require("./adapters/redis-lock-adapter");
|
|
59
59
|
var import_orchestrator = require("./actions/orchestrator");
|
|
60
60
|
var import_plugin_operations = require("./actions/plugin-operations");
|
|
61
|
+
var import_queue_mappings = require("./actions/queue-mappings");
|
|
61
62
|
var import_docker_adapter = require("./orchestrator/docker-adapter");
|
|
62
63
|
var import_k8s_adapter = require("./orchestrator/k8s-adapter");
|
|
63
64
|
var import_leader_election = require("./orchestrator/leader-election");
|
|
64
65
|
var import_package_manager = require("./actions/package-manager");
|
|
65
66
|
var import_PackageManager = require("./orchestrator/PackageManager");
|
|
67
|
+
var import_listMetaCacheMiddleware = require("./middlewares/listMetaCacheMiddleware");
|
|
68
|
+
var import_cacheInvalidationHooks = require("./hooks/cacheInvalidationHooks");
|
|
69
|
+
var import_doctor = require("./actions/doctor");
|
|
66
70
|
class PluginClusterManagerServer extends import_server.Plugin {
|
|
67
71
|
nodeRegistry;
|
|
68
72
|
orchestrator = null;
|
|
@@ -84,8 +88,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
84
88
|
this.app.on("afterStart", () => {
|
|
85
89
|
var _a;
|
|
86
90
|
(_a = this.nodeRegistry) == null ? void 0 : _a.start();
|
|
87
|
-
const
|
|
88
|
-
const isWorker = mode === "worker" || mode === "task" || mode === "*" || process.env.APP_ROLE === "worker" || process.env.APP_ROLE === "sandbox";
|
|
91
|
+
const isWorker = (0, import_node.isWorkerMode)(process.env.WORKER_MODE) || process.env.APP_ROLE === "worker" || process.env.APP_ROLE === "sandbox";
|
|
89
92
|
if (isWorker) {
|
|
90
93
|
setTimeout(async () => {
|
|
91
94
|
try {
|
|
@@ -107,7 +110,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
107
110
|
try {
|
|
108
111
|
const customRaw = config.get("customPackages");
|
|
109
112
|
if (customRaw) custom = typeof customRaw === "string" ? JSON.parse(customRaw) : customRaw;
|
|
110
|
-
} catch {
|
|
113
|
+
} catch (err) {
|
|
111
114
|
}
|
|
112
115
|
const unique = (arr) => Array.from(new Set(arr.filter(Boolean)));
|
|
113
116
|
const packages = {
|
|
@@ -138,9 +141,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
138
141
|
(_a = this.nodeRegistry) == null ? void 0 : _a.stop();
|
|
139
142
|
});
|
|
140
143
|
this.app.db.on("executions.afterSave", async (model) => {
|
|
141
|
-
|
|
142
|
-
const isWorker = mode === "worker" || mode === "task" || mode === "*";
|
|
143
|
-
if (isWorker) {
|
|
144
|
+
if ((0, import_node.isWorkerMode)(process.env.WORKER_MODE)) {
|
|
144
145
|
const id = model.get("id");
|
|
145
146
|
const redis = (0, import_redis.getRedisClient)(this.app);
|
|
146
147
|
if (id && redis) {
|
|
@@ -170,16 +171,51 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
170
171
|
if (!redis || !requestId) return;
|
|
171
172
|
const logData = await (0, import_cluster_nodes.readLocalLogs)(this.app, lines || 200);
|
|
172
173
|
const responseKey = `cluster-manager:log-response:${requestId}`;
|
|
174
|
+
await redis.sendCommand(["SET", responseKey, JSON.stringify(logData), "EX", "30"]);
|
|
175
|
+
this.app.logger.debug(`[ClusterManager] Served log request ${requestId} for ${targetNodeId}`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
this.app.logger.error(`[ClusterManager] Error handling log request: ${err.message}`);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
pubSub.subscribe(`cluster-manager:doctor-collect:${myNodeId}`, async (msg) => {
|
|
181
|
+
const redis = (0, import_redis.getRedisClient)(this.app);
|
|
182
|
+
let requestId = "";
|
|
183
|
+
try {
|
|
184
|
+
const parsed = typeof msg === "string" ? JSON.parse(msg) : msg;
|
|
185
|
+
requestId = parsed.requestId;
|
|
186
|
+
if (!redis || !requestId) return;
|
|
187
|
+
const snapshot = await (0, import_doctor.collectLocalDoctorSnapshot)(this.app, {
|
|
188
|
+
runId: parsed.runId,
|
|
189
|
+
sinceMs: parsed.sinceMs,
|
|
190
|
+
untilMs: parsed.untilMs,
|
|
191
|
+
maxLines: parsed.maxLines
|
|
192
|
+
});
|
|
173
193
|
await redis.sendCommand([
|
|
174
194
|
"SET",
|
|
175
|
-
|
|
176
|
-
JSON.stringify(
|
|
195
|
+
`cluster-manager:doctor-response:${requestId}`,
|
|
196
|
+
JSON.stringify(snapshot),
|
|
177
197
|
"EX",
|
|
178
|
-
"
|
|
198
|
+
"90"
|
|
179
199
|
]);
|
|
180
|
-
this.app.logger.debug(`[ClusterManager] Served
|
|
200
|
+
this.app.logger.debug(`[ClusterManager] Served doctor snapshot request ${requestId}`);
|
|
181
201
|
} catch (err) {
|
|
182
|
-
|
|
202
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
203
|
+
this.app.logger.error(`[ClusterManager] Error handling doctor snapshot request: ${message}`);
|
|
204
|
+
if (redis && requestId) {
|
|
205
|
+
const fallback = {
|
|
206
|
+
nodeId: (0, import_node.getLocalNodeId)(this.app),
|
|
207
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
208
|
+
error: message
|
|
209
|
+
};
|
|
210
|
+
await redis.sendCommand([
|
|
211
|
+
"SET",
|
|
212
|
+
`cluster-manager:doctor-response:${requestId}`,
|
|
213
|
+
JSON.stringify(fallback),
|
|
214
|
+
"EX",
|
|
215
|
+
"90"
|
|
216
|
+
]).catch(() => {
|
|
217
|
+
});
|
|
218
|
+
}
|
|
183
219
|
}
|
|
184
220
|
});
|
|
185
221
|
pubSub.subscribe("cluster-manager.install-packages", async (payload) => {
|
|
@@ -195,12 +231,16 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
195
231
|
try {
|
|
196
232
|
let target = msg;
|
|
197
233
|
let mode = "hard";
|
|
234
|
+
let targetNodeId = "";
|
|
198
235
|
if (msg.startsWith("{")) {
|
|
199
236
|
const parsed = JSON.parse(msg);
|
|
200
|
-
target = parsed.hostname;
|
|
237
|
+
target = parsed.hostname || parsed.target || "";
|
|
238
|
+
targetNodeId = parsed.targetNodeId || "";
|
|
201
239
|
mode = parsed.mode || "hard";
|
|
202
240
|
}
|
|
203
|
-
|
|
241
|
+
const myNodeId2 = (0, import_node.getLocalNodeId)(this.app);
|
|
242
|
+
const shouldRestart = targetNodeId ? targetNodeId === myNodeId2 : target === os.hostname() || target === "*";
|
|
243
|
+
if (shouldRestart) {
|
|
204
244
|
this.app.logger.warn(`[ClusterManager] Received ${mode} restart command for node ${os.hostname()}...`);
|
|
205
245
|
setTimeout(async () => {
|
|
206
246
|
try {
|
|
@@ -241,6 +281,10 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
241
281
|
name: "clusterManagerCluster",
|
|
242
282
|
actions: import_cluster_nodes.clusterActions
|
|
243
283
|
});
|
|
284
|
+
this.app.resourcer.define({
|
|
285
|
+
name: "clusterManagerDoctor",
|
|
286
|
+
actions: import_doctor.doctorActions
|
|
287
|
+
});
|
|
244
288
|
this.app.resourcer.define({
|
|
245
289
|
name: "clusterManagerQueue",
|
|
246
290
|
actions: import_event_queue_monitor.eventQueueActions
|
|
@@ -261,12 +305,22 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
261
305
|
name: "clusterManagerPlugins",
|
|
262
306
|
actions: import_plugin_operations.pluginOperationsActions
|
|
263
307
|
});
|
|
308
|
+
this.app.resourcer.define({
|
|
309
|
+
name: "workerQueueMappings",
|
|
310
|
+
actions: import_queue_mappings.queueMappingsActions
|
|
311
|
+
});
|
|
264
312
|
const aclCacheMiddleware = (0, import_acl_cache.createAclCacheMiddleware)(this.app);
|
|
265
313
|
this.app.acl.use(aclCacheMiddleware, {
|
|
266
314
|
tag: "aclCache",
|
|
267
315
|
before: "core",
|
|
268
316
|
after: "allow-manager"
|
|
269
317
|
});
|
|
318
|
+
const listMetaCacheMiddleware = (0, import_listMetaCacheMiddleware.createListMetaCacheMiddleware)(this.app);
|
|
319
|
+
this.app.resourcer.use(listMetaCacheMiddleware, {
|
|
320
|
+
tag: "listMetaCache",
|
|
321
|
+
after: "setCurrentRole"
|
|
322
|
+
});
|
|
323
|
+
(0, import_cacheInvalidationHooks.registerCacheHooks)(this.app);
|
|
270
324
|
this.app.use(async (ctx, next) => {
|
|
271
325
|
if (ctx.path === "/api/clusterManager:health" && (ctx.method === "GET" || ctx.method === "HEAD")) {
|
|
272
326
|
ctx.body = {
|
|
@@ -286,13 +340,15 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
286
340
|
"clusterManagerRedis:*",
|
|
287
341
|
"clusterManagerAclCache:*",
|
|
288
342
|
"clusterManagerCluster:*",
|
|
343
|
+
"clusterManagerDoctor:*",
|
|
289
344
|
"clusterManagerQueue:*",
|
|
290
345
|
"clusterManagerLock:*",
|
|
291
346
|
"clusterManagerCacheMgr:*",
|
|
292
347
|
"workerOrchestrator:*",
|
|
293
348
|
"orchestratorStacks:*",
|
|
294
349
|
"workerPackages:*",
|
|
295
|
-
"clusterManagerPlugins:*"
|
|
350
|
+
"clusterManagerPlugins:*",
|
|
351
|
+
"workerQueueMappings:*"
|
|
296
352
|
]
|
|
297
353
|
});
|
|
298
354
|
await this.initOrchestrator();
|
|
@@ -443,8 +499,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
443
499
|
}
|
|
444
500
|
}
|
|
445
501
|
isWorkerOnlyNode() {
|
|
446
|
-
|
|
447
|
-
return workerMode === "worker" || workerMode === "task" || workerMode === "*";
|
|
502
|
+
return (0, import_node.isWorkerMode)(process.env.WORKER_MODE);
|
|
448
503
|
}
|
|
449
504
|
}
|
|
450
505
|
var plugin_default = PluginClusterManagerServer;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var queue_scanner_exports = {};
|
|
28
|
+
__export(queue_scanner_exports, {
|
|
29
|
+
scanQueues: () => scanQueues
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(queue_scanner_exports);
|
|
32
|
+
var import_redis = require("./utils/redis");
|
|
33
|
+
const KNOWN_QUEUE_LABELS = {
|
|
34
|
+
"workflow:process": {
|
|
35
|
+
label: "Workflow",
|
|
36
|
+
description: "Process workflow executions (plugin-workflow)"
|
|
37
|
+
},
|
|
38
|
+
"async-task:process": {
|
|
39
|
+
label: "Async Tasks",
|
|
40
|
+
description: "Execute async tasks (plugin-async-task-manager)"
|
|
41
|
+
},
|
|
42
|
+
"knowledge-base:document-vectorize": {
|
|
43
|
+
label: "Document Vectorization",
|
|
44
|
+
description: "Vectorize knowledge base documents (plugin-knowledge-base)"
|
|
45
|
+
},
|
|
46
|
+
"git-review:process": {
|
|
47
|
+
label: "Git Review",
|
|
48
|
+
description: "AI code review jobs (plugin-git-manager)"
|
|
49
|
+
},
|
|
50
|
+
"build-guide:process": {
|
|
51
|
+
label: "Build Guide",
|
|
52
|
+
description: "Build user guide pages (plugin-build-guide-block)"
|
|
53
|
+
},
|
|
54
|
+
"build-ui-template:process": {
|
|
55
|
+
label: "Build UI Template",
|
|
56
|
+
description: "Build UI template pages (plugin-build-ui-template)"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const REDIS_QUEUE_PATTERNS = ["*:plugin-git-manager:review:queue", "*:plugin-build-guide-block:build:queue"];
|
|
60
|
+
function describeRedisQueueKey(key) {
|
|
61
|
+
const parts = String(key).split(":");
|
|
62
|
+
const plugin = parts[parts.length - 3] || "unknown";
|
|
63
|
+
const queue = parts[parts.length - 2] || key;
|
|
64
|
+
return {
|
|
65
|
+
label: `${queue} (${plugin})`,
|
|
66
|
+
description: `Redis List queue from ${plugin}`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function scanEventQueue(app) {
|
|
70
|
+
const eq = app.eventQueue;
|
|
71
|
+
if (!eq || !eq.events) return [];
|
|
72
|
+
const events = eq.events;
|
|
73
|
+
const items = [];
|
|
74
|
+
for (const [channel] of events.entries()) {
|
|
75
|
+
const known = KNOWN_QUEUE_LABELS[channel];
|
|
76
|
+
items.push({
|
|
77
|
+
name: channel,
|
|
78
|
+
label: (known == null ? void 0 : known.label) ?? channel,
|
|
79
|
+
description: (known == null ? void 0 : known.description) ?? `EventQueue channel: ${channel}`,
|
|
80
|
+
type: "event-queue",
|
|
81
|
+
pending: null
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
async function scanRedisQueues(app) {
|
|
87
|
+
var _a;
|
|
88
|
+
const redis = (0, import_redis.getRedisClient)(app);
|
|
89
|
+
if (!redis) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93
|
+
const items = [];
|
|
94
|
+
for (const pattern of REDIS_QUEUE_PATTERNS) {
|
|
95
|
+
try {
|
|
96
|
+
const keys = await redis.sendCommand(["SCAN", "0", "MATCH", pattern, "COUNT", "200"]);
|
|
97
|
+
const keyList = typeof ((_a = keys[1]) == null ? void 0 : _a.length) === "number" ? keys[1] : [];
|
|
98
|
+
for (const key of keyList) {
|
|
99
|
+
if (seen.has(key)) continue;
|
|
100
|
+
seen.add(key);
|
|
101
|
+
const desc = describeRedisQueueKey(key);
|
|
102
|
+
let pending = 0;
|
|
103
|
+
try {
|
|
104
|
+
pending = Number(await redis.sendCommand(["LLEN", key])) || 0;
|
|
105
|
+
} catch {
|
|
106
|
+
pending = 0;
|
|
107
|
+
}
|
|
108
|
+
items.push({
|
|
109
|
+
name: key,
|
|
110
|
+
label: desc.label,
|
|
111
|
+
description: desc.description,
|
|
112
|
+
type: "redis-list",
|
|
113
|
+
pending
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return items;
|
|
120
|
+
}
|
|
121
|
+
async function scanQueues(app) {
|
|
122
|
+
const eventQueues = scanEventQueue(app);
|
|
123
|
+
const redisQueues = await scanRedisQueues(app);
|
|
124
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
125
|
+
const merged = [];
|
|
126
|
+
for (const q of eventQueues) {
|
|
127
|
+
merged.push(q);
|
|
128
|
+
seenNames.add(q.name);
|
|
129
|
+
}
|
|
130
|
+
for (const q of redisQueues) {
|
|
131
|
+
if (!seenNames.has(q.name)) {
|
|
132
|
+
merged.push(q);
|
|
133
|
+
seenNames.add(q.name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { queues: merged, total: merged.length };
|
|
137
|
+
}
|
|
138
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
139
|
+
0 && (module.exports = {
|
|
140
|
+
scanQueues
|
|
141
|
+
});
|
|
@@ -36,10 +36,35 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var node_exports = {};
|
|
38
38
|
__export(node_exports, {
|
|
39
|
-
getLocalNodeId: () => getLocalNodeId
|
|
39
|
+
getLocalNodeId: () => getLocalNodeId,
|
|
40
|
+
getLocalRole: () => getLocalRole,
|
|
41
|
+
getNodeRoleFrom: () => getNodeRoleFrom,
|
|
42
|
+
isWorkerMode: () => isWorkerMode
|
|
40
43
|
});
|
|
41
44
|
module.exports = __toCommonJS(node_exports);
|
|
42
45
|
var import_os = __toESM(require("os"));
|
|
46
|
+
function isWorkerMode(workerMode) {
|
|
47
|
+
const mode = (workerMode ?? process.env.WORKER_MODE ?? "").trim();
|
|
48
|
+
if (!mode || mode === "main" || mode === "app") return false;
|
|
49
|
+
if (mode === "-") return false;
|
|
50
|
+
const topics = mode.split(",").map((t) => t.trim()).filter(Boolean);
|
|
51
|
+
if (topics.includes("!")) return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function getNodeRoleFrom(opts) {
|
|
55
|
+
if (opts.appRole === "app" || opts.appRole === "worker" || opts.appRole === "sandbox") {
|
|
56
|
+
return opts.appRole;
|
|
57
|
+
}
|
|
58
|
+
if (opts.isSandbox) return "sandbox";
|
|
59
|
+
return isWorkerMode(opts.workerMode) ? "worker" : "app";
|
|
60
|
+
}
|
|
61
|
+
function getLocalRole() {
|
|
62
|
+
return getNodeRoleFrom({
|
|
63
|
+
workerMode: process.env.WORKER_MODE,
|
|
64
|
+
appRole: process.env.APP_ROLE,
|
|
65
|
+
isSandbox: process.env.SKILL_HUB_SANDBOX === "true"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
43
68
|
function getLocalNodeId(app) {
|
|
44
69
|
const port = process.env.APP_PORT || "unknown";
|
|
45
70
|
const mode = process.env.WORKER_MODE || "main";
|
|
@@ -48,5 +73,8 @@ function getLocalNodeId(app) {
|
|
|
48
73
|
}
|
|
49
74
|
// Annotate the CommonJS export names for ESM import in node:
|
|
50
75
|
0 && (module.exports = {
|
|
51
|
-
getLocalNodeId
|
|
76
|
+
getLocalNodeId,
|
|
77
|
+
getLocalRole,
|
|
78
|
+
getNodeRoleFrom,
|
|
79
|
+
isWorkerMode
|
|
52
80
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var versionManager_exports = {};
|
|
28
|
+
__export(versionManager_exports, {
|
|
29
|
+
cacheVersionManager: () => cacheVersionManager
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(versionManager_exports);
|
|
32
|
+
var import_redis = require("./redis");
|
|
33
|
+
const localCounters = /* @__PURE__ */ new Map();
|
|
34
|
+
async function getVersion(app, key) {
|
|
35
|
+
const redis = (0, import_redis.getRedisClient)(app);
|
|
36
|
+
if (redis) {
|
|
37
|
+
try {
|
|
38
|
+
const val = await redis.sendCommand(["GET", key]);
|
|
39
|
+
return val ? parseInt(val, 10) : 1;
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return localCounters.get(key) || 1;
|
|
44
|
+
}
|
|
45
|
+
async function incrementVersion(app, key) {
|
|
46
|
+
const redis = (0, import_redis.getRedisClient)(app);
|
|
47
|
+
if (redis) {
|
|
48
|
+
try {
|
|
49
|
+
const val = await redis.sendCommand(["INCR", key]);
|
|
50
|
+
return parseInt(val, 10);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const next = (localCounters.get(key) || 1) + 1;
|
|
55
|
+
localCounters.set(key, next);
|
|
56
|
+
return next;
|
|
57
|
+
}
|
|
58
|
+
const cacheVersionManager = {
|
|
59
|
+
async getCollectionVersion(app) {
|
|
60
|
+
return getVersion(app, "nocobase:version:collections");
|
|
61
|
+
},
|
|
62
|
+
async incrementCollectionVersion(app) {
|
|
63
|
+
app.logger.info("[ClusterManager] Incrementing collections cache version due to schema change");
|
|
64
|
+
return incrementVersion(app, "nocobase:version:collections");
|
|
65
|
+
},
|
|
66
|
+
async getSchemaVersion(app) {
|
|
67
|
+
return getVersion(app, "nocobase:version:schemas");
|
|
68
|
+
},
|
|
69
|
+
async incrementSchemaVersion(app) {
|
|
70
|
+
app.logger.info("[ClusterManager] Incrementing uiSchemas cache version due to UI change");
|
|
71
|
+
return incrementVersion(app, "nocobase:version:schemas");
|
|
72
|
+
},
|
|
73
|
+
async getAclVersion(app, roleName) {
|
|
74
|
+
return getVersion(app, `nocobase:version:acl:role:${roleName}`);
|
|
75
|
+
},
|
|
76
|
+
async incrementAclVersion(app, roleName) {
|
|
77
|
+
app.logger.info(`[ClusterManager] Incrementing ACL cache version for role: ${roleName}`);
|
|
78
|
+
return incrementVersion(app, `nocobase:version:acl:role:${roleName}`);
|
|
79
|
+
},
|
|
80
|
+
async incrementAllAclVersions(app) {
|
|
81
|
+
app.logger.info("[ClusterManager] Incrementing global ACL cache version");
|
|
82
|
+
return incrementVersion(app, "nocobase:version:acl:global");
|
|
83
|
+
},
|
|
84
|
+
async getGlobalAclVersion(app) {
|
|
85
|
+
return getVersion(app, "nocobase:version:acl:global");
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
89
|
+
0 && (module.exports = {
|
|
90
|
+
cacheVersionManager
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"displayName": "Cluster Manager",
|
|
4
4
|
"displayName.zh-CN": "Cluster Manager",
|
|
5
5
|
"description": "Cluster node tracking, task orchestration, worker management, and package distribution",
|
|
6
|
-
"version": "1.1.
|
|
6
|
+
"version": "1.1.13",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
|
-
"main": "
|
|
8
|
+
"main": "dist/server/index.js",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"Monitoring",
|
|
11
11
|
"DevOps"
|
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
"client.js",
|
|
17
17
|
"server.js",
|
|
18
18
|
"client.d.ts",
|
|
19
|
-
"server.d.ts"
|
|
19
|
+
"server.d.ts",
|
|
20
|
+
"client-v2.js",
|
|
21
|
+
"client-v2.d.ts"
|
|
20
22
|
],
|
|
21
23
|
"peerDependencies": {
|
|
22
24
|
"@nocobase/client": "2.x",
|
|
23
25
|
"@nocobase/server": "2.x",
|
|
24
|
-
"@nocobase/database": "2.x"
|
|
26
|
+
"@nocobase/database": "2.x",
|
|
27
|
+
"@nocobase/client-v2": "2.x",
|
|
28
|
+
"@nocobase/flow-engine": "2.x"
|
|
25
29
|
},
|
|
26
30
|
"dependencies": {
|
|
27
31
|
"@nocobase/lock-manager": "2.x"
|
|
@@ -39,4 +43,4 @@
|
|
|
39
43
|
],
|
|
40
44
|
"editionLevel": 0
|
|
41
45
|
}
|
|
42
|
-
}
|
|
46
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/server/index.js');
|