plugin-cluster-manager 1.1.0 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/server/actions/orchestrator.d.ts +5 -0
  3. package/dist/server/actions/orchestrator.js +19 -0
  4. package/dist/server/collections/app.d.ts +8 -0
  5. package/dist/server/collections/app.js +38 -0
  6. package/dist/server/collections/cluster-manager-acl-cache.d.ts +22 -0
  7. package/dist/server/collections/cluster-manager-acl-cache.js +44 -0
  8. package/dist/server/collections/cluster-manager-cache-mgr.d.ts +22 -0
  9. package/dist/server/collections/cluster-manager-cache-mgr.js +44 -0
  10. package/dist/server/collections/cluster-manager-cluster.d.ts +22 -0
  11. package/dist/server/collections/cluster-manager-cluster.js +44 -0
  12. package/dist/server/collections/cluster-manager-lock.d.ts +22 -0
  13. package/dist/server/collections/cluster-manager-lock.js +44 -0
  14. package/dist/server/collections/cluster-manager-queue.d.ts +22 -0
  15. package/dist/server/collections/cluster-manager-queue.js +44 -0
  16. package/dist/server/collections/cluster-manager-redis.d.ts +22 -0
  17. package/dist/server/collections/cluster-manager-redis.js +44 -0
  18. package/dist/server/collections/cluster-manager-workflow.d.ts +22 -0
  19. package/dist/server/collections/cluster-manager-workflow.js +44 -0
  20. package/dist/server/collections/cluster-manager.d.ts +22 -0
  21. package/dist/server/collections/cluster-manager.js +44 -0
  22. package/dist/server/collections/worker-orchestrator.d.ts +22 -0
  23. package/dist/server/collections/worker-orchestrator.js +44 -0
  24. package/dist/server/collections/worker-packages.d.ts +22 -0
  25. package/dist/server/collections/worker-packages.js +44 -0
  26. package/dist/server/orchestrator/docker-adapter.d.ts +4 -0
  27. package/dist/server/orchestrator/docker-adapter.js +19 -6
  28. package/dist/server/orchestrator/types.d.ts +5 -0
  29. package/dist/server/plugin.js +1 -1
  30. package/package.json +1 -1
  31. package/src/client/ContainerOrchestrator.tsx +38 -3
  32. package/src/server/actions/orchestrator.ts +20 -0
  33. package/src/server/collections/app.ts +7 -0
  34. package/src/server/collections/cluster-manager-acl-cache.ts +23 -0
  35. package/src/server/collections/cluster-manager-cache-mgr.ts +23 -0
  36. package/src/server/collections/cluster-manager-cluster.ts +23 -0
  37. package/src/server/collections/cluster-manager-lock.ts +23 -0
  38. package/src/server/collections/cluster-manager-queue.ts +23 -0
  39. package/src/server/collections/cluster-manager-redis.ts +23 -0
  40. package/src/server/collections/cluster-manager-workflow.ts +23 -0
  41. package/src/server/collections/cluster-manager.ts +23 -0
  42. package/src/server/collections/worker-orchestrator.ts +23 -0
  43. package/src/server/collections/worker-packages.ts +23 -0
  44. package/src/server/orchestrator/docker-adapter.ts +25 -9
  45. package/src/server/orchestrator/types.ts +3 -0
  46. package/src/server/plugin.ts +1 -1
