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,102 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
name: string;
|
|
3
|
+
autoGenId: boolean;
|
|
4
|
+
createdAt: boolean;
|
|
5
|
+
updatedAt: boolean;
|
|
6
|
+
fields: ({
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
unique: boolean;
|
|
10
|
+
interface: string;
|
|
11
|
+
uiSchema: {
|
|
12
|
+
title: string;
|
|
13
|
+
'x-component': string;
|
|
14
|
+
enum?: undefined;
|
|
15
|
+
};
|
|
16
|
+
defaultValue?: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
defaultValue: string;
|
|
21
|
+
interface: string;
|
|
22
|
+
uiSchema: {
|
|
23
|
+
title: string;
|
|
24
|
+
'x-component': string;
|
|
25
|
+
enum: {
|
|
26
|
+
value: string;
|
|
27
|
+
label: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
unique?: undefined;
|
|
31
|
+
} | {
|
|
32
|
+
name: string;
|
|
33
|
+
type: string;
|
|
34
|
+
interface: string;
|
|
35
|
+
uiSchema: {
|
|
36
|
+
title: string;
|
|
37
|
+
'x-component': string;
|
|
38
|
+
enum?: undefined;
|
|
39
|
+
};
|
|
40
|
+
unique?: undefined;
|
|
41
|
+
defaultValue?: undefined;
|
|
42
|
+
} | {
|
|
43
|
+
name: string;
|
|
44
|
+
type: string;
|
|
45
|
+
defaultValue: {};
|
|
46
|
+
interface: string;
|
|
47
|
+
uiSchema: {
|
|
48
|
+
title: string;
|
|
49
|
+
'x-component'?: undefined;
|
|
50
|
+
enum?: undefined;
|
|
51
|
+
};
|
|
52
|
+
unique?: undefined;
|
|
53
|
+
} | {
|
|
54
|
+
name: string;
|
|
55
|
+
type: string;
|
|
56
|
+
defaultValue: any[];
|
|
57
|
+
interface: string;
|
|
58
|
+
uiSchema: {
|
|
59
|
+
title: string;
|
|
60
|
+
'x-component'?: undefined;
|
|
61
|
+
enum?: undefined;
|
|
62
|
+
};
|
|
63
|
+
unique?: undefined;
|
|
64
|
+
} | {
|
|
65
|
+
name: string;
|
|
66
|
+
type: string;
|
|
67
|
+
defaultValue: number;
|
|
68
|
+
interface: string;
|
|
69
|
+
uiSchema: {
|
|
70
|
+
title: string;
|
|
71
|
+
'x-component'?: undefined;
|
|
72
|
+
enum?: undefined;
|
|
73
|
+
};
|
|
74
|
+
unique?: undefined;
|
|
75
|
+
} | {
|
|
76
|
+
name: string;
|
|
77
|
+
type: string;
|
|
78
|
+
defaultValue: boolean;
|
|
79
|
+
interface: string;
|
|
80
|
+
uiSchema: {
|
|
81
|
+
title: string;
|
|
82
|
+
'x-component'?: undefined;
|
|
83
|
+
enum?: undefined;
|
|
84
|
+
};
|
|
85
|
+
unique?: undefined;
|
|
86
|
+
} | {
|
|
87
|
+
name: string;
|
|
88
|
+
type: string;
|
|
89
|
+
defaultValue: string;
|
|
90
|
+
unique?: undefined;
|
|
91
|
+
interface?: undefined;
|
|
92
|
+
uiSchema?: undefined;
|
|
93
|
+
} | {
|
|
94
|
+
name: string;
|
|
95
|
+
type: string;
|
|
96
|
+
unique?: undefined;
|
|
97
|
+
interface?: undefined;
|
|
98
|
+
uiSchema?: undefined;
|
|
99
|
+
defaultValue?: undefined;
|
|
100
|
+
})[];
|
|
101
|
+
};
|
|
102
|
+
export default _default;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DUMMY COLLECTION
|
|
3
|
+
* This collection is created to prevent NocoBase workflow/ACL from throwing the error:
|
|
4
|
+
* '[Workflow pre-action]: collection "workerOrchestrator" not found'
|
|
5
|
+
*
|
|
6
|
+
* Since 'workerOrchestrator' is registered as a resourcer in plugin.ts, NocoBase
|
|
7
|
+
* implicitly looks for a collection with the same name.
|
|
8
|
+
* We set dumpRules: 'skip' and avoid timestamps to ensure this collection
|
|
9
|
+
* is completely ignored during backups/migrations and doesn't pollute the actual DB.
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: {
|
|
12
|
+
name: string;
|
|
13
|
+
dumpRules: string;
|
|
14
|
+
autoGenId: boolean;
|
|
15
|
+
createdAt: boolean;
|
|
16
|
+
updatedAt: boolean;
|
|
17
|
+
fields: {
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DUMMY COLLECTION
|
|
3
|
+
* This collection is created to prevent NocoBase workflow/ACL from throwing the error:
|
|
4
|
+
* '[Workflow pre-action]: collection "workerPackages" not found'
|
|
5
|
+
*
|
|
6
|
+
* Since 'workerPackages' is registered as a resourcer in plugin.ts, NocoBase
|
|
7
|
+
* implicitly looks for a collection with the same name.
|
|
8
|
+
* We set dumpRules: 'skip' and avoid timestamps to ensure this collection
|
|
9
|
+
* is completely ignored during backups/migrations and doesn't pollute the actual DB.
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: {
|
|
12
|
+
name: string;
|
|
13
|
+
dumpRules: string;
|
|
14
|
+
autoGenId: boolean;
|
|
15
|
+
createdAt: boolean;
|
|
16
|
+
updatedAt: boolean;
|
|
17
|
+
fields: {
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerCacheHooks(app: any): void;
|
|
@@ -0,0 +1,81 @@
|
|
|
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 cacheInvalidationHooks_exports = {};
|
|
28
|
+
__export(cacheInvalidationHooks_exports, {
|
|
29
|
+
registerCacheHooks: () => registerCacheHooks
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(cacheInvalidationHooks_exports);
|
|
32
|
+
var import_versionManager = require("../utils/versionManager");
|
|
33
|
+
function registerCacheHooks(app) {
|
|
34
|
+
const db = app.db;
|
|
35
|
+
db.on("collections.afterSave", async () => {
|
|
36
|
+
await import_versionManager.cacheVersionManager.incrementCollectionVersion(app);
|
|
37
|
+
});
|
|
38
|
+
db.on("collections.afterDestroy", async () => {
|
|
39
|
+
await import_versionManager.cacheVersionManager.incrementCollectionVersion(app);
|
|
40
|
+
});
|
|
41
|
+
db.on("fields.afterSave", async () => {
|
|
42
|
+
await import_versionManager.cacheVersionManager.incrementCollectionVersion(app);
|
|
43
|
+
});
|
|
44
|
+
db.on("fields.afterDestroy", async () => {
|
|
45
|
+
await import_versionManager.cacheVersionManager.incrementCollectionVersion(app);
|
|
46
|
+
});
|
|
47
|
+
db.on("uiSchemas.afterSave", async () => {
|
|
48
|
+
await import_versionManager.cacheVersionManager.incrementSchemaVersion(app);
|
|
49
|
+
});
|
|
50
|
+
db.on("uiSchemas.afterDestroy", async () => {
|
|
51
|
+
await import_versionManager.cacheVersionManager.incrementSchemaVersion(app);
|
|
52
|
+
});
|
|
53
|
+
const invalidateRole = async (model) => {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
const roleName = ((_a = model.get) == null ? void 0 : _a.call(model, "roleName")) || ((_b = model.get) == null ? void 0 : _b.call(model, "name"));
|
|
56
|
+
if (roleName) {
|
|
57
|
+
await import_versionManager.cacheVersionManager.incrementAclVersion(app, roleName);
|
|
58
|
+
} else {
|
|
59
|
+
await import_versionManager.cacheVersionManager.incrementAllAclVersions(app);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
db.on("roles.afterSave", invalidateRole);
|
|
63
|
+
db.on("roles.afterDestroy", invalidateRole);
|
|
64
|
+
db.on("rolesResources.afterSave", invalidateRole);
|
|
65
|
+
db.on("rolesResources.afterDestroy", invalidateRole);
|
|
66
|
+
db.on("rolesResourcesActions.afterSave", invalidateRole);
|
|
67
|
+
db.on("rolesResourcesActions.afterDestroy", invalidateRole);
|
|
68
|
+
db.on("rolesUsers.afterSave", invalidateRole);
|
|
69
|
+
db.on("rolesUsers.afterDestroy", invalidateRole);
|
|
70
|
+
db.on("scopes.afterSave", async () => {
|
|
71
|
+
await import_versionManager.cacheVersionManager.incrementAllAclVersions(app);
|
|
72
|
+
});
|
|
73
|
+
db.on("scopes.afterDestroy", async () => {
|
|
74
|
+
await import_versionManager.cacheVersionManager.incrementAllAclVersions(app);
|
|
75
|
+
});
|
|
76
|
+
app.logger.info("[ClusterManager] Cache invalidation hooks registered successfully");
|
|
77
|
+
}
|
|
78
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
79
|
+
0 && (module.exports = {
|
|
80
|
+
registerCacheHooks
|
|
81
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './plugin';
|
|
@@ -0,0 +1,79 @@
|
|
|
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 listMetaCacheMiddleware_exports = {};
|
|
28
|
+
__export(listMetaCacheMiddleware_exports, {
|
|
29
|
+
createListMetaCacheMiddleware: () => createListMetaCacheMiddleware
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(listMetaCacheMiddleware_exports);
|
|
32
|
+
var import_versionManager = require("../utils/versionManager");
|
|
33
|
+
const LIST_META_CACHE_TTL = 1e3 * 60 * 10;
|
|
34
|
+
function getErrorMessage(error) {
|
|
35
|
+
return error instanceof Error ? error.message : String(error);
|
|
36
|
+
}
|
|
37
|
+
function createListMetaCacheMiddleware(app) {
|
|
38
|
+
return async function listMetaCacheMiddleware(ctx, next) {
|
|
39
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
40
|
+
const cache = app.cache;
|
|
41
|
+
if (!cache || ((_a = ctx.action) == null ? void 0 : _a.resourceName) !== "collections" || ((_b = ctx.action) == null ? void 0 : _b.actionName) !== "listMeta") {
|
|
42
|
+
return next();
|
|
43
|
+
}
|
|
44
|
+
const currentRole = ((_c = ctx.state) == null ? void 0 : _c.currentRole) || "anonymous";
|
|
45
|
+
const appName = ctx.headers["x-app"] || "main";
|
|
46
|
+
const dataSource = ctx.headers["x-data-source"] || "main";
|
|
47
|
+
const locale = ctx.headers["x-locale"] || ctx.headers["accept-language"] || "en-US";
|
|
48
|
+
let cacheKey = "";
|
|
49
|
+
let version = 0;
|
|
50
|
+
try {
|
|
51
|
+
version = await import_versionManager.cacheVersionManager.getCollectionVersion(app);
|
|
52
|
+
cacheKey = `nb:cache:${appName}:meta:v${version}:ds:${dataSource}:role:${currentRole}:lang:${locale}`;
|
|
53
|
+
const cached = await cache.get(cacheKey);
|
|
54
|
+
if (cached !== void 0 && cached !== null) {
|
|
55
|
+
ctx.body = typeof cached === "string" ? JSON.parse(cached) : cached;
|
|
56
|
+
(_d = ctx.set) == null ? void 0 : _d.call(ctx, "X-Cache", "HIT");
|
|
57
|
+
(_e = ctx.set) == null ? void 0 : _e.call(ctx, "X-Collection-Version", String(version));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
app.logger.warn(`[ClusterManager] listMeta cache read skipped: ${getErrorMessage(err)}`);
|
|
62
|
+
}
|
|
63
|
+
await next();
|
|
64
|
+
if (ctx.status === 200 && ctx.body && cacheKey) {
|
|
65
|
+
try {
|
|
66
|
+
const valueToCache = typeof ctx.body === "string" ? ctx.body : JSON.stringify(ctx.body);
|
|
67
|
+
await cache.set(cacheKey, valueToCache, LIST_META_CACHE_TTL);
|
|
68
|
+
(_f = ctx.set) == null ? void 0 : _f.call(ctx, "X-Cache", "MISS");
|
|
69
|
+
(_g = ctx.set) == null ? void 0 : _g.call(ctx, "X-Collection-Version", String(version));
|
|
70
|
+
} catch (err) {
|
|
71
|
+
app.logger.warn(`[ClusterManager] listMeta cache write skipped: ${getErrorMessage(err)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
77
|
+
0 && (module.exports = {
|
|
78
|
+
createListMetaCacheMiddleware
|
|
79
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Application from '@nocobase/server';
|
|
2
|
+
type TargetRole = 'app' | 'worker' | 'sandbox' | 'all';
|
|
3
|
+
interface InstallPayload {
|
|
4
|
+
targetRole: TargetRole;
|
|
5
|
+
packages: {
|
|
6
|
+
apt?: string[];
|
|
7
|
+
npm?: string[];
|
|
8
|
+
python?: string[];
|
|
9
|
+
};
|
|
10
|
+
registryConfig?: {
|
|
11
|
+
aptMirrorUrl?: string;
|
|
12
|
+
npmRegistryUrl?: string;
|
|
13
|
+
pypiIndexUrl?: string;
|
|
14
|
+
pypiTrustedHost?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare class PackageManager {
|
|
18
|
+
private app;
|
|
19
|
+
constructor(app: Application);
|
|
20
|
+
/**
|
|
21
|
+
* Called from REST action when admin clicks "Install Packages".
|
|
22
|
+
* Publishes task to all nodes via PubSub. Nodes will filter by targetRole.
|
|
23
|
+
*/
|
|
24
|
+
dispatchInstall(payload: InstallPayload): Promise<string>;
|
|
25
|
+
executeInstall(payload: InstallPayload): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Run a command without a shell so registry URLs and package names are not re-parsed as shell syntax.
|
|
28
|
+
*/
|
|
29
|
+
private runCommand;
|
|
30
|
+
private getMissingPackages;
|
|
31
|
+
private isPackageInstalled;
|
|
32
|
+
private runStatus;
|
|
33
|
+
private getAptOsInfo;
|
|
34
|
+
private buildAptSources;
|
|
35
|
+
private configureAptMirror;
|
|
36
|
+
private moveIfExists;
|
|
37
|
+
private updateInstallStatus;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -41,9 +41,10 @@ __export(PackageManager_exports, {
|
|
|
41
41
|
module.exports = __toCommonJS(PackageManager_exports);
|
|
42
42
|
var import_child_process = require("child_process");
|
|
43
43
|
var import_redis = require("../utils/redis");
|
|
44
|
+
var import_node = require("../utils/node");
|
|
44
45
|
var import_fs = require("fs");
|
|
45
46
|
var import_path = __toESM(require("path"));
|
|
46
|
-
const SAFE_PKG_RE = /^[a-zA-Z0-9_
|
|
47
|
+
const SAFE_PKG_RE = /^(?:[a-zA-Z0-9_.@/-]|\[|\])+$/;
|
|
47
48
|
const INSTALL_CHANNEL = "cluster-manager.install-packages";
|
|
48
49
|
function sanitizePkg(name) {
|
|
49
50
|
if (typeof name !== "string") {
|
|
@@ -99,6 +100,35 @@ function formatCommand(command, args) {
|
|
|
99
100
|
return /^[a-zA-Z0-9_./:@=-]+$/.test(value) ? value : JSON.stringify(value);
|
|
100
101
|
}).join(" ");
|
|
101
102
|
}
|
|
103
|
+
function parseOsRelease(content) {
|
|
104
|
+
const values = {};
|
|
105
|
+
for (const line of content.split("\n")) {
|
|
106
|
+
const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
107
|
+
if (!match) continue;
|
|
108
|
+
values[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
109
|
+
}
|
|
110
|
+
return values;
|
|
111
|
+
}
|
|
112
|
+
function normalizeMirrorUrl(value) {
|
|
113
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
114
|
+
}
|
|
115
|
+
function isInternalCacheMirror(url) {
|
|
116
|
+
return url.hostname === "nginx-cache-registry";
|
|
117
|
+
}
|
|
118
|
+
function resolveAptMirrorForOs(aptMirrorUrl, osInfo, logs) {
|
|
119
|
+
const url = new URL(normalizeMirrorUrl(aptMirrorUrl));
|
|
120
|
+
const original = url.toString();
|
|
121
|
+
if (isInternalCacheMirror(url) && osInfo.id === "debian" && url.pathname === "/ubuntu/") {
|
|
122
|
+
url.pathname = "/debian/";
|
|
123
|
+
} else if (isInternalCacheMirror(url) && osInfo.id === "ubuntu" && url.pathname === "/debian/") {
|
|
124
|
+
url.pathname = "/ubuntu/";
|
|
125
|
+
}
|
|
126
|
+
const resolved = url.toString();
|
|
127
|
+
if (resolved !== original) {
|
|
128
|
+
logs.push(`Adjusted APT mirror for ${osInfo.id}: ${redactUrl(resolved)}`);
|
|
129
|
+
}
|
|
130
|
+
return resolved;
|
|
131
|
+
}
|
|
102
132
|
class PackageManager {
|
|
103
133
|
constructor(app) {
|
|
104
134
|
this.app = app;
|
|
@@ -143,7 +173,7 @@ class PackageManager {
|
|
|
143
173
|
if (registryConfig.aptMirrorUrl) {
|
|
144
174
|
const aptMirrorUrl = sanitizeHttpUrl(registryConfig.aptMirrorUrl, "APT mirror URL");
|
|
145
175
|
logs.push(`Applying APT mirror: ${redactUrl(aptMirrorUrl)}`);
|
|
146
|
-
await this.configureAptMirror(aptMirrorUrl);
|
|
176
|
+
await this.configureAptMirror(aptMirrorUrl, logs);
|
|
147
177
|
}
|
|
148
178
|
await this.updateInstallStatus("running", 20, "Installing APT packages...", logs);
|
|
149
179
|
await this.runCommand("apt-get", ["update", "-qq"], "Updating APT package index...", logs, 12e5);
|
|
@@ -245,11 +275,11 @@ ${logs.join("\n")}`);
|
|
|
245
275
|
let stdout = "";
|
|
246
276
|
let stderr = "";
|
|
247
277
|
let settled = false;
|
|
248
|
-
|
|
278
|
+
const state = {};
|
|
249
279
|
const finish = (error) => {
|
|
250
280
|
if (settled) return;
|
|
251
281
|
settled = true;
|
|
252
|
-
clearTimeout(timer);
|
|
282
|
+
if (state.timer) clearTimeout(state.timer);
|
|
253
283
|
if (stdout) logs.push(stdout.slice(0, 500));
|
|
254
284
|
if (stderr) logs.push(`WARN: ${stderr.slice(0, 300)}`);
|
|
255
285
|
if (error) {
|
|
@@ -259,7 +289,7 @@ ${logs.join("\n")}`);
|
|
|
259
289
|
resolve();
|
|
260
290
|
}
|
|
261
291
|
};
|
|
262
|
-
timer = setTimeout(() => {
|
|
292
|
+
state.timer = setTimeout(() => {
|
|
263
293
|
child.kill("SIGTERM");
|
|
264
294
|
finish(new Error(`${command} timed out after ${timeoutMs}ms`));
|
|
265
295
|
}, timeoutMs);
|
|
@@ -312,14 +342,14 @@ ${logs.join("\n")}`);
|
|
|
312
342
|
const child = (0, import_child_process.spawn)(command, args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
313
343
|
let stdout = "";
|
|
314
344
|
let settled = false;
|
|
315
|
-
|
|
345
|
+
const state = {};
|
|
316
346
|
const finish = (ok) => {
|
|
317
347
|
if (settled) return;
|
|
318
348
|
settled = true;
|
|
319
|
-
clearTimeout(timer);
|
|
349
|
+
if (state.timer) clearTimeout(state.timer);
|
|
320
350
|
resolve(ok);
|
|
321
351
|
};
|
|
322
|
-
timer = setTimeout(() => {
|
|
352
|
+
state.timer = setTimeout(() => {
|
|
323
353
|
child.kill("SIGTERM");
|
|
324
354
|
finish(false);
|
|
325
355
|
}, timeoutMs);
|
|
@@ -330,7 +360,31 @@ ${logs.join("\n")}`);
|
|
|
330
360
|
child.on("close", (code) => finish(code === 0 && (isSuccess ? isSuccess(stdout) : true)));
|
|
331
361
|
});
|
|
332
362
|
}
|
|
333
|
-
async
|
|
363
|
+
async getAptOsInfo() {
|
|
364
|
+
const values = parseOsRelease(await import_fs.promises.readFile("/etc/os-release", "utf8"));
|
|
365
|
+
const id = (values.ID || "").toLowerCase();
|
|
366
|
+
const codename = values.VERSION_CODENAME || values.UBUNTU_CODENAME;
|
|
367
|
+
if (!id || !codename) {
|
|
368
|
+
throw new Error("Cannot detect OS ID/version codename from /etc/os-release for APT mirror configuration.");
|
|
369
|
+
}
|
|
370
|
+
return { id, codename };
|
|
371
|
+
}
|
|
372
|
+
buildAptSources(aptMirrorUrl, osInfo) {
|
|
373
|
+
const mirror = normalizeMirrorUrl(aptMirrorUrl);
|
|
374
|
+
if (osInfo.id === "ubuntu") {
|
|
375
|
+
return `deb ${mirror} ${osInfo.codename} main universe restricted multiverse
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
if (osInfo.id === "debian") {
|
|
379
|
+
return `deb ${mirror} ${osInfo.codename} main contrib non-free non-free-firmware
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
return `deb ${mirror} ${osInfo.codename} main
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
async configureAptMirror(aptMirrorUrl, logs) {
|
|
386
|
+
const osInfo = await this.getAptOsInfo();
|
|
387
|
+
const resolvedMirrorUrl = resolveAptMirrorForOs(aptMirrorUrl, osInfo, logs);
|
|
334
388
|
const backupDir = "/etc/apt/sources.list.d.bak";
|
|
335
389
|
await import_fs.promises.mkdir(backupDir, { recursive: true });
|
|
336
390
|
await this.moveIfExists("/etc/apt/sources.list", import_path.default.join(backupDir, `sources.list.${Date.now()}.bak`));
|
|
@@ -342,8 +396,7 @@ ${logs.join("\n")}`);
|
|
|
342
396
|
}
|
|
343
397
|
} catch {
|
|
344
398
|
}
|
|
345
|
-
await import_fs.promises.writeFile("/etc/apt/sources.list",
|
|
346
|
-
`, "utf8");
|
|
399
|
+
await import_fs.promises.writeFile("/etc/apt/sources.list", this.buildAptSources(resolvedMirrorUrl, osInfo), "utf8");
|
|
347
400
|
}
|
|
348
401
|
async moveIfExists(from, to) {
|
|
349
402
|
try {
|
|
@@ -356,22 +409,25 @@ ${logs.join("\n")}`);
|
|
|
356
409
|
const redisClient = (0, import_redis.getRedisClient)(this.app);
|
|
357
410
|
const podName = process.env.POD_NAME || require("os").hostname();
|
|
358
411
|
if (redisClient) {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
412
|
+
const statusPayload = JSON.stringify({
|
|
413
|
+
initStatus,
|
|
414
|
+
initProgressPercent,
|
|
415
|
+
initProgressLog,
|
|
416
|
+
lastInitAt: /* @__PURE__ */ new Date(),
|
|
417
|
+
lastInitLog: logs.join("\n"),
|
|
418
|
+
...extraValues
|
|
419
|
+
});
|
|
420
|
+
const keys = [`orchestrator:pkg-status:${podName}`, `cluster-manager:pkg-status:${(0, import_node.getLocalNodeId)(this.app)}`];
|
|
421
|
+
for (const key of keys) {
|
|
422
|
+
await redisClient.sendCommand([
|
|
423
|
+
"SET",
|
|
424
|
+
key,
|
|
425
|
+
statusPayload,
|
|
426
|
+
"EX",
|
|
427
|
+
"86400"
|
|
428
|
+
// expire after 1 day
|
|
429
|
+
]);
|
|
430
|
+
}
|
|
375
431
|
}
|
|
376
432
|
const repo = this.app.db.getRepository("workerPackagesConfigs");
|
|
377
433
|
let config = await repo.findOne();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docker Adapter — MVP implementation
|
|
3
|
+
*
|
|
4
|
+
* Uses dockerode to communicate with the Docker daemon via unix socket.
|
|
5
|
+
* Containers are tagged with label `orchestrator.stack=<stackName>` for identification.
|
|
6
|
+
*
|
|
7
|
+
* Security: Docker socket = root on host. Only admin users should access
|
|
8
|
+
* orchestrator actions (enforced by ACL in plugin.ts).
|
|
9
|
+
*/
|
|
10
|
+
import type { IOrchestratorAdapter, ContainerInfo, ScaleResult, ContainerStats, StackConfig } from './types';
|
|
11
|
+
export declare class DockerAdapter implements IOrchestratorAdapter {
|
|
12
|
+
readonly name = "docker";
|
|
13
|
+
private docker;
|
|
14
|
+
private workerLabels;
|
|
15
|
+
constructor(options?: {
|
|
16
|
+
socketPath?: string;
|
|
17
|
+
host?: string;
|
|
18
|
+
port?: number;
|
|
19
|
+
workerLabelSelector?: string;
|
|
20
|
+
});
|
|
21
|
+
ping(): Promise<boolean>;
|
|
22
|
+
listContainers(stack: StackConfig): Promise<ContainerInfo[]>;
|
|
23
|
+
assertManagedByStack(stack: StackConfig, containerId: string): Promise<void>;
|
|
24
|
+
scale(stack: StackConfig, replicas: number): Promise<ScaleResult>;
|
|
25
|
+
startContainer(containerId: string): Promise<void>;
|
|
26
|
+
stopContainer(containerId: string, timeoutSecs?: number): Promise<void>;
|
|
27
|
+
removeContainer(containerId: string): Promise<void>;
|
|
28
|
+
getStats(containerId: string): Promise<ContainerStats>;
|
|
29
|
+
getLogs(containerId: string, tail?: number): Promise<string>;
|
|
30
|
+
listNetworks(): Promise<{
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
}[]>;
|
|
34
|
+
private mapState;
|
|
35
|
+
private buildEnvArray;
|
|
36
|
+
private buildLabelFilters;
|
|
37
|
+
private parseLabelSelector;
|
|
38
|
+
private labelsMatch;
|
|
39
|
+
private parseMemory;
|
|
40
|
+
private demuxDockerLogs;
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes Adapter
|
|
3
|
+
*
|
|
4
|
+
* Manages worker Deployments/Pods via the K8s API using in-cluster ServiceAccount
|
|
5
|
+
* or kubeconfig. Only manages resources labeled with `orchestrator.stack=<stackName>`.
|
|
6
|
+
*
|
|
7
|
+
* Prerequisites:
|
|
8
|
+
* - ServiceAccount with RBAC: get/list/create/patch/update on Deployments and Deployments/scale
|
|
9
|
+
* - get/list/delete on Pods and get on Pods/log
|
|
10
|
+
* - Or KUBECONFIG env var pointing to a valid kubeconfig file
|
|
11
|
+
*/
|
|
12
|
+
import type { IOrchestratorAdapter, ContainerInfo, ScaleResult, ContainerStats, StackConfig } from './types';
|
|
13
|
+
export declare class K8sAdapter implements IOrchestratorAdapter {
|
|
14
|
+
readonly name = "kubernetes";
|
|
15
|
+
private appsApi;
|
|
16
|
+
private coreApi;
|
|
17
|
+
private kc;
|
|
18
|
+
private namespace;
|
|
19
|
+
private workerLabelSelector;
|
|
20
|
+
private workerLabels;
|
|
21
|
+
constructor(options?: {
|
|
22
|
+
kubeconfig?: string;
|
|
23
|
+
context?: string;
|
|
24
|
+
namespace?: string;
|
|
25
|
+
workerLabelSelector?: string;
|
|
26
|
+
});
|
|
27
|
+
ping(): Promise<boolean>;
|
|
28
|
+
listContainers(stack: StackConfig): Promise<ContainerInfo[]>;
|
|
29
|
+
scale(stack: StackConfig, replicas: number): Promise<ScaleResult>;
|
|
30
|
+
startContainer(containerId: string): Promise<void>;
|
|
31
|
+
stopContainer(containerId: string): Promise<void>;
|
|
32
|
+
removeContainer(containerId: string): Promise<void>;
|
|
33
|
+
getStats(containerId: string): Promise<ContainerStats>;
|
|
34
|
+
getLogs(containerId: string, tail?: number): Promise<string>;
|
|
35
|
+
assertManagedByStack(stack: StackConfig, containerId: string): Promise<void>;
|
|
36
|
+
private parsePodRef;
|
|
37
|
+
private buildDeployment;
|
|
38
|
+
private buildWorkerLabels;
|
|
39
|
+
private buildContainerEnv;
|
|
40
|
+
private buildResources;
|
|
41
|
+
private cleanArray;
|
|
42
|
+
private isEmptyValue;
|
|
43
|
+
private isNotFound;
|
|
44
|
+
private assertDeploymentManagedByStack;
|
|
45
|
+
private joinSelectors;
|
|
46
|
+
private parseLabelSelector;
|
|
47
|
+
private labelsMatch;
|
|
48
|
+
private mapPodPhase;
|
|
49
|
+
private parseK8sMemory;
|
|
50
|
+
}
|