@kapeta/local-cluster-service 0.15.2 → 0.16.0

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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/src/RepositoryWatcher.d.ts +22 -0
  3. package/dist/cjs/src/RepositoryWatcher.js +273 -0
  4. package/dist/cjs/src/assetManager.js +33 -20
  5. package/dist/cjs/src/containerManager.js +0 -1
  6. package/dist/cjs/src/identities/routes.js +2 -1
  7. package/dist/cjs/src/instanceManager.d.ts +0 -2
  8. package/dist/cjs/src/instanceManager.js +16 -35
  9. package/dist/cjs/src/progressListener.d.ts +5 -6
  10. package/dist/cjs/src/progressListener.js +54 -36
  11. package/dist/cjs/src/repositoryManager.d.ts +4 -4
  12. package/dist/cjs/src/repositoryManager.js +20 -93
  13. package/dist/cjs/src/socketManager.d.ts +18 -6
  14. package/dist/cjs/src/socketManager.js +35 -1
  15. package/dist/esm/src/RepositoryWatcher.d.ts +22 -0
  16. package/dist/esm/src/RepositoryWatcher.js +266 -0
  17. package/dist/esm/src/assetManager.js +35 -22
  18. package/dist/esm/src/containerManager.js +0 -1
  19. package/dist/esm/src/identities/routes.js +2 -1
  20. package/dist/esm/src/instanceManager.d.ts +0 -2
  21. package/dist/esm/src/instanceManager.js +16 -35
  22. package/dist/esm/src/progressListener.d.ts +5 -6
  23. package/dist/esm/src/progressListener.js +53 -36
  24. package/dist/esm/src/repositoryManager.d.ts +4 -4
  25. package/dist/esm/src/repositoryManager.js +20 -93
  26. package/dist/esm/src/socketManager.d.ts +18 -6
  27. package/dist/esm/src/socketManager.js +34 -0
  28. package/package.json +2 -2
  29. package/src/RepositoryWatcher.ts +304 -0
  30. package/src/assetManager.ts +39 -25
  31. package/src/containerManager.ts +0 -5
  32. package/src/identities/routes.ts +2 -1
  33. package/src/instanceManager.ts +22 -35
  34. package/src/progressListener.ts +59 -39
  35. package/src/repositoryManager.ts +26 -100
  36. package/src/socketManager.ts +44 -5