@@ -0,0 +1,44 @@
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 worker_orchestrator_exports = {};
28
+ __export(worker_orchestrator_exports, {
29
+ default: () => worker_orchestrator_default
30
+ });
31
+ module.exports = __toCommonJS(worker_orchestrator_exports);
32
+ var worker_orchestrator_default = {
33
+ name: "workerOrchestrator",
34
+ dumpRules: "skip",
35
+ autoGenId: true,
36
+ createdAt: false,
37
+ updatedAt: false,
38
+ fields: [
39
+ {
40
+ name: "name",
41
+ type: "string"
42
+ }
43
+ ]
44
+ };
@@ -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,44 @@
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 worker_packages_exports = {};
28
+ __export(worker_packages_exports, {
29
+ default: () => worker_packages_default
30
+ });
31
+ module.exports = __toCommonJS(worker_packages_exports);
32
+ var worker_packages_default = {
33
+ name: "workerPackages",
34
+ dumpRules: "skip",
35
+ autoGenId: true,
36
+ createdAt: false,
37
+ updatedAt: false,
38
+ fields: [
39
+ {
40
+ name: "name",
41
+ type: "string"
42
+ }
43
+ ]
44
+ };
@@ -27,6 +27,10 @@ export declare class DockerAdapter implements IOrchestratorAdapter {
27
27
  removeContainer(containerId: string): Promise<void>;
28
28
  getStats(containerId: string): Promise<ContainerStats>;
29
29
  getLogs(containerId: string, tail?: number): Promise<string>;
30
+ listNetworks(): Promise<{
31
+ id: string;
32
+ name: string;
33
+ }[]>;
30
34
  private mapState;
31
35
  private buildEnvArray;
32
36
  private buildLabelFilters;
@@ -120,10 +120,10 @@ class DockerAdapter {
120
120
  const myContainerId = os.hostname();
121
121
  const myContainer = this.docker.getContainer(myContainerId);
122
122
  const myInfo = await myContainer.inspect();
123
- if (!targetNetworkMode && targetNetworks.length === 0) {
124
- if ((_a = myInfo == null ? void 0 : myInfo.NetworkSettings) == null ? void 0 : _a.Networks) {
125
- targetNetworks = Object.keys(myInfo.NetworkSettings.Networks);
126
- }
123
+ if ((_a = myInfo == null ? void 0 : myInfo.NetworkSettings) == null ? void 0 : _a.Networks) {
124
+ const inheritedNetworks = Object.keys(myInfo.NetworkSettings.Networks);
125
+ targetNetworks = Array.from(/* @__PURE__ */ new Set([...inheritedNetworks, ...targetNetworks]));
126
+ console.log("[DockerAdapter] Inherited networks:", targetNetworks);
127
127
  }
128
128
  if ((_b = myInfo == null ? void 0 : myInfo.Config) == null ? void 0 : _b.Env) {
129
129
  const envDict = {};
@@ -141,6 +141,11 @@ class DockerAdapter {
141
141
  targetVolumes = Array.from(/* @__PURE__ */ new Set([...inheritedBinds, ...targetVolumes]));
142
142
  }
143
143
  } catch (e) {
144
+ console.error("[DockerAdapter] Failed to inherit container config:", e.message);
145
+ }
146
+ const hasLoggerBase = targetEnvVars.some((e) => e.startsWith("LOGGER_BASE_PATH="));
147
+ if (!hasLoggerBase) {
148
+ targetEnvVars.push(`LOGGER_BASE_PATH=/app/nocobase/storage/logs/${stack.name}`);
144
149
  }
145
150
  for (let i = 0; i < diff; i++) {
146
151
  const suffix = `${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
@@ -172,8 +177,9 @@ class DockerAdapter {
172
177
  }
173
178
  createOpts.HostConfig.SecurityOpt = ["no-new-privileges:true"];
174
179
  const container = await this.docker.createContainer(createOpts);
175
- if (!targetNetworkMode && targetNetworks.length > 1) {
176
- for (let i2 = 1; i2 < targetNetworks.length; i2++) {
180
+ if (targetNetworks.length > 0) {
181
+ const startIndex = targetNetworkMode ? 0 : 1;
182
+ for (let i2 = startIndex; i2 < targetNetworks.length; i2++) {
177
183
  try {
178
184
  const net = this.docker.getNetwork(targetNetworks[i2]);
179
185
  await net.connect({ Container: container.id });
@@ -254,6 +260,13 @@ class DockerAdapter {
254
260
  });
255
261
  return this.demuxDockerLogs(logBuffer);
256
262
  }
263
+ async listNetworks() {
264
+ const networks = await this.docker.listNetworks();
265
+ return networks.map((n) => ({
266
+ id: n.Id,
267
+ name: n.Name
268
+ }));
269
+ }
257
270
  // ─── Private helpers ───
258
271
  mapState(state) {
259
272
  switch (state) {
@@ -76,4 +76,9 @@ export interface IOrchestratorAdapter {
76
76
  getStats(containerId: string): Promise<ContainerStats>;
77
77
  /** Get tail logs from a container */
78
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
+ }[]>;
79
84
  }
@@ -67,7 +67,7 @@ class PluginClusterManagerServer extends import_server.Plugin {
67
67
  orchestrator = null;
68
68
  leaderElection = null;
69
69
  async beforeLoad() {
70
- this.db.import({ directory: import_path.default.resolve(__dirname, "collections") });
70
+ await this.db.import({ directory: import_path.default.resolve(__dirname, "collections") });
71
71
  }
72
72
  async load() {
73
73
  this.nodeRegistry = new import_redis_node_registry.RedisNodeRegistry(this.app);
package/package.json CHANGED
@@ -3,7 +3,7 @@
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.0",
6
+ "version": "1.1.5",
7
7
  "license": "Apache-2.0",
8
8
  "main": "./dist/server/index.js",
9
9
  "keywords": ["Monitoring", "DevOps"],
@@ -146,6 +146,7 @@ function stackToFormValues(stack?: StackInfo | null) {
146
146
  k8sEnvFrom: jsonText(DEFAULT_K8S_ENV_FROM, []),
147
147
  k8sVolumeMounts: jsonText(DEFAULT_K8S_VOLUME_MOUNTS, []),
148
148
  k8sVolumes: jsonText(DEFAULT_K8S_VOLUMES, []),
149
+ networkMode: '',
149
150
  };
150
151
  }
151
152
 
@@ -196,6 +197,19 @@ export function ContainerOrchestrator() {
196
197
  });
197
198
  const [settingsForm] = Form.useForm();
198
199
  const [stackForm] = Form.useForm();
200
+ const [dockerNetworks, setDockerNetworks] = useState<{id: string, name: string}[]>([]);
201
+
202
+ // Fetch Docker networks
203
+ const fetchNetworks = useCallback(async () => {
204
+ try {
205
+ const res = await api.request({ url: '/workerOrchestrator:networks' });
206
+ let networks = res.data?.data || res.data;
207
+ if (!Array.isArray(networks)) networks = [];
208
+ setDockerNetworks(networks);
209
+ } catch {
210
+ setDockerNetworks([]);
211
+ }
212
+ }, [api]);
199
213
 
200
214
  // Ping the orchestrator adapter
201
215
  const fetchPing = useCallback(async () => {
@@ -268,6 +282,7 @@ export function ContainerOrchestrator() {
268
282
  const openStackModal = (stack?: StackInfo) => {
269
283
  setStackModal({ visible: true, stack: stack || null });
270
284
  stackForm.setFieldsValue(stackToFormValues(stack));
285
+ fetchNetworks();
271
286
  };
272
287
 
273
288
  const closeStackModal = () => {
@@ -774,18 +789,38 @@ export function ContainerOrchestrator() {
774
789
  </Col>
775
790
  </Row>
776
791
 
777
- {/* Row 4: Container name | Namespace */}
792
+ {/* Row 4: Container name | Namespace | Network Mode */}
778
793
  <Row gutter={12}>
779
- <Col span={12}>
794
+ <Col span={8}>
780
795
  <Form.Item name="k8sContainerName" label={t('Container name')}>
781
796
  <Input placeholder="worker" />
782
797
  </Form.Item>
783
798
  </Col>
784
- <Col span={12}>
799
+ <Col span={8}>
785
800
  <Form.Item name="namespace" label={t('Namespace')}>
786
801
  <Input placeholder="nocobase" />
787
802
  </Form.Item>
788
803
  </Col>
804
+ <Col span={8}>
805
+ <Form.Item noStyle shouldUpdate={(prev, curr) => prev.adapter !== curr.adapter}>
806
+ {() => {
807
+ if (stackForm.getFieldValue('adapter') === 'docker') {
808
+ return (
809
+ <Form.Item name="networkMode" label={t('Docker Network')} extra={t('Main network. Workers also inherit app networks.')}>
810
+ <Select allowClear placeholder={t('Default (bridge)')}>
811
+ <Select.Option value="bridge">bridge</Select.Option>
812
+ <Select.Option value="host">host</Select.Option>
813
+ {(Array.isArray(dockerNetworks) ? dockerNetworks : []).map(n => (
814
+ <Select.Option key={n.id} value={n.name}>{n.name}</Select.Option>
815
+ ))}
816
+ </Select>
817
+ </Form.Item>
818
+ );
819
+ }
820
+ return null;
821
+ }}
822
+ </Form.Item>
823
+ </Col>
789
824
  </Row>
790
825
 
791
826
  <Form.Item name="command" label={t('Command')}>
@@ -251,4 +251,24 @@ export const orchestratorActions = {
251
251
  }
252
252
  await next();
253
253
  },
254
+
255
+ /**
256
+ * GET /workerOrchestrator:networks
257
+ * List available networks (if supported by adapter)
258
+ */
259
+ async networks(ctx: Context, next: () => Promise<void>) {
260
+ const adapter = getAdapter(ctx);
261
+ if (!adapter.listNetworks) {
262
+ ctx.body = { data: [] };
263
+ } else {
264
+ try {
265
+ const networks = await adapter.listNetworks();
266
+ ctx.body = { data: networks };
267
+ } catch (err: any) {
268
+ ctx.app.logger.warn(`Failed to list networks: ${err.message}`);
269
+ ctx.body = { data: [] };
270
+ }
271
+ }
272
+ await next();
273
+ },
254
274
  };
@@ -0,0 +1,7 @@
1
+ export default {
2
+ name: 'app',
3
+ autoCreate: false,
4
+ dumpRules: 'skip',
5
+ model: 'Collection',
6
+ fields: [],
7
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerAclCache" not found'
5
+ *
6
+ * Since 'clusterManagerAclCache' 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
+ export default {
12
+ name: 'clusterManagerAclCache',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerCacheMgr" not found'
5
+ *
6
+ * Since 'clusterManagerCacheMgr' 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
+ export default {
12
+ name: 'clusterManagerCacheMgr',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerCluster" not found'
5
+ *
6
+ * Since 'clusterManagerCluster' 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
+ export default {
12
+ name: 'clusterManagerCluster',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerLock" not found'
5
+ *
6
+ * Since 'clusterManagerLock' 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
+ export default {
12
+ name: 'clusterManagerLock',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerQueue" not found'
5
+ *
6
+ * Since 'clusterManagerQueue' 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
+ export default {
12
+ name: 'clusterManagerQueue',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerRedis" not found'
5
+ *
6
+ * Since 'clusterManagerRedis' 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
+ export default {
12
+ name: 'clusterManagerRedis',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManagerWorkflow" not found'
5
+ *
6
+ * Since 'clusterManagerWorkflow' 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
+ export default {
12
+ name: 'clusterManagerWorkflow',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "clusterManager" not found'
5
+ *
6
+ * Since 'clusterManager' 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
+ export default {
12
+ name: 'clusterManager',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
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
+ export default {
12
+ name: 'workerOrchestrator',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -0,0 +1,23 @@
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
+ export default {
12
+ name: 'workerPackages',
13
+ dumpRules: 'skip',
14
+ autoGenId: true,
15
+ createdAt: false,
16
+ updatedAt: false,
17
+ fields: [
18
+ {
19
+ name: 'name',
20
+ type: 'string',
21
+ },
22
+ ],
23
+ };
@@ -116,11 +116,11 @@ export class DockerAdapter implements IOrchestratorAdapter {
116
116
  const myContainer = this.docker.getContainer(myContainerId);
117
117
  const myInfo = await myContainer.inspect();
118
118
 
119
- // Inherit Networks if none specified
120
- if (!targetNetworkMode && targetNetworks.length === 0) {
121
- if (myInfo?.NetworkSettings?.Networks) {
122
- targetNetworks = Object.keys(myInfo.NetworkSettings.Networks);
123
- }
119
+ // Always inherit Networks so worker can communicate with main app
120
+ if (myInfo?.NetworkSettings?.Networks) {
121
+ const inheritedNetworks = Object.keys(myInfo.NetworkSettings.Networks);
122
+ targetNetworks = Array.from(new Set([...inheritedNetworks, ...targetNetworks]));
123
+ console.log('[DockerAdapter] Inherited networks:', targetNetworks);
124
124
  }
125
125
 
126
126
  // Inherit Environment Variables and merge with stack.envVars
@@ -134,16 +134,23 @@ export class DockerAdapter implements IOrchestratorAdapter {
134
134
  });
135
135
  // Overwrite with explicitly defined env vars
136
136
  Object.assign(envDict, stack.envVars || {});
137
+
137
138
  targetEnvVars = Object.entries(envDict).map(([k, v]) => `${k}=${v}`);
138
139
  }
139
-
140
140
  // Inherit Volumes (Binds)
141
141
  if (myInfo?.HostConfig?.Binds) {
142
142
  const inheritedBinds = myInfo.HostConfig.Binds as string[];
143
143
  targetVolumes = Array.from(new Set([...inheritedBinds, ...targetVolumes]));
144
144
  }
145
- } catch (e) {
145
+ } catch (e: any) {
146
146
  // Ignore error if not running in a container or cannot inspect
147
+ console.error('[DockerAdapter] Failed to inherit container config:', e.message);
148
+ }
149
+
150
+ // Automatically separate logs for workers to prevent log interleaving with the main app
151
+ const hasLoggerBase = targetEnvVars.some(e => e.startsWith('LOGGER_BASE_PATH='));
152
+ if (!hasLoggerBase) {
153
+ targetEnvVars.push(`LOGGER_BASE_PATH=/app/nocobase/storage/logs/${stack.name}`);
147
154
  }
148
155
 
149
156
  for (let i = 0; i < diff; i++) {
@@ -185,8 +192,9 @@ export class DockerAdapter implements IOrchestratorAdapter {
185
192
  const container = await this.docker.createContainer(createOpts);
186
193
 
187
194
  // Connect to additional networks before starting
188
- if (!targetNetworkMode && targetNetworks.length > 1) {
189
- for (let i = 1; i < targetNetworks.length; i++) {
195
+ if (targetNetworks.length > 0) {
196
+ const startIndex = targetNetworkMode ? 0 : 1;
197
+ for (let i = startIndex; i < targetNetworks.length; i++) {
190
198
  try {
191
199
  const net = this.docker.getNetwork(targetNetworks[i]);
192
200
  await net.connect({ Container: container.id });
@@ -284,6 +292,14 @@ export class DockerAdapter implements IOrchestratorAdapter {
284
292
  return this.demuxDockerLogs(logBuffer);
285
293
  }
286
294
 
295
+ async listNetworks(): Promise<{ id: string; name: string }[]> {
296
+ const networks = await this.docker.listNetworks();
297
+ return networks.map((n: any) => ({
298
+ id: n.Id,
299
+ name: n.Name,
300
+ }));
301
+ }
302
+
287
303
  // ─── Private helpers ───
288
304
 
289
305
  private mapState(state: string): ContainerInfo['status'] {
@@ -92,4 +92,7 @@ export interface IOrchestratorAdapter {
92
92
 
93
93
  /** Get tail logs from a container */
94
94
  getLogs(containerId: string, tail?: number): Promise<string>;
95
+
96
+ /** List available networks (if supported by adapter) */
97
+ listNetworks?(): Promise<{ id: string; name: string }[]>;
95
98
  }
@@ -28,7 +28,7 @@ export class PluginClusterManagerServer extends Plugin {
28
28
  public leaderElection: LeaderElection | null = null;
29
29
 
30
30
  async beforeLoad() {
31
- this.db.import({ directory: path.resolve(__dirname, 'collections') });
31
+ await this.db.import({ directory: path.resolve(__dirname, 'collections') });
32
32
  }
33
33
 
34
34
  async load() {