plugin-cluster-manager 1.1.7 → 1.1.11
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.js +1 -0
- package/dist/client/AclCacheManager.d.ts +2 -0
- package/dist/client/CacheMonitor.d.ts +2 -0
- package/dist/client/ClusterManagerLayout.d.ts +2 -0
- package/dist/client/ClusterNodes.d.ts +2 -0
- package/dist/client/ContainerOrchestrator.d.ts +2 -0
- package/dist/client/Doctor.d.ts +2 -0
- package/dist/client/EventQueueMonitor.d.ts +2 -0
- package/dist/client/LockMonitor.d.ts +2 -0
- package/dist/client/NginxCacheManager.d.ts +2 -0
- package/dist/client/PackageInstaller.d.ts +2 -0
- package/dist/client/PluginOperations.d.ts +2 -0
- package/dist/client/RedisMonitor.d.ts +2 -0
- package/dist/client/TaskManager.d.ts +2 -0
- package/dist/client/WorkflowExecutions.d.ts +2 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +1 -1
- package/dist/client/utils/clientSafeCache.d.ts +3 -0
- package/dist/client/utils/requestDedupInterceptor.d.ts +2 -0
- package/dist/client/utils.d.ts +12 -0
- package/dist/externalVersion.js +5 -5
- package/dist/index.d.ts +2 -0
- package/dist/locale/en-US.json +97 -1
- package/dist/locale/vi-VN.json +98 -1
- package/dist/locale/zh-CN.json +98 -1
- package/dist/server/actions/acl-cache.d.ts +53 -0
- package/dist/server/actions/acl-cache.js +1 -1
- package/dist/server/actions/cache-monitor.d.ts +33 -0
- package/dist/server/actions/cache-monitor.js +301 -0
- package/dist/server/actions/cluster-nodes.d.ts +64 -0
- package/dist/server/actions/cluster-nodes.js +394 -10
- package/dist/server/actions/doctor.d.ts +82 -0
- package/dist/server/actions/doctor.js +1250 -0
- package/dist/server/actions/event-queue-monitor.d.ts +13 -0
- package/dist/server/actions/lock-monitor.d.ts +19 -0
- package/dist/server/actions/orchestrator.d.ts +58 -0
- package/dist/server/actions/package-manager.d.ts +6 -0
- package/dist/server/actions/plugin-operations.d.ts +6 -0
- package/dist/server/actions/redis-monitor.d.ts +12 -0
- package/dist/server/actions/tasks.d.ts +7 -0
- package/dist/server/actions/workflow-executions.d.ts +7 -0
- package/dist/server/adapters/redis-lock-adapter.d.ts +15 -0
- package/dist/server/adapters/redis-node-registry.d.ts +12 -0
- package/dist/server/adapters/redis-pubsub-adapter.d.ts +16 -0
- package/dist/server/collections/app.d.ts +8 -0
- package/dist/server/collections/cluster-manager-acl-cache.d.ts +22 -0
- package/dist/server/collections/cluster-manager-cache-mgr.d.ts +22 -0
- package/dist/server/collections/cluster-manager-cluster.d.ts +22 -0
- package/dist/server/collections/cluster-manager-doctor-runs.d.ts +3 -0
- package/dist/server/collections/cluster-manager-doctor-runs.js +52 -0
- package/dist/server/collections/cluster-manager-doctor.d.ts +18 -0
- package/dist/server/collections/cluster-manager-doctor.js +44 -0
- package/dist/server/collections/cluster-manager-lock.d.ts +22 -0
- package/dist/server/collections/cluster-manager-plugins.d.ts +18 -0
- package/dist/server/collections/cluster-manager-queue.d.ts +22 -0
- package/dist/server/collections/cluster-manager-redis.d.ts +22 -0
- package/dist/server/collections/cluster-manager-workflow.d.ts +22 -0
- package/dist/server/collections/cluster-manager.d.ts +22 -0
- package/dist/server/collections/orchestrator-settings.d.ts +59 -0
- package/dist/server/collections/orchestrator-stacks.d.ts +102 -0
- package/dist/server/collections/worker-orchestrator.d.ts +22 -0
- package/dist/server/collections/worker-packages-configs.d.ts +3 -0
- package/dist/server/collections/worker-packages.d.ts +22 -0
- package/dist/server/hooks/cacheInvalidationHooks.d.ts +1 -0
- package/dist/server/hooks/cacheInvalidationHooks.js +81 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/middlewares/listMetaCacheMiddleware.d.ts +2 -0
- package/dist/server/middlewares/listMetaCacheMiddleware.js +79 -0
- package/dist/server/orchestrator/PackageManager.d.ts +39 -0
- package/dist/server/orchestrator/PackageManager.js +83 -27
- package/dist/server/orchestrator/docker-adapter.d.ts +41 -0
- package/dist/server/orchestrator/index.d.ts +4 -0
- package/dist/server/orchestrator/k8s-adapter.d.ts +50 -0
- package/dist/server/orchestrator/leader-election.d.ts +48 -0
- package/dist/server/orchestrator/types.d.ts +84 -0
- package/dist/server/plugin.d.ts +26 -0
- package/dist/server/plugin.js +70 -8
- package/dist/server/utils/node.d.ts +6 -0
- package/dist/server/utils/redis.d.ts +29 -0
- package/dist/server/utils/versionManager.d.ts +10 -0
- package/dist/server/utils/versionManager.js +91 -0
- package/dist/shared/packages.d.ts +23 -0
- package/package.json +41 -41
- package/server.js +1 -0
- package/src/client/CacheMonitor.tsx +166 -179
- package/src/client/ClusterManagerLayout.tsx +48 -42
- package/src/client/ClusterNodes.tsx +691 -418
- package/src/client/Doctor.tsx +559 -0
- package/src/client/NginxCacheManager.tsx +415 -0
- package/src/client/PluginOperations.tsx +234 -234
- 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/locale/en-US.json +97 -1
- package/src/locale/vi-VN.json +98 -1
- package/src/locale/zh-CN.json +98 -1
- 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 +882 -378
- package/src/server/actions/doctor.ts +1540 -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/hooks/cacheInvalidationHooks.ts +58 -0
- package/src/server/middlewares/listMetaCacheMiddleware.ts +55 -0
- package/src/server/orchestrator/PackageManager.ts +19 -15
- package/src/server/plugin.ts +353 -263
- package/src/server/utils/versionManager.ts +69 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leader Election using native Redis commands
|
|
3
|
+
*
|
|
4
|
+
* Ensures only ONE app instance runs orchestrator write operations (scale, start, stop).
|
|
5
|
+
* Other instances remain read-only followers.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. After app starts, tryBecomeLeader() attempts to acquire the Redis lock using SET NX PX.
|
|
9
|
+
* 2. If acquired → this node is leader; a renewal timer extends the lock periodically using a Lua script.
|
|
10
|
+
* 3. If not acquired → this node is follower; it retries periodically.
|
|
11
|
+
* 4. On app stop → release the lock gracefully using a Lua script.
|
|
12
|
+
* 5. On crash → lock auto-expires after TTL.
|
|
13
|
+
*/
|
|
14
|
+
export declare class LeaderElection {
|
|
15
|
+
private app;
|
|
16
|
+
private renewTimer;
|
|
17
|
+
private retryTimer;
|
|
18
|
+
private _isLeader;
|
|
19
|
+
private _leaderId;
|
|
20
|
+
private standaloneMode;
|
|
21
|
+
private enabled;
|
|
22
|
+
private _disabledReason;
|
|
23
|
+
private redis;
|
|
24
|
+
constructor(app: any, options?: {
|
|
25
|
+
standaloneMode?: boolean;
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
disabledReason?: string;
|
|
28
|
+
});
|
|
29
|
+
get isLeader(): boolean;
|
|
30
|
+
get leaderId(): string;
|
|
31
|
+
get disabledReason(): string;
|
|
32
|
+
/**
|
|
33
|
+
* Initialize Redis client. Must be called after Redis is connected.
|
|
34
|
+
*/
|
|
35
|
+
init(): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Attempt to become the orchestrator leader.
|
|
38
|
+
* If successful, starts a renewal loop.
|
|
39
|
+
* If not, starts a retry loop.
|
|
40
|
+
*/
|
|
41
|
+
tryBecomeLeader(): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Gracefully release leadership.
|
|
44
|
+
*/
|
|
45
|
+
release(): Promise<void>;
|
|
46
|
+
private startRenewal;
|
|
47
|
+
private startRetry;
|
|
48
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Abstraction layer for container runtimes (Docker, K8s, Swarm).
|
|
5
|
+
* Each adapter implements this interface so the plugin logic is runtime-agnostic.
|
|
6
|
+
*/
|
|
7
|
+
export interface ContainerInfo {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
status: 'running' | 'stopped' | 'pending' | 'error' | 'creating' | 'exited';
|
|
11
|
+
image: string;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
cpu?: number;
|
|
14
|
+
memory?: number;
|
|
15
|
+
labels?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export interface ScaleResult {
|
|
18
|
+
previousReplicas: number;
|
|
19
|
+
currentReplicas: number;
|
|
20
|
+
containersCreated?: string[];
|
|
21
|
+
containersRemoved?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface ContainerStats {
|
|
24
|
+
cpu: number;
|
|
25
|
+
memory: number;
|
|
26
|
+
memoryLimit: number;
|
|
27
|
+
networkRx: number;
|
|
28
|
+
networkTx: number;
|
|
29
|
+
}
|
|
30
|
+
export interface StackConfig {
|
|
31
|
+
id?: number;
|
|
32
|
+
name: string;
|
|
33
|
+
adapter: 'docker' | 'kubernetes';
|
|
34
|
+
image: string;
|
|
35
|
+
command?: string;
|
|
36
|
+
envVars?: Record<string, string>;
|
|
37
|
+
volumes?: string[];
|
|
38
|
+
networks?: string[];
|
|
39
|
+
resourceLimits?: {
|
|
40
|
+
memory?: string;
|
|
41
|
+
cpu?: string;
|
|
42
|
+
};
|
|
43
|
+
replicas: number;
|
|
44
|
+
desiredReplicas: number;
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
namespace?: string;
|
|
47
|
+
deploymentName?: string;
|
|
48
|
+
serviceAccountName?: string;
|
|
49
|
+
imagePullPolicy?: string;
|
|
50
|
+
k8sContainerName?: string;
|
|
51
|
+
k8sEnv?: Record<string, any>[];
|
|
52
|
+
k8sEnvFrom?: Record<string, any>[];
|
|
53
|
+
k8sVolumeMounts?: Record<string, any>[];
|
|
54
|
+
k8sVolumes?: Record<string, any>[];
|
|
55
|
+
networkMode?: string;
|
|
56
|
+
restartPolicy?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface IOrchestratorAdapter {
|
|
59
|
+
/** Human-readable adapter name */
|
|
60
|
+
readonly name: string;
|
|
61
|
+
/** Test connectivity to the runtime */
|
|
62
|
+
ping(): Promise<boolean>;
|
|
63
|
+
/** List containers managed by a stack */
|
|
64
|
+
listContainers(stack: StackConfig): Promise<ContainerInfo[]>;
|
|
65
|
+
/** Verify that a runtime container/pod belongs to the requested stack */
|
|
66
|
+
assertManagedByStack(stack: StackConfig, containerId: string): Promise<void>;
|
|
67
|
+
/** Scale stack to N replicas */
|
|
68
|
+
scale(stack: StackConfig, replicas: number): Promise<ScaleResult>;
|
|
69
|
+
/** Start a stopped container */
|
|
70
|
+
startContainer(containerId: string): Promise<void>;
|
|
71
|
+
/** Gracefully stop a running container */
|
|
72
|
+
stopContainer(containerId: string, timeoutSecs?: number): Promise<void>;
|
|
73
|
+
/** Remove/delete a container (force) */
|
|
74
|
+
removeContainer(containerId: string): Promise<void>;
|
|
75
|
+
/** Get real-time resource stats for a container */
|
|
76
|
+
getStats(containerId: string): Promise<ContainerStats>;
|
|
77
|
+
/** Get tail logs from a container */
|
|
78
|
+
getLogs(containerId: string, tail?: number): Promise<string>;
|
|
79
|
+
/** List available networks (if supported by adapter) */
|
|
80
|
+
listNetworks?(): Promise<{
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
}[]>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/server';
|
|
2
|
+
import { RedisNodeRegistry } from './adapters/redis-node-registry';
|
|
3
|
+
import type { IOrchestratorAdapter } from './orchestrator/types';
|
|
4
|
+
import { LeaderElection } from './orchestrator/leader-election';
|
|
5
|
+
export declare class PluginClusterManagerServer extends Plugin {
|
|
6
|
+
nodeRegistry: RedisNodeRegistry;
|
|
7
|
+
orchestrator: IOrchestratorAdapter | null;
|
|
8
|
+
leaderElection: LeaderElection | null;
|
|
9
|
+
beforeLoad(): Promise<void>;
|
|
10
|
+
load(): Promise<void>;
|
|
11
|
+
private registerPubSubAdapter;
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the Container Orchestrator subsystem.
|
|
14
|
+
* Config is loaded from DB (orchestratorSettings collection) first,
|
|
15
|
+
* then falls back to ORCHESTRATOR_ADAPTER env var.
|
|
16
|
+
* This allows manual configuration via the NocoBase admin UI.
|
|
17
|
+
*/
|
|
18
|
+
private initOrchestrator;
|
|
19
|
+
/**
|
|
20
|
+
* Connect (or reconnect) the orchestrator adapter based on settings.
|
|
21
|
+
* Can be called at startup or when user saves new settings via UI.
|
|
22
|
+
*/
|
|
23
|
+
connectAdapter(settings: any): Promise<boolean>;
|
|
24
|
+
private isWorkerOnlyNode;
|
|
25
|
+
}
|
|
26
|
+
export default PluginClusterManagerServer;
|
package/dist/server/plugin.js
CHANGED
|
@@ -63,6 +63,9 @@ var import_k8s_adapter = require("./orchestrator/k8s-adapter");
|
|
|
63
63
|
var import_leader_election = require("./orchestrator/leader-election");
|
|
64
64
|
var import_package_manager = require("./actions/package-manager");
|
|
65
65
|
var import_PackageManager = require("./orchestrator/PackageManager");
|
|
66
|
+
var import_listMetaCacheMiddleware = require("./middlewares/listMetaCacheMiddleware");
|
|
67
|
+
var import_cacheInvalidationHooks = require("./hooks/cacheInvalidationHooks");
|
|
68
|
+
var import_doctor = require("./actions/doctor");
|
|
66
69
|
class PluginClusterManagerServer extends import_server.Plugin {
|
|
67
70
|
nodeRegistry;
|
|
68
71
|
orchestrator = null;
|
|
@@ -71,6 +74,15 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
71
74
|
await this.db.import({ directory: import_path.default.resolve(__dirname, "collections") });
|
|
72
75
|
}
|
|
73
76
|
async load() {
|
|
77
|
+
this.db.extendCollection({
|
|
78
|
+
name: "attachments",
|
|
79
|
+
fields: [
|
|
80
|
+
{
|
|
81
|
+
type: "bigInt",
|
|
82
|
+
name: "createdById"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
});
|
|
74
86
|
this.nodeRegistry = new import_redis_node_registry.RedisNodeRegistry(this.app);
|
|
75
87
|
this.app.on("afterStart", () => {
|
|
76
88
|
var _a;
|
|
@@ -98,7 +110,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
98
110
|
try {
|
|
99
111
|
const customRaw = config.get("customPackages");
|
|
100
112
|
if (customRaw) custom = typeof customRaw === "string" ? JSON.parse(customRaw) : customRaw;
|
|
101
|
-
} catch {
|
|
113
|
+
} catch (err) {
|
|
102
114
|
}
|
|
103
115
|
const unique = (arr) => Array.from(new Set(arr.filter(Boolean)));
|
|
104
116
|
const packages = {
|
|
@@ -161,16 +173,51 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
161
173
|
if (!redis || !requestId) return;
|
|
162
174
|
const logData = await (0, import_cluster_nodes.readLocalLogs)(this.app, lines || 200);
|
|
163
175
|
const responseKey = `cluster-manager:log-response:${requestId}`;
|
|
176
|
+
await redis.sendCommand(["SET", responseKey, JSON.stringify(logData), "EX", "30"]);
|
|
177
|
+
this.app.logger.debug(`[ClusterManager] Served log request ${requestId} for ${targetNodeId}`);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
this.app.logger.error(`[ClusterManager] Error handling log request: ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
pubSub.subscribe(`cluster-manager:doctor-collect:${myNodeId}`, async (msg) => {
|
|
183
|
+
const redis = (0, import_redis.getRedisClient)(this.app);
|
|
184
|
+
let requestId = "";
|
|
185
|
+
try {
|
|
186
|
+
const parsed = typeof msg === "string" ? JSON.parse(msg) : msg;
|
|
187
|
+
requestId = parsed.requestId;
|
|
188
|
+
if (!redis || !requestId) return;
|
|
189
|
+
const snapshot = await (0, import_doctor.collectLocalDoctorSnapshot)(this.app, {
|
|
190
|
+
runId: parsed.runId,
|
|
191
|
+
sinceMs: parsed.sinceMs,
|
|
192
|
+
untilMs: parsed.untilMs,
|
|
193
|
+
maxLines: parsed.maxLines
|
|
194
|
+
});
|
|
164
195
|
await redis.sendCommand([
|
|
165
196
|
"SET",
|
|
166
|
-
|
|
167
|
-
JSON.stringify(
|
|
197
|
+
`cluster-manager:doctor-response:${requestId}`,
|
|
198
|
+
JSON.stringify(snapshot),
|
|
168
199
|
"EX",
|
|
169
|
-
"
|
|
200
|
+
"90"
|
|
170
201
|
]);
|
|
171
|
-
this.app.logger.debug(`[ClusterManager] Served
|
|
202
|
+
this.app.logger.debug(`[ClusterManager] Served doctor snapshot request ${requestId}`);
|
|
172
203
|
} catch (err) {
|
|
173
|
-
|
|
204
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
205
|
+
this.app.logger.error(`[ClusterManager] Error handling doctor snapshot request: ${message}`);
|
|
206
|
+
if (redis && requestId) {
|
|
207
|
+
const fallback = {
|
|
208
|
+
nodeId: (0, import_node.getLocalNodeId)(this.app),
|
|
209
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
210
|
+
error: message
|
|
211
|
+
};
|
|
212
|
+
await redis.sendCommand([
|
|
213
|
+
"SET",
|
|
214
|
+
`cluster-manager:doctor-response:${requestId}`,
|
|
215
|
+
JSON.stringify(fallback),
|
|
216
|
+
"EX",
|
|
217
|
+
"90"
|
|
218
|
+
]).catch(() => {
|
|
219
|
+
});
|
|
220
|
+
}
|
|
174
221
|
}
|
|
175
222
|
});
|
|
176
223
|
pubSub.subscribe("cluster-manager.install-packages", async (payload) => {
|
|
@@ -186,12 +233,16 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
186
233
|
try {
|
|
187
234
|
let target = msg;
|
|
188
235
|
let mode = "hard";
|
|
236
|
+
let targetNodeId = "";
|
|
189
237
|
if (msg.startsWith("{")) {
|
|
190
238
|
const parsed = JSON.parse(msg);
|
|
191
|
-
target = parsed.hostname;
|
|
239
|
+
target = parsed.hostname || parsed.target || "";
|
|
240
|
+
targetNodeId = parsed.targetNodeId || "";
|
|
192
241
|
mode = parsed.mode || "hard";
|
|
193
242
|
}
|
|
194
|
-
|
|
243
|
+
const myNodeId2 = (0, import_node.getLocalNodeId)(this.app);
|
|
244
|
+
const shouldRestart = targetNodeId ? targetNodeId === myNodeId2 : target === os.hostname() || target === "*";
|
|
245
|
+
if (shouldRestart) {
|
|
195
246
|
this.app.logger.warn(`[ClusterManager] Received ${mode} restart command for node ${os.hostname()}...`);
|
|
196
247
|
setTimeout(async () => {
|
|
197
248
|
try {
|
|
@@ -232,6 +283,10 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
232
283
|
name: "clusterManagerCluster",
|
|
233
284
|
actions: import_cluster_nodes.clusterActions
|
|
234
285
|
});
|
|
286
|
+
this.app.resourcer.define({
|
|
287
|
+
name: "clusterManagerDoctor",
|
|
288
|
+
actions: import_doctor.doctorActions
|
|
289
|
+
});
|
|
235
290
|
this.app.resourcer.define({
|
|
236
291
|
name: "clusterManagerQueue",
|
|
237
292
|
actions: import_event_queue_monitor.eventQueueActions
|
|
@@ -258,6 +313,12 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
258
313
|
before: "core",
|
|
259
314
|
after: "allow-manager"
|
|
260
315
|
});
|
|
316
|
+
const listMetaCacheMiddleware = (0, import_listMetaCacheMiddleware.createListMetaCacheMiddleware)(this.app);
|
|
317
|
+
this.app.resourcer.use(listMetaCacheMiddleware, {
|
|
318
|
+
tag: "listMetaCache",
|
|
319
|
+
after: "setCurrentRole"
|
|
320
|
+
});
|
|
321
|
+
(0, import_cacheInvalidationHooks.registerCacheHooks)(this.app);
|
|
261
322
|
this.app.use(async (ctx, next) => {
|
|
262
323
|
if (ctx.path === "/api/clusterManager:health" && (ctx.method === "GET" || ctx.method === "HEAD")) {
|
|
263
324
|
ctx.body = {
|
|
@@ -277,6 +338,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
|
|
|
277
338
|
"clusterManagerRedis:*",
|
|
278
339
|
"clusterManagerAclCache:*",
|
|
279
340
|
"clusterManagerCluster:*",
|
|
341
|
+
"clusterManagerDoctor:*",
|
|
280
342
|
"clusterManagerQueue:*",
|
|
281
343
|
"clusterManagerLock:*",
|
|
282
344
|
"clusterManagerCacheMgr:*",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a universally unique identifier for this specific Node.js process.
|
|
3
|
+
* Combines app name, worker mode, hostname, port, and PID to ensure uniqueness
|
|
4
|
+
* even when multiple workers run on the exact same host.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getLocalNodeId(app: any): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Context } from '@nocobase/actions';
|
|
2
|
+
export declare function getRedisClient(app?: any): any;
|
|
3
|
+
/**
|
|
4
|
+
* Get the shared Redis connection from the app's connection manager.
|
|
5
|
+
* Returns undefined if Redis is not configured.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getRedis(ctx: Context): any;
|
|
8
|
+
/**
|
|
9
|
+
* Get Redis connection or throw 503 if not available.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRedisOrThrow(ctx: Context): any;
|
|
12
|
+
/**
|
|
13
|
+
* Scan Redis keys using SCAN (cursor-based) instead of the blocking KEYS command.
|
|
14
|
+
* Safe for production use — never blocks the Redis event loop.
|
|
15
|
+
*
|
|
16
|
+
* @param redis - Redis client instance
|
|
17
|
+
* @param pattern - Glob pattern to match keys (e.g. "acl:can:*")
|
|
18
|
+
* @param batchSize - Number of keys to scan per iteration (default 200)
|
|
19
|
+
*/
|
|
20
|
+
export declare function scanKeys(redis: any, pattern: string, batchSize?: number): Promise<string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Delete keys in chunked batches to avoid exceeding Redis command argument limits.
|
|
23
|
+
*
|
|
24
|
+
* @param redis - Redis client instance
|
|
25
|
+
* @param keys - Array of keys to delete
|
|
26
|
+
* @param chunkSize - Max keys per DEL command (default 500)
|
|
27
|
+
* @returns Total number of keys deleted
|
|
28
|
+
*/
|
|
29
|
+
export declare function deleteKeysChunked(redis: any, keys: string[], chunkSize?: number): Promise<number>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const cacheVersionManager: {
|
|
2
|
+
getCollectionVersion(app: any): Promise<number>;
|
|
3
|
+
incrementCollectionVersion(app: any): Promise<number>;
|
|
4
|
+
getSchemaVersion(app: any): Promise<number>;
|
|
5
|
+
incrementSchemaVersion(app: any): Promise<number>;
|
|
6
|
+
getAclVersion(app: any, roleName: string): Promise<number>;
|
|
7
|
+
incrementAclVersion(app: any, roleName: string): Promise<number>;
|
|
8
|
+
incrementAllAclVersions(app: any): Promise<number>;
|
|
9
|
+
getGlobalAclVersion(app: any): Promise<number>;
|
|
10
|
+
};
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const DEFAULT_WORKER_PACKAGES: {
|
|
2
|
+
apt: string[];
|
|
3
|
+
python: string[];
|
|
4
|
+
npm: string[];
|
|
5
|
+
};
|
|
6
|
+
export interface WorkerPackageMap {
|
|
7
|
+
apt?: string[];
|
|
8
|
+
npm?: string[];
|
|
9
|
+
python?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface CustomPackageMap {
|
|
12
|
+
python?: string[];
|
|
13
|
+
node?: string[];
|
|
14
|
+
npm?: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizePackages(packages?: Array<string | undefined>): string[];
|
|
17
|
+
export declare function parsePackageText(value: unknown, fallback?: string[]): string[];
|
|
18
|
+
export declare function formatPackageText(packages?: string[]): string;
|
|
19
|
+
export declare function packagesFromConfig(config: {
|
|
20
|
+
aptPackages?: unknown;
|
|
21
|
+
pythonPackages?: unknown;
|
|
22
|
+
npmPackages?: unknown;
|
|
23
|
+
}): WorkerPackageMap;
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "plugin-cluster-manager",
|
|
3
|
-
"displayName": "Cluster Manager",
|
|
4
|
-
"displayName.zh-CN": "Cluster Manager",
|
|
5
|
-
"description": "Cluster node tracking, task orchestration, worker management, and package distribution",
|
|
6
|
-
"version": "1.1.
|
|
7
|
-
"license": "Apache-2.0",
|
|
8
|
-
"main": "./dist/server/index.js",
|
|
9
|
-
"keywords": [
|
|
10
|
-
"Monitoring",
|
|
11
|
-
"DevOps"
|
|
12
|
-
],
|
|
13
|
-
"files": [
|
|
14
|
-
"dist",
|
|
15
|
-
"src",
|
|
16
|
-
"client.js",
|
|
17
|
-
"server.js",
|
|
18
|
-
"client.d.ts",
|
|
19
|
-
"server.d.ts"
|
|
20
|
-
],
|
|
21
|
-
"peerDependencies": {
|
|
22
|
-
"@nocobase/client": "2.x",
|
|
23
|
-
"@nocobase/server": "2.x",
|
|
24
|
-
"@nocobase/database": "2.x"
|
|
25
|
-
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"@nocobase/lock-manager": "2.x"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"@kubernetes/client-node": "^0.22.3",
|
|
31
|
-
"dockerode": "^3.3.5",
|
|
32
|
-
"redis": "^5.10.0",
|
|
33
|
-
"redlock": "5.0.0-beta.2",
|
|
34
|
-
"uuid": "^9.0.1"
|
|
35
|
-
},
|
|
36
|
-
"nocobase": {
|
|
37
|
-
"supportedVersions": [
|
|
38
|
-
"2.x"
|
|
39
|
-
],
|
|
40
|
-
"editionLevel": 0
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "plugin-cluster-manager",
|
|
3
|
+
"displayName": "Cluster Manager",
|
|
4
|
+
"displayName.zh-CN": "Cluster Manager",
|
|
5
|
+
"description": "Cluster node tracking, task orchestration, worker management, and package distribution",
|
|
6
|
+
"version": "1.1.11",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"main": "./dist/server/index.js",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"Monitoring",
|
|
11
|
+
"DevOps"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src",
|
|
16
|
+
"client.js",
|
|
17
|
+
"server.js",
|
|
18
|
+
"client.d.ts",
|
|
19
|
+
"server.d.ts"
|
|
20
|
+
],
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@nocobase/client": "2.x",
|
|
23
|
+
"@nocobase/server": "2.x",
|
|
24
|
+
"@nocobase/database": "2.x"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@nocobase/lock-manager": "2.x"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@kubernetes/client-node": "^0.22.3",
|
|
31
|
+
"dockerode": "^3.3.5",
|
|
32
|
+
"redis": "^5.10.0",
|
|
33
|
+
"redlock": "5.0.0-beta.2",
|
|
34
|
+
"uuid": "^9.0.1"
|
|
35
|
+
},
|
|
36
|
+
"nocobase": {
|
|
37
|
+
"supportedVersions": [
|
|
38
|
+
"2.x"
|
|
39
|
+
],
|
|
40
|
+
"editionLevel": 0
|
|
41
|
+
}
|
|
42
42
|
}
|
package/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/server/index.js');
|