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
|
@@ -40,7 +40,6 @@ __export(cluster_nodes_exports, {
|
|
|
40
40
|
readLocalLogs: () => readLocalLogs
|
|
41
41
|
});
|
|
42
42
|
module.exports = __toCommonJS(cluster_nodes_exports);
|
|
43
|
-
var import_server = require("@nocobase/server");
|
|
44
43
|
var import_os = __toESM(require("os"));
|
|
45
44
|
var import_fs = require("fs");
|
|
46
45
|
var import_path = __toESM(require("path"));
|
|
@@ -48,11 +47,158 @@ var import_crypto = __toESM(require("crypto"));
|
|
|
48
47
|
var import_redis_node_registry = require("../adapters/redis-node-registry");
|
|
49
48
|
var import_redis = require("../utils/redis");
|
|
50
49
|
var import_node = require("../utils/node");
|
|
50
|
+
var import_packages = require("../../shared/packages");
|
|
51
51
|
const LOG_RESPONSE_KEY_PREFIX = "cluster-manager:log-response:";
|
|
52
|
-
const
|
|
52
|
+
const LEGACY_MULTI_APP_PLUGINS = ["multi-app-manager", "multi-app-share-collection"];
|
|
53
53
|
function sleep(ms) {
|
|
54
54
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
55
|
}
|
|
56
|
+
function normalizeList(value) {
|
|
57
|
+
if (!Array.isArray(value)) return [];
|
|
58
|
+
return Array.from(
|
|
59
|
+
new Set(
|
|
60
|
+
value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function normalizePackageMap(packages) {
|
|
65
|
+
return {
|
|
66
|
+
apt: normalizeList(packages == null ? void 0 : packages.apt),
|
|
67
|
+
npm: normalizeList(packages == null ? void 0 : packages.npm),
|
|
68
|
+
python: normalizeList(packages == null ? void 0 : packages.python)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function parseCustomPackages(value) {
|
|
72
|
+
if (!value) {
|
|
73
|
+
return { python: [], node: [], npm: [] };
|
|
74
|
+
}
|
|
75
|
+
let customValue = value;
|
|
76
|
+
if (typeof customValue === "string") {
|
|
77
|
+
try {
|
|
78
|
+
customValue = JSON.parse(customValue);
|
|
79
|
+
} catch {
|
|
80
|
+
return { python: [], node: [], npm: [] };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!customValue || typeof customValue !== "object" || Array.isArray(customValue)) {
|
|
84
|
+
return { python: [], node: [], npm: [] };
|
|
85
|
+
}
|
|
86
|
+
const custom = customValue;
|
|
87
|
+
return {
|
|
88
|
+
python: normalizeList(custom.python),
|
|
89
|
+
node: normalizeList(custom.node),
|
|
90
|
+
npm: normalizeList(custom.npm)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function parsePackageWhitelist(status) {
|
|
94
|
+
if (!(status == null ? void 0 : status.packageWhitelist)) {
|
|
95
|
+
return { apt: [], npm: [], python: [] };
|
|
96
|
+
}
|
|
97
|
+
let whitelistValue = status.packageWhitelist;
|
|
98
|
+
if (typeof whitelistValue === "string") {
|
|
99
|
+
try {
|
|
100
|
+
whitelistValue = JSON.parse(whitelistValue);
|
|
101
|
+
} catch {
|
|
102
|
+
return { apt: [], npm: [], python: [] };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!whitelistValue || typeof whitelistValue !== "object" || Array.isArray(whitelistValue)) {
|
|
106
|
+
return { apt: [], npm: [], python: [] };
|
|
107
|
+
}
|
|
108
|
+
const whitelist = whitelistValue;
|
|
109
|
+
const npmPackages = [
|
|
110
|
+
...Array.isArray(whitelist.npm) ? whitelist.npm : [],
|
|
111
|
+
...Array.isArray(whitelist.node) ? whitelist.node : []
|
|
112
|
+
];
|
|
113
|
+
return {
|
|
114
|
+
apt: normalizeList(whitelist.apt),
|
|
115
|
+
npm: normalizeList(npmPackages),
|
|
116
|
+
python: normalizeList(whitelist.python)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function diffPackages(expected, installed) {
|
|
120
|
+
return {
|
|
121
|
+
apt: expected.apt.filter((pkg) => !installed.apt.includes(pkg)),
|
|
122
|
+
npm: expected.npm.filter((pkg) => !installed.npm.includes(pkg)),
|
|
123
|
+
python: expected.python.filter((pkg) => !installed.python.includes(pkg))
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function hasMissingPackages(packages) {
|
|
127
|
+
return packages.apt.length > 0 || packages.npm.length > 0 || packages.python.length > 0;
|
|
128
|
+
}
|
|
129
|
+
function getErrorMessage(error) {
|
|
130
|
+
return error instanceof Error ? error.message : String(error);
|
|
131
|
+
}
|
|
132
|
+
function getNodeRole(node) {
|
|
133
|
+
return (0, import_node.getNodeRoleFrom)({ workerMode: node.workerMode, isSandbox: node.isSandbox });
|
|
134
|
+
}
|
|
135
|
+
function getReferenceVersion(nodes) {
|
|
136
|
+
var _a;
|
|
137
|
+
const appNode = nodes.find((node) => getNodeRole(node) === "app" && node.appVersion);
|
|
138
|
+
if (appNode == null ? void 0 : appNode.appVersion) {
|
|
139
|
+
return appNode.appVersion;
|
|
140
|
+
}
|
|
141
|
+
const counts = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const node of nodes) {
|
|
143
|
+
if (!node.appVersion) continue;
|
|
144
|
+
counts.set(node.appVersion, (counts.get(node.appVersion) || 0) + 1);
|
|
145
|
+
}
|
|
146
|
+
return ((_a = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]) == null ? void 0 : _a[0]) || null;
|
|
147
|
+
}
|
|
148
|
+
async function getClusterNodes(ctx) {
|
|
149
|
+
var _a, _b;
|
|
150
|
+
const plugin = (_b = (_a = ctx.app.pm) == null ? void 0 : _a.get) == null ? void 0 : _b.call(_a, "plugin-cluster-manager");
|
|
151
|
+
const registry = (plugin == null ? void 0 : plugin.nodeRegistry) ?? new import_redis_node_registry.RedisNodeRegistry(ctx.app);
|
|
152
|
+
return registry.getNodes();
|
|
153
|
+
}
|
|
154
|
+
async function getExpectedPackages(ctx) {
|
|
155
|
+
var _a;
|
|
156
|
+
const repo = ctx.db.getRepository("workerPackagesConfigs");
|
|
157
|
+
const config = await ((_a = repo == null ? void 0 : repo.findOne) == null ? void 0 : _a.call(repo));
|
|
158
|
+
if (!config) {
|
|
159
|
+
return normalizePackageMap((0, import_packages.packagesFromConfig)({}));
|
|
160
|
+
}
|
|
161
|
+
const configured = (0, import_packages.packagesFromConfig)({
|
|
162
|
+
aptPackages: config.get("aptPackages"),
|
|
163
|
+
pythonPackages: config.get("pythonPackages"),
|
|
164
|
+
npmPackages: config.get("npmPackages")
|
|
165
|
+
});
|
|
166
|
+
const custom = parseCustomPackages(config.get("customPackages"));
|
|
167
|
+
return normalizePackageMap({
|
|
168
|
+
apt: configured.apt,
|
|
169
|
+
npm: [...configured.npm || [], ...custom.node || [], ...custom.npm || []],
|
|
170
|
+
python: [...configured.python || [], ...custom.python || []]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async function readPackageStatus(ctx, node) {
|
|
174
|
+
const redis = (0, import_redis.getRedis)(ctx);
|
|
175
|
+
if (!redis) return null;
|
|
176
|
+
const keys = [
|
|
177
|
+
node.id ? `cluster-manager:pkg-status:${node.id}` : null,
|
|
178
|
+
node.hostname ? `orchestrator:pkg-status:${node.hostname}` : null,
|
|
179
|
+
node.name ? `orchestrator:pkg-status:${node.name}` : null
|
|
180
|
+
].filter(Boolean);
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
try {
|
|
183
|
+
const raw = await redis.sendCommand(["GET", key]);
|
|
184
|
+
if (raw && typeof raw === "string") {
|
|
185
|
+
return JSON.parse(raw);
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
async function getApplicationPluginRows(ctx) {
|
|
193
|
+
const repo = ctx.db.getRepository("applicationPlugins");
|
|
194
|
+
if (!repo) return [];
|
|
195
|
+
const rows = await repo.find({ sort: ["name"] });
|
|
196
|
+
return rows.map((row) => row.toJSON());
|
|
197
|
+
}
|
|
198
|
+
function getPayload(ctx) {
|
|
199
|
+
var _a, _b, _c;
|
|
200
|
+
return ctx.action.params.values || ((_b = (_a = ctx.request) == null ? void 0 : _a.body) == null ? void 0 : _b.values) || ((_c = ctx.request) == null ? void 0 : _c.body) || {};
|
|
201
|
+
}
|
|
56
202
|
async function readLocalLogs(app, maxLines) {
|
|
57
203
|
const logBasePath = process.env.LOGGER_BASE_PATH || import_path.default.resolve(process.cwd(), "storage", "logs");
|
|
58
204
|
const appName = process.env.APP_NAME || app.name || "main";
|
|
@@ -96,7 +242,7 @@ const clusterActions = {
|
|
|
96
242
|
async current(ctx, next) {
|
|
97
243
|
var _a, _b;
|
|
98
244
|
const currentMode = process.env.WORKER_MODE || "main";
|
|
99
|
-
const isApp =
|
|
245
|
+
const isApp = !(0, import_node.isWorkerMode)(process.env.WORKER_MODE);
|
|
100
246
|
if (isApp) {
|
|
101
247
|
const mem = process.memoryUsage();
|
|
102
248
|
ctx.body = {
|
|
@@ -129,9 +275,7 @@ const clusterActions = {
|
|
|
129
275
|
const plugin = (_b = (_a = ctx.app.pm) == null ? void 0 : _a.get) == null ? void 0 : _b.call(_a, "plugin-cluster-manager");
|
|
130
276
|
const registry = (plugin == null ? void 0 : plugin.nodeRegistry) ?? new import_redis_node_registry.RedisNodeRegistry(ctx.app);
|
|
131
277
|
const nodes = await registry.getNodes();
|
|
132
|
-
const appNode = nodes.find(
|
|
133
|
-
(n) => n.workerMode === "main" || n.workerMode === "" || n.workerMode === "app"
|
|
134
|
-
);
|
|
278
|
+
const appNode = nodes.find((n) => n.workerMode === "main" || n.workerMode === "" || n.workerMode === "app");
|
|
135
279
|
if (appNode == null ? void 0 : appNode.nodeDetails) {
|
|
136
280
|
ctx.body = appNode.nodeDetails;
|
|
137
281
|
} else {
|
|
@@ -173,12 +317,8 @@ const clusterActions = {
|
|
|
173
317
|
* Returns all known cluster environments/nodes (if discovery adapter supports it)
|
|
174
318
|
*/
|
|
175
319
|
async list(ctx, next) {
|
|
176
|
-
var _a, _b;
|
|
177
|
-
const supervisor = import_server.AppSupervisor.getInstance();
|
|
178
320
|
const environments = [];
|
|
179
|
-
const
|
|
180
|
-
const registry = (plugin == null ? void 0 : plugin.nodeRegistry) ?? new import_redis_node_registry.RedisNodeRegistry(ctx.app);
|
|
181
|
-
const nodes = await registry.getNodes();
|
|
321
|
+
const nodes = await getClusterNodes(ctx);
|
|
182
322
|
if (nodes && nodes.length > 0) {
|
|
183
323
|
for (const env of nodes) {
|
|
184
324
|
environments.push({
|
|
@@ -210,6 +350,168 @@ const clusterActions = {
|
|
|
210
350
|
ctx.body = { data: environments, meta: { count: environments.length } };
|
|
211
351
|
await next();
|
|
212
352
|
},
|
|
353
|
+
/**
|
|
354
|
+
* GET /clusterManagerCluster:drift
|
|
355
|
+
* Reports version/runtime/package drift across active cluster nodes.
|
|
356
|
+
*/
|
|
357
|
+
async drift(ctx, next) {
|
|
358
|
+
var _a, _b;
|
|
359
|
+
const nodes = await getClusterNodes(ctx);
|
|
360
|
+
const referenceVersion = getReferenceVersion(nodes);
|
|
361
|
+
const expectedPackages = await getExpectedPackages(ctx);
|
|
362
|
+
const versionDrifts = nodes.filter((node) => node.status !== "offline").filter((node) => referenceVersion && node.appVersion && node.appVersion !== referenceVersion).map((node) => ({
|
|
363
|
+
id: node.id,
|
|
364
|
+
name: node.name,
|
|
365
|
+
hostname: node.hostname,
|
|
366
|
+
role: getNodeRole(node),
|
|
367
|
+
expectedVersion: referenceVersion,
|
|
368
|
+
actualVersion: node.appVersion
|
|
369
|
+
}));
|
|
370
|
+
const runtimeReference = (_b = (_a = nodes.find((node) => getNodeRole(node) === "app")) == null ? void 0 : _a.nodeDetails) == null ? void 0 : _b.node;
|
|
371
|
+
const runtimeDrifts = runtimeReference ? nodes.filter((node) => node.status !== "offline").filter((node) => {
|
|
372
|
+
var _a2;
|
|
373
|
+
const runtime = (_a2 = node.nodeDetails) == null ? void 0 : _a2.node;
|
|
374
|
+
if (!runtime) return false;
|
|
375
|
+
return runtime.nodeVersion !== runtimeReference.nodeVersion || runtime.platform !== runtimeReference.platform || runtime.arch !== runtimeReference.arch;
|
|
376
|
+
}).map((node) => {
|
|
377
|
+
var _a2, _b2, _c, _d, _e, _f;
|
|
378
|
+
return {
|
|
379
|
+
id: node.id,
|
|
380
|
+
name: node.name,
|
|
381
|
+
hostname: node.hostname,
|
|
382
|
+
role: getNodeRole(node),
|
|
383
|
+
expected: {
|
|
384
|
+
nodeVersion: runtimeReference.nodeVersion,
|
|
385
|
+
platform: runtimeReference.platform,
|
|
386
|
+
arch: runtimeReference.arch
|
|
387
|
+
},
|
|
388
|
+
actual: {
|
|
389
|
+
nodeVersion: (_b2 = (_a2 = node.nodeDetails) == null ? void 0 : _a2.node) == null ? void 0 : _b2.nodeVersion,
|
|
390
|
+
platform: (_d = (_c = node.nodeDetails) == null ? void 0 : _c.node) == null ? void 0 : _d.platform,
|
|
391
|
+
arch: (_f = (_e = node.nodeDetails) == null ? void 0 : _e.node) == null ? void 0 : _f.arch
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}) : [];
|
|
395
|
+
const packageDrifts = [];
|
|
396
|
+
for (const node of nodes.filter((item) => item.status !== "offline" && getNodeRole(item) !== "app")) {
|
|
397
|
+
const status = await readPackageStatus(ctx, node);
|
|
398
|
+
const installedPackages = parsePackageWhitelist(status);
|
|
399
|
+
const missingPackages = diffPackages(expectedPackages, installedPackages);
|
|
400
|
+
const hasPackageStatus = Boolean(status);
|
|
401
|
+
const statusOk = (status == null ? void 0 : status.initStatus) === "succeeded";
|
|
402
|
+
if (!hasPackageStatus || !statusOk || hasMissingPackages(missingPackages)) {
|
|
403
|
+
packageDrifts.push({
|
|
404
|
+
id: node.id,
|
|
405
|
+
name: node.name,
|
|
406
|
+
hostname: node.hostname,
|
|
407
|
+
role: getNodeRole(node),
|
|
408
|
+
status: (status == null ? void 0 : status.initStatus) || "unknown",
|
|
409
|
+
lastInitAt: (status == null ? void 0 : status.lastInitAt) || null,
|
|
410
|
+
missingPackages,
|
|
411
|
+
installedPackages,
|
|
412
|
+
initProgressLog: (status == null ? void 0 : status.initProgressLog) || ""
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
ctx.body = {
|
|
417
|
+
healthy: versionDrifts.length === 0 && runtimeDrifts.length === 0 && packageDrifts.length === 0,
|
|
418
|
+
referenceVersion,
|
|
419
|
+
expectedPackages,
|
|
420
|
+
versionDrifts,
|
|
421
|
+
runtimeDrifts,
|
|
422
|
+
packageDrifts,
|
|
423
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
424
|
+
summary: {
|
|
425
|
+
nodes: nodes.length,
|
|
426
|
+
versionDrifts: versionDrifts.length,
|
|
427
|
+
runtimeDrifts: runtimeDrifts.length,
|
|
428
|
+
packageDrifts: packageDrifts.length
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
await next();
|
|
432
|
+
},
|
|
433
|
+
/**
|
|
434
|
+
* GET /clusterManagerCluster:legacyDiagnostics
|
|
435
|
+
* Detects deprecated legacy multi-app plugins and leftover application records.
|
|
436
|
+
*/
|
|
437
|
+
async legacyDiagnostics(ctx, next) {
|
|
438
|
+
var _a, _b;
|
|
439
|
+
const rows = await getApplicationPluginRows(ctx);
|
|
440
|
+
const plugins = LEGACY_MULTI_APP_PLUGINS.map((name) => {
|
|
441
|
+
var _a2, _b2, _c, _d;
|
|
442
|
+
const row = rows.find((item) => item.name === name || item.packageName === `@nocobase/plugin-${name}`);
|
|
443
|
+
const loaded = Boolean(
|
|
444
|
+
((_b2 = (_a2 = ctx.app.pm) == null ? void 0 : _a2.get) == null ? void 0 : _b2.call(_a2, name)) || ((_d = (_c = ctx.app.pm) == null ? void 0 : _c.get) == null ? void 0 : _d.call(_c, `@nocobase/plugin-${name}`))
|
|
445
|
+
);
|
|
446
|
+
return {
|
|
447
|
+
name,
|
|
448
|
+
packageName: `@nocobase/plugin-${name}`,
|
|
449
|
+
installed: Boolean(row),
|
|
450
|
+
enabled: Boolean(row == null ? void 0 : row.enabled),
|
|
451
|
+
loaded,
|
|
452
|
+
version: row == null ? void 0 : row.version
|
|
453
|
+
};
|
|
454
|
+
});
|
|
455
|
+
let legacyApplicationCount = 0;
|
|
456
|
+
if ((_b = (_a = ctx.db).hasCollection) == null ? void 0 : _b.call(_a, "applications")) {
|
|
457
|
+
try {
|
|
458
|
+
legacyApplicationCount = await ctx.db.getRepository("applications").count();
|
|
459
|
+
} catch {
|
|
460
|
+
legacyApplicationCount = 0;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const findings = [];
|
|
464
|
+
const manager = plugins.find((plugin) => plugin.name === "multi-app-manager");
|
|
465
|
+
const shareCollection = plugins.find((plugin) => plugin.name === "multi-app-share-collection");
|
|
466
|
+
const appSupervisor = rows.find(
|
|
467
|
+
(item) => item.name === "app-supervisor" || item.packageName === "@nocobase/plugin-app-supervisor"
|
|
468
|
+
);
|
|
469
|
+
if ((manager == null ? void 0 : manager.enabled) || (manager == null ? void 0 : manager.loaded)) {
|
|
470
|
+
findings.push({
|
|
471
|
+
level: "warning",
|
|
472
|
+
code: "legacy_multi_app_manager_active",
|
|
473
|
+
messageKey: "Deprecated multi-app manager is active. It runs apps in shared process memory and should not be used for production cluster isolation.",
|
|
474
|
+
message: "Deprecated multi-app manager is active. It runs apps in shared process memory and should not be used for production cluster isolation."
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if ((shareCollection == null ? void 0 : shareCollection.enabled) || (shareCollection == null ? void 0 : shareCollection.loaded)) {
|
|
478
|
+
findings.push({
|
|
479
|
+
level: "warning",
|
|
480
|
+
code: "legacy_share_collection_active",
|
|
481
|
+
messageKey: "Deprecated multi-app share collection is active. Avoid schema/table sharing for new cluster deployments.",
|
|
482
|
+
message: "Deprecated multi-app share collection is active. Avoid schema/table sharing for new cluster deployments."
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (legacyApplicationCount > 0) {
|
|
486
|
+
findings.push({
|
|
487
|
+
level: "warning",
|
|
488
|
+
code: "legacy_app_records_found",
|
|
489
|
+
messageKey: "{count} legacy application record(s) were found in the applications collection.",
|
|
490
|
+
messageArgs: { count: legacyApplicationCount },
|
|
491
|
+
message: `${legacyApplicationCount} legacy application record(s) were found in the applications collection.`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
if (!(appSupervisor == null ? void 0 : appSupervisor.enabled)) {
|
|
495
|
+
findings.push({
|
|
496
|
+
level: "info",
|
|
497
|
+
code: "app_supervisor_not_enabled",
|
|
498
|
+
messageKey: "App Supervisor is not enabled. Use it for new multi-application management instead of deprecated multi-app plugins.",
|
|
499
|
+
message: "App Supervisor is not enabled. Use it for new multi-application management instead of deprecated multi-app plugins."
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
ctx.body = {
|
|
503
|
+
healthy: findings.every((finding) => finding.level !== "warning"),
|
|
504
|
+
plugins,
|
|
505
|
+
appSupervisor: appSupervisor ? {
|
|
506
|
+
installed: true,
|
|
507
|
+
enabled: Boolean(appSupervisor.enabled),
|
|
508
|
+
version: appSupervisor.version
|
|
509
|
+
} : { installed: false, enabled: false },
|
|
510
|
+
legacyApplicationCount,
|
|
511
|
+
findings
|
|
512
|
+
};
|
|
513
|
+
await next();
|
|
514
|
+
},
|
|
213
515
|
/**
|
|
214
516
|
* GET /clusterManagerCluster:health
|
|
215
517
|
* Health check for all subsystems
|
|
@@ -290,6 +592,84 @@ const clusterActions = {
|
|
|
290
592
|
}
|
|
291
593
|
await next();
|
|
292
594
|
},
|
|
595
|
+
/**
|
|
596
|
+
* POST /clusterManagerCluster:rollingRestart
|
|
597
|
+
* Restarts online nodes one-by-one, optionally filtered by role.
|
|
598
|
+
*/
|
|
599
|
+
async rollingRestart(ctx, next) {
|
|
600
|
+
const payload = getPayload(ctx);
|
|
601
|
+
const mode = payload.mode === "soft" ? "soft" : "hard";
|
|
602
|
+
const role = payload.role || "worker";
|
|
603
|
+
const delayMs = Math.min(Math.max(Number(payload.delayMs) || 5e3, 1e3), 6e4);
|
|
604
|
+
const requestedNodeIds = Array.isArray(payload.nodeIds) ? payload.nodeIds.map(String) : [];
|
|
605
|
+
const pubSub = ctx.app.pubSubManager;
|
|
606
|
+
if (!pubSub) {
|
|
607
|
+
ctx.throw(500, "PubSub manager is not initialized. HA requires PUBSUB_ADAPTER_REDIS_URL to be set.");
|
|
608
|
+
}
|
|
609
|
+
const nodes = (await getClusterNodes(ctx)).filter((node) => {
|
|
610
|
+
if (node.status === "offline") return false;
|
|
611
|
+
if (requestedNodeIds.length > 0) return node.id && requestedNodeIds.includes(node.id);
|
|
612
|
+
if (role === "all") return true;
|
|
613
|
+
return getNodeRole(node) === role;
|
|
614
|
+
});
|
|
615
|
+
if (nodes.length === 0) {
|
|
616
|
+
ctx.throw(404, "No online nodes match the rolling restart target.");
|
|
617
|
+
}
|
|
618
|
+
const myNodeId = (0, import_node.getLocalNodeId)(ctx.app);
|
|
619
|
+
const sortedNodes = nodes.sort((a, b) => {
|
|
620
|
+
if (a.id === myNodeId) return 1;
|
|
621
|
+
if (b.id === myNodeId) return -1;
|
|
622
|
+
return String(a.name || a.id).localeCompare(String(b.name || b.id));
|
|
623
|
+
});
|
|
624
|
+
const restartId = import_crypto.default.randomBytes(8).toString("hex");
|
|
625
|
+
const startedAt = Date.now();
|
|
626
|
+
const logger = ctx.app.logger;
|
|
627
|
+
const published = sortedNodes.map((node, index) => ({
|
|
628
|
+
id: node.id,
|
|
629
|
+
name: node.name,
|
|
630
|
+
hostname: node.hostname,
|
|
631
|
+
role: getNodeRole(node),
|
|
632
|
+
mode,
|
|
633
|
+
order: index + 1,
|
|
634
|
+
scheduledDelayMs: index * delayMs,
|
|
635
|
+
scheduledAt: new Date(startedAt + index * delayMs).toISOString()
|
|
636
|
+
}));
|
|
637
|
+
sortedNodes.forEach((node, index) => {
|
|
638
|
+
setTimeout(() => {
|
|
639
|
+
try {
|
|
640
|
+
const publishResult = pubSub.publish(
|
|
641
|
+
"cluster-manager:restart",
|
|
642
|
+
JSON.stringify({
|
|
643
|
+
restartId,
|
|
644
|
+
targetNodeId: node.id,
|
|
645
|
+
hostname: node.hostname,
|
|
646
|
+
mode
|
|
647
|
+
})
|
|
648
|
+
);
|
|
649
|
+
Promise.resolve(publishResult).catch((error) => {
|
|
650
|
+
logger.error(
|
|
651
|
+
`[ClusterManager] Failed to publish rolling restart ${restartId} for ${node.id || node.hostname}: ${getErrorMessage(error)}`
|
|
652
|
+
);
|
|
653
|
+
});
|
|
654
|
+
} catch (error) {
|
|
655
|
+
logger.error(
|
|
656
|
+
`[ClusterManager] Failed to schedule rolling restart ${restartId} for ${node.id || node.hostname}: ${getErrorMessage(error)}`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}, index * delayMs);
|
|
660
|
+
});
|
|
661
|
+
ctx.body = {
|
|
662
|
+
success: true,
|
|
663
|
+
restartId,
|
|
664
|
+
mode,
|
|
665
|
+
role,
|
|
666
|
+
delayMs,
|
|
667
|
+
scheduled: true,
|
|
668
|
+
estimatedDurationMs: Math.max(0, (sortedNodes.length - 1) * delayMs),
|
|
669
|
+
published
|
|
670
|
+
};
|
|
671
|
+
await next();
|
|
672
|
+
},
|
|
293
673
|
/**
|
|
294
674
|
* GET /clusterManagerCluster:logs?targetNodeId=xxx&lines=200
|
|
295
675
|
*
|