@@ -0,0 +1,304 @@
1
+ import chokidar, { FSWatcher } from 'chokidar';
2
+ import ClusterConfiguration, { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
3
+ import FS from 'fs-extra';
4
+ import Path from 'node:path';
5
+ import YAML from 'yaml';
6
+ import { parseKapetaUri } from '@kapeta/nodejs-utils';
7
+ import _ from 'lodash';
8
+ import { socketManager } from './socketManager';
9
+ import { definitionsManager } from './definitionsManager';
10
+ import { assetManager } from './assetManager';
11
+
12
+ function clearAllCaches() {
13
+ definitionsManager.clearCache();
14
+ assetManager.clearCache();
15
+ }
16
+
17
+ type WatchEventName = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
18
+ interface AssetIdentity {
19
+ handle: string;
20
+ name: string;
21
+ version: string;
22
+ }
23
+ const KAPETA_YML_RX = /^kapeta.ya?ml$/;
24
+ export class RepositoryWatcher {
25
+ private watcher?: FSWatcher;
26
+ private disabled: boolean = false;
27
+ private readonly baseDir: string;
28
+ private allDefinitions: DefinitionInfo[] = [];
29
+ private symbolicLinks: { [link: string]: string } = {};
30
+ private ignoredFiles: Set<string> = new Set<string>();
31
+ constructor() {
32
+ this.baseDir = ClusterConfiguration.getRepositoryBasedir();
33
+ }
34
+
35
+ setDisabled(disabled: boolean) {
36
+ this.disabled = disabled;
37
+ }
38
+ public watch() {
39
+ if (!FS.existsSync(this.baseDir)) {
40
+ FS.mkdirpSync(this.baseDir);
41
+ }
42
+
43
+ this.allDefinitions = ClusterConfiguration.getDefinitions();
44
+
45
+ try {
46
+ this.watcher = chokidar.watch(this.baseDir, {
47
+ followSymlinks: false,
48
+ ignorePermissionErrors: true,
49
+ disableGlobbing: true,
50
+ persistent: true,
51
+ depth: 2,
52
+ ignored: (path) => this.ignoreFile(path),
53
+ });
54
+ this.watcher.on('all', this.handleFileChange.bind(this));
55
+ this.watcher.on('error', (error) => {
56
+ console.log('Error watching repository', error);
57
+ });
58
+ this.watcher.on('ready', () => {
59
+ console.log('Watching local repository for provider changes: %s', this.baseDir);
60
+ });
61
+ } catch (e) {
62
+ // Fallback to run without watch mode due to potential platform issues.
63
+ // https://nodejs.org/docs/latest/api/fs.html#caveats
64
+ console.log('Unable to watch for changes. Changes to assets will not update automatically.', e);
65
+ return;
66
+ }
67
+ }
68
+
69
+ async ignoreChangesFor(file: string) {
70
+ this.ignoredFiles.add(file);
71
+ const realPath = await FS.realpath(file);
72
+ if (realPath !== file) {
73
+ this.ignoredFiles.add(realPath);
74
+ }
75
+ }
76
+
77
+ async resumeChangedFor(file: string) {
78
+ this.ignoredFiles.delete(file);
79
+ const realPath = await FS.realpath(file);
80
+ if (realPath !== file) {
81
+ this.ignoredFiles.delete(realPath);
82
+ }
83
+ }
84
+
85
+ public async unwatch() {
86
+ if (!this.watcher) {
87
+ return;
88
+ }
89
+ this.symbolicLinks = {};
90
+ await this.watcher.close();
91
+ this.watcher = undefined;
92
+ }
93
+
94
+ private async getAssetIdentity(path: string): Promise<AssetIdentity | undefined> {
95
+ const baseName = Path.basename(path);
96
+ let handle, name, version;
97
+ if (path.startsWith(this.baseDir)) {
98
+ const relativePath = Path.relative(this.baseDir, path);
99
+ // Inside the repo we can use the path to determine the handle, name and version
100
+ [handle, name, version] = relativePath.split(/\//g);
101
+ if (!handle || !name || !version) {
102
+ // Do nothing with this
103
+ return;
104
+ }
105
+
106
+ return {
107
+ handle,
108
+ name,
109
+ version,
110
+ };
111
+ }
112
+
113
+ if (!KAPETA_YML_RX.test(baseName)) {
114
+ // Do nothing with this
115
+ return;
116
+ }
117
+ // Outside the repo we need to use the file content to determine the handle, name
118
+ // Version is always 'local'
119
+ version = 'local';
120
+
121
+ try {
122
+ const definition: Definition = YAML.parse((await FS.readFile(path)).toString());
123
+ const uri = parseKapetaUri(definition.metadata.name);
124
+ handle = uri.handle;
125
+ name = uri.name;
126
+ return {
127
+ handle,
128
+ name,
129
+ version,
130
+ };
131
+ } catch (e) {
132
+ // Ignore issues in the YML file
133
+ return;
134
+ }
135
+ }
136
+
137
+ private async handleFileChange(eventName: WatchEventName, path: string) {
138
+ if (!path) {
139
+ return;
140
+ }
141
+
142
+ if (this.ignoredFiles.has(path)) {
143
+ return;
144
+ }
145
+
146
+ //console.log('File changed', eventName, path);
147
+
148
+ const assetIdentity = await this.getAssetIdentity(path);
149
+ if (!assetIdentity) {
150
+ return;
151
+ }
152
+
153
+ if (this.disabled) {
154
+ return;
155
+ }
156
+
157
+ // If this is false it's because we're watching a symlink target
158
+ const withinRepo = path.startsWith(this.baseDir);
159
+ if (withinRepo && assetIdentity.version === 'local' && path.endsWith('/local')) {
160
+ // This is likely a symlink target
161
+ if (eventName === 'add') {
162
+ //console.log('Checking if we should add symlink target', handle, name, version, path);
163
+ await this.addSymlinkTarget(path);
164
+ }
165
+
166
+ if (eventName === 'unlink') {
167
+ await this.removeSymlinkTarget(path);
168
+ }
169
+
170
+ if (eventName === 'change') {
171
+ await this.updateSymlinkTarget(path);
172
+ }
173
+ }
174
+
175
+ await this.checkForChange(assetIdentity);
176
+ }
177
+
178
+ private async checkForChange(assetIdentity: AssetIdentity) {
179
+ const ymlPath = Path.join(
180
+ this.baseDir,
181
+ assetIdentity.handle,
182
+ assetIdentity.name,
183
+ assetIdentity.version,
184
+ 'kapeta.yml'
185
+ );
186
+ const newDefinitions = ClusterConfiguration.getDefinitions();
187
+ const newDefinition = newDefinitions.find((d) => d.ymlPath === ymlPath);
188
+ let currentDefinition = this.allDefinitions.find((d) => d.ymlPath === ymlPath);
189
+ const ymlExists = await this.exists(ymlPath);
190
+ let type;
191
+ if (ymlExists) {
192
+ if (currentDefinition) {
193
+ if (newDefinition && _.isEqual(currentDefinition, newDefinition)) {
194
+ //Definition was not changed
195
+ return;
196
+ }
197
+ type = 'updated';
198
+ } else if (newDefinition) {
199
+ type = 'added';
200
+ currentDefinition = newDefinition;
201
+ } else {
202
+ //Other definition was added / updated - ignore
203
+ return;
204
+ }
205
+ } else {
206
+ if (currentDefinition) {
207
+ const ref = parseKapetaUri(
208
+ `${currentDefinition.definition.metadata.name}:${currentDefinition.version}`
209
+ ).id;
210
+ //Something was removed
211
+ type = 'removed';
212
+ } else {
213
+ //Other definition was removed - ignore
214
+ return;
215
+ }
216
+ }
217
+
218
+ const payload = {
219
+ type,
220
+ definition: newDefinition?.definition ?? currentDefinition?.definition,
221
+ asset: assetIdentity,
222
+ };
223
+
224
+ this.allDefinitions = newDefinitions;
225
+
226
+ //console.log('Asset changed', payload);
227
+ socketManager.emitGlobal('asset-change', payload);
228
+ clearAllCaches();
229
+ }
230
+
231
+ private async exists(path: string): Promise<boolean> {
232
+ try {
233
+ await FS.access(path);
234
+ return true;
235
+ } catch (e) {
236
+ return false;
237
+ }
238
+ }
239
+ private async removeSymlinkTarget(path: string) {
240
+ if (this.symbolicLinks[path]) {
241
+ //console.log('Unwatching symlink target %s => %s', path, this.symbolicLinks[path]);
242
+ this.watcher?.unwatch(this.symbolicLinks[path]);
243
+ delete this.symbolicLinks[path];
244
+ }
245
+ }
246
+
247
+ private async updateSymlinkTarget(path: string) {
248
+ if (this.symbolicLinks[path]) {
249
+ //console.log('Updating symlink target %s => %s', path, this.symbolicLinks[path]);
250
+ this.watcher?.unwatch(this.symbolicLinks[path]);
251
+ delete this.symbolicLinks[path];
252
+ await this.addSymlinkTarget(path);
253
+ }
254
+ }
255
+
256
+ private async addSymlinkTarget(path: string) {
257
+ try {
258
+ // Make sure we're not watching the symlink target
259
+ await this.removeSymlinkTarget(path);
260
+ const stat = await FS.lstat(path);
261
+ if (stat.isSymbolicLink()) {
262
+ const realPath = `${await FS.realpath(path)}/kapeta.yml`;
263
+ if (await this.exists(realPath)) {
264
+ //console.log('Watching symlink target %s => %s', path, realPath);
265
+ this.watcher?.add(realPath);
266
+ this.symbolicLinks[path] = realPath;
267
+ }
268
+ }
269
+ } catch (e) {
270
+ // Ignore
271
+ console.warn('Failed to check local symlink target', e);
272
+ }
273
+ }
274
+
275
+ private ignoreFile(path: string) {
276
+ if (!path.startsWith(this.baseDir)) {
277
+ return false;
278
+ }
279
+ if (path.includes('/node_modules/')) {
280
+ return true;
281
+ }
282
+
283
+ const filename = Path.basename(path);
284
+ if (filename.startsWith('.')) {
285
+ return true;
286
+ }
287
+
288
+ const relativePath = Path.relative(this.baseDir, path).split(Path.sep);
289
+
290
+ try {
291
+ if (FS.statSync(path).isDirectory()) {
292
+ if (relativePath.length > 3) {
293
+ return true;
294
+ }
295
+ return false;
296
+ }
297
+ } catch (e) {
298
+ // Didn't exist - dont ignore
299
+ return false;
300
+ }
301
+
302
+ return !/^kapeta\.ya?ml$/.test(filename);
303
+ }
304
+ }
@@ -1,11 +1,10 @@
1
1
  import Path from 'node:path';
2
- import FS from 'node:fs';
3
- import FSExtra from 'fs-extra';
2
+ import FS from 'fs-extra';
4
3
  import YAML from 'yaml';
5
4
  import NodeCache from 'node-cache';
6
- import ClusterConfiguration, { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
5
+ import { Definition, DefinitionInfo } from '@kapeta/local-cluster-config';
7
6
  import { codeGeneratorManager } from './codeGeneratorManager';
8
- import { progressListener } from './progressListener';
7
+ import { ProgressListener } from './progressListener';
9
8
  import { parseKapetaUri } from '@kapeta/nodejs-utils';
10
9
  import { repositoryManager } from './repositoryManager';
11
10
  import { BlockDefinition } from '@kapeta/schemas';
@@ -14,6 +13,11 @@ import { definitionsManager } from './definitionsManager';
14
13
  import { normalizeKapetaUri } from './utils/utils';
15
14
  import { taskManager } from './taskManager';
16
15
 
16
+ function clearAllCaches() {
17
+ definitionsManager.clearCache();
18
+ assetManager.clearCache();
19
+ }
20
+
17
21
  export interface EnrichedAsset {
18
22
  ref: string;
19
23
  editable: boolean;
@@ -133,23 +137,19 @@ class AssetManager {
133
137
  }
134
138
 
135
139
  async createAsset(path: string, yaml: BlockDefinition): Promise<EnrichedAsset[]> {
136
- if (FS.existsSync(path)) {
140
+ if (await FS.pathExists(path)) {
137
141
  throw new Error('File already exists: ' + path);
138
142
  }
139
143
 
140
144
  const dirName = Path.dirname(path);
141
- if (!FS.existsSync(dirName)) {
142
- FSExtra.mkdirpSync(dirName);
145
+ if (!(await FS.pathExists(dirName))) {
146
+ await FS.mkdirp(dirName);
143
147
  }
144
148
 
145
- console.log('Wrote to ' + path);
146
- FS.writeFileSync(path, YAML.stringify(yaml));
147
-
149
+ await FS.writeFile(path, YAML.stringify(yaml));
148
150
  const asset = await this.importFile(path);
149
- console.log('Imported');
150
151
 
151
- this.cache.flushAll();
152
- definitionsManager.clearCache();
152
+ clearAllCaches();
153
153
 
154
154
  const ref = `kapeta://${yaml.metadata.name}:local`;
155
155
 
@@ -172,12 +172,21 @@ class AssetManager {
172
172
  throw new Error('Attempted to update corrupted asset: ' + ref);
173
173
  }
174
174
 
175
- console.log('Wrote to ' + asset.ymlPath);
176
- FS.writeFileSync(asset.ymlPath, YAML.stringify(yaml));
177
- this.cache.flushAll();
178
- definitionsManager.clearCache();
179
-
180
- this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
175
+ const path = asset.ymlPath;
176
+ try {
177
+ await repositoryManager.ignoreChangesFor(path);
178
+ await FS.writeFile(asset.ymlPath, YAML.stringify(yaml));
179
+ console.log('Wrote to ' + asset.ymlPath);
180
+ clearAllCaches();
181
+ this.maybeGenerateCode(asset.ref, asset.ymlPath, yaml);
182
+ } finally {
183
+ //We need to wait a bit for the disk to settle before we can resume watching
184
+ setTimeout(async () => {
185
+ try {
186
+ await repositoryManager.resumeChangedFor(path);
187
+ } catch (e) {}
188
+ }, 500);
189
+ }
181
190
  }
182
191
 
183
192
  private maybeGenerateCode(ref: string, ymlPath: string, block: BlockDefinition) {
@@ -203,17 +212,20 @@ class AssetManager {
203
212
  filePath = filePath.substring('file://'.length);
204
213
  }
205
214
 
206
- if (!FS.existsSync(filePath)) {
215
+ if (!(await FS.pathExists(filePath))) {
207
216
  throw new Error('File not found: ' + filePath);
208
217
  }
218
+ const content = await FS.readFile(filePath);
209
219
 
210
- const assetInfos = YAML.parseAllDocuments(FS.readFileSync(filePath).toString()).map((doc) => doc.toJSON());
220
+ const assetInfos = YAML.parseAllDocuments(content.toString()).map((doc) => doc.toJSON());
211
221
 
212
- await Actions.link(progressListener, Path.dirname(filePath));
222
+ await Actions.link(new ProgressListener(), Path.dirname(filePath));
213
223
 
214
224
  const version = 'local';
215
225
  const refs = assetInfos.map((assetInfo) => `kapeta://${assetInfo.metadata.name}:${version}`);
216
- this.cache.flushAll();
226
+
227
+ clearAllCaches();
228
+
217
229
  return this.getAssets().filter((a) => refs.some((ref) => compareRefs(ref, a.ref)));
218
230
  }
219
231
 
@@ -222,8 +234,10 @@ class AssetManager {
222
234
  if (!asset) {
223
235
  throw new Error('Asset does not exists: ' + ref);
224
236
  }
225
- this.cache.flushAll();
226
- await Actions.uninstall(progressListener, [asset.ref]);
237
+
238
+ clearAllCaches();
239
+
240
+ await Actions.uninstall(new ProgressListener(), [asset.ref]);
227
241
  }
228
242
 
229
243
  async installAsset(ref: string) {
@@ -11,14 +11,9 @@ import uuid from 'node-uuid';
11
11
  import md5 from 'md5';
12
12
  import { getBlockInstanceContainerName } from './utils/utils';
13
13
  import { InstanceInfo, LogEntry, LogSource } from './types';
14
- import { socketManager } from './socketManager';
15
- import { handlers as ArtifactHandlers } from '@kapeta/nodejs-registry-utils';
16
- import { progressListener } from './progressListener';
17
14
  import { KapetaAPI } from '@kapeta/nodejs-api-client';
18
15
  import { taskManager, Task } from './taskManager';
19
16
 
20
- const EVENT_IMAGE_PULL = 'docker-image-pull';
21
-
22
17
  type StringMap = { [key: string]: string };
23
18
 
24
19
  export type PortMap = {
@@ -5,12 +5,12 @@ import { corsHandler } from '../middleware/cors';
5
5
  import { Request, Response } from 'express';
6
6
 
7
7
  const router = Router();
8
- const api = new KapetaAPI();
9
8
 
10
9
  router.use('/', corsHandler);
11
10
 
12
11
  router.get('/current', async (req: Request, res: Response) => {
13
12
  try {
13
+ const api = new KapetaAPI();
14
14
  if (api.hasToken()) {
15
15
  res.send(await api.getCurrentIdentity());
16
16
  } else {
@@ -23,6 +23,7 @@ router.get('/current', async (req: Request, res: Response) => {
23
23
 
24
24
  router.get('/:identityId/memberships', async (req: Request, res: Response) => {
25
25
  try {
26
+ const api = new KapetaAPI();
26
27
  if (api.hasToken()) {
27
28
  res.send(await api.getMemberships(req.params.identityId));
28
29
  } else {
@@ -3,7 +3,13 @@ import request from 'request';
3
3
  import AsyncLock from 'async-lock';
4
4
  import { BlockInstanceRunner } from './utils/BlockInstanceRunner';
5
5
  import { storageService } from './storageService';
6
- import { socketManager } from './socketManager';
6
+ import {
7
+ EVENT_INSTANCE_CREATED,
8
+ EVENT_INSTANCE_EXITED,
9
+ EVENT_INSTANCE_LOG,
10
+ EVENT_STATUS_CHANGED,
11
+ socketManager,
12
+ } from './socketManager';
7
13
  import { serviceManager } from './serviceManager';
8
14
  import { assetManager } from './assetManager';
9
15
  import { containerManager, HEALTH_CHECK_TIMEOUT } from './containerManager';
@@ -19,10 +25,6 @@ import { Task, taskManager } from './taskManager';
19
25
  const CHECK_INTERVAL = 5000;
20
26
  const DEFAULT_HEALTH_PORT_TYPE = 'rest';
21
27
 
22
- const EVENT_STATUS_CHANGED = 'status-changed';
23
- const EVENT_INSTANCE_CREATED = 'instance-created';
24
- const EVENT_INSTANCE_EXITED = 'instance-exited';
25
- const EVENT_INSTANCE_LOG = 'instance-log';
26
28
  const MIN_TIME_RUNNING = 30000; //If something didnt run for more than 30 secs - it failed
27
29
 
28
30
  export class InstanceManager {
@@ -72,8 +74,11 @@ export class InstanceManager {
72
74
  }
73
75
 
74
76
  const plan = planInfo.definition as Plan;
77
+ if (!plan?.spec?.blocks) {
78
+ return [];
79
+ }
75
80
 
76
- const instanceIds = plan.spec.blocks.map((block) => block.id);
81
+ const instanceIds = plan.spec?.blocks?.map((block) => block.id) || [];
77
82
 
78
83
  return this._instances.filter(
79
84
  (instance) => instance.systemId === systemId && instanceIds.includes(instance.instanceId)
@@ -153,10 +158,10 @@ export class InstanceManager {
153
158
  if (existingInstance) {
154
159
  const ix = this._instances.indexOf(existingInstance);
155
160
  this._instances.splice(ix, 1, instance);
156
- this.emitSystemEvent(instance.systemId, EVENT_STATUS_CHANGED, instance);
161
+ socketManager.emitSystemEvent(instance.systemId, EVENT_STATUS_CHANGED, instance);
157
162
  } else {
158
163
  this._instances.push(instance);
159
- this.emitSystemEvent(instance.systemId, EVENT_INSTANCE_CREATED, instance);
164
+ socketManager.emitSystemEvent(instance.systemId, EVENT_INSTANCE_CREATED, instance);
160
165
  }
161
166
 
162
167
  this.save();
@@ -222,7 +227,7 @@ export class InstanceManager {
222
227
  instance.health = healthUrl;
223
228
  }
224
229
 
225
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
230
+ socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
226
231
  } else {
227
232
  //If instance was not found - then we're receiving an externally started instance
228
233
  instance = {
@@ -239,7 +244,7 @@ export class InstanceManager {
239
244
 
240
245
  this._instances.push(instance);
241
246
 
242
- this.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
247
+ socketManager.emitSystemEvent(systemId, EVENT_INSTANCE_CREATED, instance);
243
248
  }
244
249
 
245
250
  this.save();
@@ -268,7 +273,7 @@ export class InstanceManager {
268
273
  instance.status = InstanceStatus.STOPPED;
269
274
  instance.pid = null;
270
275
  instance.health = null;
271
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
276
+ socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
272
277
  this.save();
273
278
  }
274
279
  });
@@ -343,7 +348,7 @@ export class InstanceManager {
343
348
 
344
349
  instance.status = InstanceStatus.STOPPING;
345
350
 
346
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
351
+ socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
347
352
  console.log('Stopping instance: %s::%s [desired: %s]', systemId, instanceId, instance.desiredStatus);
348
353
  this.save();
349
354
 
@@ -355,7 +360,7 @@ export class InstanceManager {
355
360
  try {
356
361
  await container.stop();
357
362
  instance.status = InstanceStatus.STOPPED;
358
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
363
+ socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
359
364
  this.save();
360
365
  } catch (e) {
361
366
  console.error('Failed to stop container', e);
@@ -374,7 +379,7 @@ export class InstanceManager {
374
379
 
375
380
  process.kill(instance.pid as number, 'SIGTERM');
376
381
  instance.status = InstanceStatus.STOPPED;
377
- this.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
382
+ socketManager.emitSystemEvent(systemId, EVENT_STATUS_CHANGED, instance);
378
383
  this.save();
379
384
  } catch (e) {
380
385
  console.error('Failed to stop process', e);
@@ -528,9 +533,9 @@ export class InstanceManager {
528
533
  errorMessage: e.message ?? 'Failed to start - Check logs for details.',
529
534
  });
530
535
 
531
- this.emitInstanceEvent(systemId, instanceId, EVENT_INSTANCE_LOG, logs[0]);
536
+ socketManager.emitInstanceLog(systemId, instanceId, logs[0]);
532
537
 
533
- this.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
538
+ socketManager.emitInstanceEvent(systemId, blockInstance.id, EVENT_INSTANCE_EXITED, {
534
539
  error: `Failed to start instance: ${e.message}`,
535
540
  status: EVENT_INSTANCE_EXITED,
536
541
  instanceId: blockInstance.id,
@@ -645,7 +650,7 @@ export class InstanceManager {
645
650
  oldStatus,
646
651
  instance.status
647
652
  );
648
- this.emitSystemEvent(instance.systemId, EVENT_STATUS_CHANGED, instance);
653
+ socketManager.emitSystemEvent(instance.systemId, EVENT_STATUS_CHANGED, instance);
649
654
  changed = true;
650
655
  }
651
656
  }
@@ -802,24 +807,6 @@ export class InstanceManager {
802
807
  });
803
808
  });
804
809
  }
805
-
806
- private emitSystemEvent(systemId: string, type: string, payload: any) {
807
- systemId = normalizeKapetaUri(systemId);
808
- try {
809
- socketManager.emit(`${systemId}/instances`, type, payload);
810
- } catch (e: any) {
811
- console.warn('Failed to emit instance event: %s', e.message);
812
- }
813
- }
814
-
815
- private emitInstanceEvent(systemId: string, instanceId: string, type: string, payload: any) {
816
- systemId = normalizeKapetaUri(systemId);
817
- try {
818
- socketManager.emit(`${systemId}/instances/${instanceId}`, type, payload);
819
- } catch (e: any) {
820
- console.warn('Failed to emit instance event: %s', e.message);
821
- }
822
- }
823
810
  }
824
811
 
825
812
  export const instanceManager = new InstanceManager();