@xen-orchestra/rest-api 0.14.0 → 0.15.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.
@@ -0,0 +1,10 @@
1
+ export class ApiError extends Error {
2
+ #status;
3
+ constructor(message, status) {
4
+ super(message);
5
+ this.#status = status;
6
+ }
7
+ get status() {
8
+ return this.#status;
9
+ }
10
+ }
@@ -14,15 +14,18 @@ import { provide } from 'inversify-binding-decorators';
14
14
  import { AlarmService } from '../alarms/alarm.service.mjs';
15
15
  import { escapeUnsafeComplexMatcher } from '../helpers/utils.helper.mjs';
16
16
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
17
- import { host, hostIds, hostSmt, hostStats, partialHosts } from '../open-api/oa-examples/host.oa-example.mjs';
17
+ import { host, hostIds, hostSmt, hostMissingPatches, hostStats, partialHosts, } from '../open-api/oa-examples/host.oa-example.mjs';
18
18
  import { RestApi } from '../rest-api/rest-api.mjs';
19
19
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
20
- import { internalServerErrorResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
20
+ import { featureUnauthorized, internalServerErrorResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
21
+ import { HostService } from './host.service.mjs';
21
22
  let HostController = class HostController extends XapiXoController {
22
23
  #alarmService;
23
- constructor(restApi, alarmService) {
24
+ #hostService;
25
+ constructor(restApi, alarmService, hostService) {
24
26
  super('host', restApi);
25
27
  this.#alarmService = alarmService;
28
+ this.#hostService = hostService;
26
29
  }
27
30
  /**
28
31
  * @example fields "id,name_label,productBrand"
@@ -105,6 +108,15 @@ let HostController = class HostController extends XapiXoController {
105
108
  const enabled = Boolean(await xapiHost.$xapi.isHyperThreadingEnabled(hostId));
106
109
  return { enabled };
107
110
  }
111
+ /**
112
+ * Host must be running
113
+ *
114
+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
115
+ */
116
+ async getMissingPatches(id) {
117
+ const { missingPatches } = await this.#hostService.getMissingPatchesInfo({ filter: host => host.id === id });
118
+ return missingPatches;
119
+ }
108
120
  };
109
121
  __decorate([
110
122
  Example(hostIds),
@@ -166,6 +178,13 @@ __decorate([
166
178
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
167
179
  __param(0, Path())
168
180
  ], HostController.prototype, "gethostSmt", null);
181
+ __decorate([
182
+ Example(hostMissingPatches),
183
+ Get('{id}/missing_patches'),
184
+ Response(notFoundResp.status, notFoundResp.description),
185
+ Response(featureUnauthorized.status, featureUnauthorized.description),
186
+ __param(0, Path())
187
+ ], HostController.prototype, "getMissingPatches", null);
169
188
  HostController = __decorate([
170
189
  Route('hosts'),
171
190
  Security('*'),
@@ -173,6 +192,7 @@ HostController = __decorate([
173
192
  Tags('hosts'),
174
193
  provide(HostController),
175
194
  __param(0, inject(RestApi)),
176
- __param(1, inject(AlarmService))
195
+ __param(1, inject(AlarmService)),
196
+ __param(2, inject(HostService))
177
197
  ], HostController);
178
198
  export { HostController };
@@ -41,13 +41,12 @@ export class HostService {
41
41
  total,
42
42
  };
43
43
  }
44
- async getMissingPatchesInfo(opts) {
45
- if (!(await this.#restApi.xoApp.hasFeatureAuthorization('LIST_MISSING_PATCHES'))) {
46
- return {
47
- hasAuthorization: false,
48
- };
49
- }
50
- const hosts = Object.values(this.#restApi.getObjectsByType('host', opts));
44
+ /**
45
+ * Throw if no authorization
46
+ */
47
+ async getMissingPatchesInfo({ filter, } = {}) {
48
+ await this.#restApi.xoApp.checkFeatureAuthorization('LIST_MISSING_PATCHES');
49
+ const hosts = Object.values(this.#restApi.getObjectsByType('host', { filter }));
51
50
  const missingPatches = new Map();
52
51
  const poolsWithMissingPatches = new Set();
53
52
  let nHostsWithMissingPatches = 0;
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.mjs CHANGED
@@ -32,6 +32,10 @@ const SWAGGER_UI_OPTIONS = {
32
32
  export default function setupRestApi(express, xoApp) {
33
33
  setupContainer(xoApp);
34
34
  RegisterRoutes(express);
35
+ express.get(`${BASE_URL}/docs/swagger.json`, (_req, res) => {
36
+ res.setHeader('Content-Type', 'application/json');
37
+ res.json(swaggerOpenApiSpec);
38
+ });
35
39
  // do not register the doc at the root level, or it may lead to unwanted behaviour
36
40
  // uncomment when all endpoints are migrated to this API
37
41
  // express.get('/rest/v0', (_req, res) => res.redirect('/rest/v0/docs'))
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from '@xen-orchestra/log';
2
2
  import { featureUnauthorized, forbiddenOperation, incorrectState, invalidCredentials, invalidParameters, noSuchObject, notImplemented, objectAlreadyExists, unauthorized, } from 'xo-common/api-errors.js';
3
+ import { ApiError } from '../helpers/error.helper.mjs';
3
4
  const log = createLogger('xo:rest-api:error-handler');
4
5
  // must have 4 parameters to be recognized as an error middleware by express
5
6
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -14,7 +15,10 @@ export default function genericErrorHandler(error, req, res, _next) {
14
15
  data: 'data' in error ? error.data : undefined,
15
16
  };
16
17
  let statusCode;
17
- if (noSuchObject.is(error)) {
18
+ if (error instanceof ApiError) {
19
+ statusCode = error.status;
20
+ }
21
+ else if (noSuchObject.is(error)) {
18
22
  statusCode = 404;
19
23
  }
20
24
  else if (unauthorized.is(error) || forbiddenOperation.is(error)) {
@@ -39,3 +39,7 @@ export const forbiddenOperationResp = {
39
39
  status: 403,
40
40
  description: 'Forbidden',
41
41
  };
42
+ export const badRequestResp = {
43
+ status: 400,
44
+ description: 'Bad request',
45
+ };
@@ -776,3 +776,33 @@ export const hostStats = {
776
776
  },
777
777
  };
778
778
  export const hostSmt = { enabled: true };
779
+ export const hostMissingPatches = [
780
+ {
781
+ url: 'http://www.samba.org/',
782
+ version: '4.10.16',
783
+ name: 'libsmbclient',
784
+ license: 'GPLv3+ and LGPLv3+',
785
+ changelog: {
786
+ date: 1690286400,
787
+ description: '- resolves: #2222250 - Fix netlogon capabilities level 2',
788
+ author: 'Andreas Schneider <asn@redhat.com> - 4.10.16-25',
789
+ },
790
+ release: '25.el7_9',
791
+ size: 149400,
792
+ description: 'The SMB client library',
793
+ },
794
+ {
795
+ url: 'http://www.openssh.com/portable.html',
796
+ version: '7.4p1',
797
+ name: 'openssh',
798
+ license: 'BSD',
799
+ changelog: {
800
+ date: 1742212800,
801
+ description: '- Fix CVE-2025-26465 - Fix cases where error codes were not correctly set',
802
+ author: 'Lucas Ravagnier <lucas.ravagnier@vates.tech> - 7.4p1-23.3.2 + 0.10.3-2.23.3.2',
803
+ },
804
+ release: '23.3.2.xcpng8.2',
805
+ size: 429044,
806
+ description: 'An open source implementation of SSH protocol versions 1 and 2',
807
+ },
808
+ ];
@@ -473,3 +473,33 @@ export const poolDashboard = {
473
473
  percent: 8.333333333333334,
474
474
  },
475
475
  };
476
+ export const poolMissingPatches = [
477
+ {
478
+ url: 'http://www.samba.org/',
479
+ version: '4.10.16',
480
+ name: 'libsmbclient',
481
+ license: 'GPLv3+ and LGPLv3+',
482
+ changelog: {
483
+ date: 1690286400,
484
+ description: '- resolves: #2222250 - Fix netlogon capabilities level 2',
485
+ author: 'Andreas Schneider <asn@redhat.com> - 4.10.16-25',
486
+ },
487
+ release: '25.el7_9',
488
+ size: 149400,
489
+ description: 'The SMB client library',
490
+ },
491
+ {
492
+ url: 'http://www.openssh.com/portable.html',
493
+ version: '7.4p1',
494
+ name: 'openssh',
495
+ license: 'BSD',
496
+ changelog: {
497
+ date: 1742212800,
498
+ description: '- Fix CVE-2025-26465 - Fix cases where error codes were not correctly set',
499
+ author: 'Lucas Ravagnier <lucas.ravagnier@vates.tech> - 7.4p1-23.3.2 + 0.10.3-2.23.3.2',
500
+ },
501
+ release: '23.3.2.xcpng8.2',
502
+ size: 429044,
503
+ description: 'An open source implementation of SSH protocol versions 1 and 2',
504
+ },
505
+ ];
@@ -1 +1,47 @@
1
1
  export const taskLocation = '/rest/v0/tasks/0m7kl0j9l';
2
+ export const taskIds = ['/rest/v0/tasks/0mdd1basu', '/rest/v0/tasks/0mdd1t24g'];
3
+ export const partialTasks = [
4
+ {
5
+ status: 'failure',
6
+ id: '0mdd1basu',
7
+ properties: {
8
+ method: 'xoa.licenses.getSelf',
9
+ params: {},
10
+ name: 'API call: xoa.licenses.getSelf',
11
+ userId: 'e531b8c9-3876-4ed9-8fd2-0476d5f825c9',
12
+ type: 'api.call',
13
+ },
14
+ href: '/rest/v0/tasks/0mdd1basu',
15
+ },
16
+ {
17
+ status: 'failure',
18
+ id: '0mdd1t24g',
19
+ properties: {
20
+ method: 'xoa.licenses.getSelf',
21
+ params: {},
22
+ name: 'API call: xoa.licenses.getSelf',
23
+ userId: 'e531b8c9-3876-4ed9-8fd2-0476d5f825c9',
24
+ type: 'api.call',
25
+ },
26
+ href: '/rest/v0/tasks/0mdd1t24g',
27
+ },
28
+ ];
29
+ export const task = {
30
+ id: '0mdd1basu',
31
+ properties: {
32
+ method: 'xoa.licenses.getSelf',
33
+ params: {},
34
+ name: 'API call: xoa.licenses.getSelf',
35
+ userId: 'e531b8c9-3876-4ed9-8fd2-0476d5f825c9',
36
+ type: 'api.call',
37
+ },
38
+ start: 1753098047598,
39
+ status: 'failure',
40
+ updatedAt: 1753098047696,
41
+ end: 1753098047600,
42
+ result: {
43
+ message: 'invalid status closed, expected open',
44
+ name: 'ConnectionError',
45
+ stack: 'ConnectionError: invalid status closed, expected open\n at JsonRpcWebSocketClient._assertStatus (/home/debian/xoa/node_modules/jsonrpc-websocket-client/src/websocket-client.js:141:13)\n at JsonRpcWebSocketClient.send (/home/debian/xoa/node_modules/jsonrpc-websocket-client/src/websocket-client.js:128:10)\n at Peer.<anonymous> (/home/debian/xoa/node_modules/jsonrpc-websocket-client/src/index.js:47:12)\n at Peer.emit (node:events:518:28)\n at Peer.emit (/home/debian/xen-orchestra/@xen-orchestra/log/configure.js:52:17)\n at Peer.push (/home/debian/xoa/node_modules/json-rpc-peer/src/index.js:196:52)\n at /home/debian/xoa/node_modules/json-rpc-peer/src/index.js:142:12\n at Promise._execute (/home/debian/xen-orchestra/node_modules/bluebird/js/release/debuggability.js:384:9)\n at Promise._resolveFromExecutor (/home/debian/xen-orchestra/node_modules/bluebird/js/release/promise.js:518:18)\n at new Promise (/home/debian/xen-orchestra/node_modules/bluebird/js/release/promise.js:103:10)\n at Peer.request (/home/debian/xoa/node_modules/json-rpc-peer/src/index.js:139:12)\n at JsonRpcWebSocketClient.call (/home/debian/xoa/node_modules/jsonrpc-websocket-client/src/index.js:63:23)\n at Xoa.apply [as _getSelfLicenses] (/home/debian/xoa/packages/xo-server-xoa/src/index.js:929:26)\n at Xo.call (file:///home/debian/xen-orchestra/packages/xo-server/src/xo-mixins/api.mjs:269:25)\n at file:///home/debian/xen-orchestra/packages/xo-server/src/xo-mixins/api.mjs:421:33\n at AsyncLocalStorage.run (node:internal/async_local_storage/async_hooks:91:14)\n at Task.runInside (/home/debian/xen-orchestra/@vates/task/index.js:175:41)\n at Task.run (/home/debian/xen-orchestra/@vates/task/index.js:159:31)\n at run (file:///home/debian/xen-orchestra/packages/xo-server/src/xo-mixins/api.mjs:421:16)\n at Api.#callApiMethod (file:///home/debian/xen-orchestra/packages/xo-server/src/xo-mixins/api.mjs:469:24)',
46
+ },
47
+ };
@@ -20,6 +20,8 @@ import { VbdController } from './../../vbds/vbd.controller.mjs';
20
20
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
21
21
  import { UserController } from './../../users/user.controller.mjs';
22
22
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
23
+ import { TaskController } from './../../tasks/task.controller.mjs';
24
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
23
25
  import { SrController } from './../../srs/sr.controller.mjs';
24
26
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
25
27
  import { SmController } from './../../sms/sm.controller.mjs';
@@ -384,6 +386,41 @@ const models = {
384
386
  "additionalProperties": false,
385
387
  },
386
388
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
389
+ "Record_string.unknown_": {
390
+ "dataType": "refAlias",
391
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": {}, "validators": {} },
392
+ },
393
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
394
+ "Branded_task_": {
395
+ "dataType": "refAlias",
396
+ "type": { "dataType": "intersection", "subSchemas": [{ "dataType": "string" }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "undefined": { "dataType": "enum", "enums": ["task"], "required": true } } }], "validators": {} },
397
+ },
398
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
399
+ "XoTask": {
400
+ "dataType": "refAlias",
401
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "warning": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "message": { "dataType": "string", "required": true }, "data": { "dataType": "any", "required": true } } } }, "updatedAt": { "dataType": "double" }, "tasks": { "dataType": "array", "array": { "dataType": "refAlias", "ref": "XoTask" } }, "status": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["failure"] }, { "dataType": "enum", "enums": ["interrupted"] }, { "dataType": "enum", "enums": ["pending"] }, { "dataType": "enum", "enums": ["success"] }], "required": true }, "start": { "dataType": "double", "required": true }, "result": { "ref": "Record_string.unknown_", "required": true }, "properties": { "dataType": "nestedObjectLiteral", "nestedProperties": { "userId": { "dataType": "string" }, "type": { "dataType": "string" }, "params": { "ref": "Record_string.unknown_" }, "objectId": { "dataType": "string" }, "name": { "dataType": "string" }, "method": { "dataType": "string" } }, "additionalProperties": { "dataType": "union", "subSchemas": [{ "dataType": "any" }, { "dataType": "undefined" }] }, "required": true }, "infos": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "message": { "dataType": "string", "required": true }, "data": { "dataType": "any", "required": true } } } }, "id": { "ref": "Branded_task_", "required": true }, "end": { "dataType": "double" }, "abortionRequestedAt": { "dataType": "double" } }, "validators": {} },
402
+ },
403
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
404
+ "Partial_Unbrand_XoTask__": {
405
+ "dataType": "refAlias",
406
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "abortionRequestedAt": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "end": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "id": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "infos": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "message": { "dataType": "string", "required": true }, "data": { "dataType": "any", "required": true } } } }, { "dataType": "undefined" }] }, "properties": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "userId": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "type": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "params": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.unknown_" }, { "dataType": "undefined" }] }, "objectId": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "name": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "method": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } }, "additionalProperties": { "dataType": "any" } }, { "dataType": "undefined" }] }, "result": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.unknown_" }, { "dataType": "undefined" }] }, "start": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "status": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["failure"] }, { "dataType": "enum", "enums": ["interrupted"] }, { "dataType": "enum", "enums": ["pending"] }, { "dataType": "enum", "enums": ["success"] }, { "dataType": "undefined" }] }, "tasks": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "refAlias", "ref": "XoTask" } }, { "dataType": "undefined" }] }, "updatedAt": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "warning": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "message": { "dataType": "string", "required": true }, "data": { "dataType": "any", "required": true } } } }, { "dataType": "undefined" }] } }, "validators": {} },
407
+ },
408
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
409
+ "WithHref_Partial_Unbrand_XoTask___": {
410
+ "dataType": "refAlias",
411
+ "type": { "dataType": "intersection", "subSchemas": [{ "ref": "Partial_Unbrand_XoTask__" }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "href": { "dataType": "string", "required": true } } }], "validators": {} },
412
+ },
413
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
414
+ "SendObjects_Partial_Unbrand_XoTask___": {
415
+ "dataType": "refAlias",
416
+ "type": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "string" } }, { "dataType": "array", "array": { "dataType": "refAlias", "ref": "WithHref_Partial_Unbrand_XoTask___" } }, { "ref": "NdjsonStream" }], "validators": {} },
417
+ },
418
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
419
+ "Unbrand_XoTask_": {
420
+ "dataType": "refAlias",
421
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "abortionRequestedAt": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "end": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "id": { "dataType": "string", "required": true }, "infos": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "message": { "dataType": "string", "required": true }, "data": { "dataType": "any", "required": true } } } }, { "dataType": "undefined" }] }, "properties": { "dataType": "nestedObjectLiteral", "nestedProperties": { "userId": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "type": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "params": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.unknown_" }, { "dataType": "undefined" }] }, "objectId": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "name": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "method": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } }, "additionalProperties": { "dataType": "any" }, "required": true }, "result": { "ref": "Record_string.unknown_", "required": true }, "start": { "dataType": "double", "required": true }, "status": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["failure"] }, { "dataType": "enum", "enums": ["interrupted"] }, { "dataType": "enum", "enums": ["pending"] }, { "dataType": "enum", "enums": ["success"] }], "required": true }, "tasks": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "refAlias", "ref": "XoTask" } }, { "dataType": "undefined" }] }, "updatedAt": { "dataType": "union", "subSchemas": [{ "dataType": "double" }, { "dataType": "undefined" }] }, "warning": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "message": { "dataType": "string", "required": true }, "data": { "dataType": "any", "required": true } } } }, { "dataType": "undefined" }] } }, "validators": {} },
422
+ },
423
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
387
424
  "Record_string.STORAGE_OPERATIONS_": {
388
425
  "dataType": "refAlias",
389
426
  "type": { "dataType": "nestedObjectLiteral", "nestedProperties": {}, "validators": {} },
@@ -434,11 +471,6 @@ const models = {
434
471
  "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "$pool": { "dataType": "string", "required": true }, "$poolId": { "dataType": "string", "required": true }, "_xapiRef": { "dataType": "string", "required": true }, "uuid": { "dataType": "string", "required": true }, "id": { "dataType": "string", "required": true }, "SM_type": { "dataType": "string", "required": true }, "vendor": { "dataType": "string", "required": true }, "name_label": { "dataType": "string", "required": true }, "name_description": { "dataType": "string", "required": true }, "configuration": { "ref": "Record_string.string_", "required": true }, "features": { "ref": "Record_string.number_", "required": true }, "driver_filename": { "dataType": "string", "required": true }, "required_cluster_stack": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "supported_image_formats": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "type": { "dataType": "enum", "enums": ["SM"], "required": true } }, "validators": {} },
435
472
  },
436
473
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
437
- "Record_string.unknown_": {
438
- "dataType": "refAlias",
439
- "type": { "dataType": "nestedObjectLiteral", "nestedProperties": {}, "validators": {} },
440
- },
441
- // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
442
474
  "Partial_Unbrand_XoServer__": {
443
475
  "dataType": "refAlias",
444
476
  "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "allowUnauthorized": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "enabled": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "error": { "dataType": "union", "subSchemas": [{ "ref": "Record_string.unknown_" }, { "dataType": "undefined" }] }, "host": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "httpProxy": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "id": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "label": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "master": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "poolId": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "poolNameDescription": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "poolNameLabel": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] }, "readOnly": { "dataType": "union", "subSchemas": [{ "dataType": "boolean" }, { "dataType": "undefined" }] }, "status": { "dataType": "union", "subSchemas": [{ "dataType": "enum", "enums": ["connected"] }, { "dataType": "enum", "enums": ["disconnected"] }, { "dataType": "enum", "enums": ["connecting"] }, { "dataType": "undefined" }] }, "username": { "dataType": "union", "subSchemas": [{ "dataType": "string" }, { "dataType": "undefined" }] } }, "validators": {} },
@@ -572,7 +604,7 @@ const models = {
572
604
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
573
605
  "PoolDashboard": {
574
606
  "dataType": "refAlias",
575
- "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "cpuProvisioning": { "dataType": "nestedObjectLiteral", "nestedProperties": { "percent": { "dataType": "double", "required": true }, "assigned": { "dataType": "double", "required": true }, "total": { "dataType": "double", "required": true } }, "required": true }, "alarms": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "srs": { "dataType": "nestedObjectLiteral", "nestedProperties": { "topFiveUsage": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "size": { "dataType": "double", "required": true }, "physical_usage": { "dataType": "double", "required": true }, "percent": { "dataType": "double", "required": true }, "id": { "dataType": "string", "required": true }, "name_label": { "dataType": "string", "required": true } } }, "required": true } }, "required": true }, "vms": { "dataType": "nestedObjectLiteral", "nestedProperties": { "topFiveUsage": { "dataType": "nestedObjectLiteral", "nestedProperties": { "isExpired": { "dataType": "boolean" }, "ram": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "memoryFree": { "dataType": "double", "required": true }, "memory": { "dataType": "double", "required": true }, "percent": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true }, "id": { "dataType": "string", "required": true } } }, "required": true }, "cpu": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "percent": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true }, "id": { "dataType": "string", "required": true } } }, "required": true } } }, "status": { "dataType": "nestedObjectLiteral", "nestedProperties": { "suspended": { "dataType": "double", "required": true }, "total": { "dataType": "double", "required": true }, "paused": { "dataType": "double", "required": true }, "halted": { "dataType": "double", "required": true }, "running": { "dataType": "double", "required": true } }, "required": true } }, "required": true }, "hosts": { "dataType": "nestedObjectLiteral", "nestedProperties": { "missingPatches": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "hasAuthorization": { "dataType": "enum", "enums": [false], "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "missingPatches": { "dataType": "array", "array": { "dataType": "union", "subSchemas": [{ "ref": "XcpPatches" }, { "ref": "XsPatches" }] }, "required": true }, "hasAuthorization": { "dataType": "enum", "enums": [true], "required": true } } }], "required": true }, "topFiveUsage": { "dataType": "nestedObjectLiteral", "nestedProperties": { "cpu": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "id": { "dataType": "string", "required": true }, "percent": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true } } }, "required": true }, "ram": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "id": { "dataType": "string", "required": true }, "percent": { "dataType": "double", "required": true }, "usage": { "dataType": "double", "required": true }, "size": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true } } }, "required": true } }, "required": true }, "status": { "dataType": "nestedObjectLiteral", "nestedProperties": { "total": { "dataType": "double", "required": true }, "halted": { "dataType": "double", "required": true }, "disabled": { "dataType": "double", "required": true }, "running": { "dataType": "double", "required": true } }, "required": true } }, "required": true } }, "validators": {} },
607
+ "type": { "dataType": "nestedObjectLiteral", "nestedProperties": { "cpuProvisioning": { "dataType": "nestedObjectLiteral", "nestedProperties": { "percent": { "dataType": "double", "required": true }, "assigned": { "dataType": "double", "required": true }, "total": { "dataType": "double", "required": true } }, "required": true }, "alarms": { "dataType": "array", "array": { "dataType": "string" }, "required": true }, "srs": { "dataType": "nestedObjectLiteral", "nestedProperties": { "topFiveUsage": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "size": { "dataType": "double", "required": true }, "physical_usage": { "dataType": "double", "required": true }, "percent": { "dataType": "double", "required": true }, "id": { "dataType": "string", "required": true }, "name_label": { "dataType": "string", "required": true } } }, "required": true } }, "required": true }, "vms": { "dataType": "nestedObjectLiteral", "nestedProperties": { "topFiveUsage": { "dataType": "nestedObjectLiteral", "nestedProperties": { "isExpired": { "dataType": "boolean" }, "ram": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "memoryFree": { "dataType": "double", "required": true }, "memory": { "dataType": "double", "required": true }, "percent": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true }, "id": { "dataType": "string", "required": true } } }, "required": true }, "cpu": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "percent": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true }, "id": { "dataType": "string", "required": true } } }, "required": true } } }, "status": { "dataType": "nestedObjectLiteral", "nestedProperties": { "suspended": { "dataType": "double", "required": true }, "total": { "dataType": "double", "required": true }, "paused": { "dataType": "double", "required": true }, "halted": { "dataType": "double", "required": true }, "running": { "dataType": "double", "required": true } }, "required": true } }, "required": true }, "hosts": { "dataType": "nestedObjectLiteral", "nestedProperties": { "missingPatches": { "dataType": "union", "subSchemas": [{ "dataType": "nestedObjectLiteral", "nestedProperties": { "hasAuthorization": { "dataType": "enum", "enums": [false], "required": true } } }, { "dataType": "nestedObjectLiteral", "nestedProperties": { "missingPatches": { "dataType": "union", "subSchemas": [{ "dataType": "array", "array": { "dataType": "refAlias", "ref": "XcpPatches" } }, { "dataType": "array", "array": { "dataType": "refAlias", "ref": "XsPatches" } }], "required": true }, "hasAuthorization": { "dataType": "enum", "enums": [true], "required": true } } }], "required": true }, "topFiveUsage": { "dataType": "nestedObjectLiteral", "nestedProperties": { "cpu": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "id": { "dataType": "string", "required": true }, "percent": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true } } }, "required": true }, "ram": { "dataType": "array", "array": { "dataType": "nestedObjectLiteral", "nestedProperties": { "id": { "dataType": "string", "required": true }, "percent": { "dataType": "double", "required": true }, "usage": { "dataType": "double", "required": true }, "size": { "dataType": "double", "required": true }, "name_label": { "dataType": "string", "required": true } } }, "required": true } }, "required": true }, "status": { "dataType": "nestedObjectLiteral", "nestedProperties": { "total": { "dataType": "double", "required": true }, "halted": { "dataType": "double", "required": true }, "disabled": { "dataType": "double", "required": true }, "running": { "dataType": "double", "required": true } }, "required": true } }, "required": true } }, "validators": {} },
576
608
  },
577
609
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
578
610
  "Branded_PIF_": {
@@ -2218,6 +2250,67 @@ export function RegisterRoutes(app) {
2218
2250
  }
2219
2251
  });
2220
2252
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
2253
+ const argsTaskController_getTasks = {
2254
+ req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
2255
+ fields: { "in": "query", "name": "fields", "dataType": "string" },
2256
+ ndjson: { "in": "query", "name": "ndjson", "dataType": "boolean" },
2257
+ watch: { "in": "query", "name": "watch", "dataType": "boolean" },
2258
+ filter: { "in": "query", "name": "filter", "dataType": "string" },
2259
+ limit: { "in": "query", "name": "limit", "dataType": "double" },
2260
+ };
2261
+ app.get('/rest/v0/tasks', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(TaskController)), ...(fetchMiddlewares(TaskController.prototype.getTasks)), async function TaskController_getTasks(request, response, next) {
2262
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
2263
+ let validatedArgs = [];
2264
+ try {
2265
+ validatedArgs = templateService.getValidatedArgs({ args: argsTaskController_getTasks, request, response });
2266
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
2267
+ const controller = await container.get(TaskController);
2268
+ if (typeof controller['setStatus'] === 'function') {
2269
+ controller.setStatus(undefined);
2270
+ }
2271
+ await templateService.apiHandler({
2272
+ methodName: 'getTasks',
2273
+ controller,
2274
+ response,
2275
+ next,
2276
+ validatedArgs,
2277
+ successStatus: undefined,
2278
+ });
2279
+ }
2280
+ catch (err) {
2281
+ return next(err);
2282
+ }
2283
+ });
2284
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
2285
+ const argsTaskController_getTask = {
2286
+ req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
2287
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
2288
+ wait: { "in": "query", "name": "wait", "dataType": "boolean" },
2289
+ };
2290
+ app.get('/rest/v0/tasks/:id', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(TaskController)), ...(fetchMiddlewares(TaskController.prototype.getTask)), async function TaskController_getTask(request, response, next) {
2291
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
2292
+ let validatedArgs = [];
2293
+ try {
2294
+ validatedArgs = templateService.getValidatedArgs({ args: argsTaskController_getTask, request, response });
2295
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
2296
+ const controller = await container.get(TaskController);
2297
+ if (typeof controller['setStatus'] === 'function') {
2298
+ controller.setStatus(undefined);
2299
+ }
2300
+ await templateService.apiHandler({
2301
+ methodName: 'getTask',
2302
+ controller,
2303
+ response,
2304
+ next,
2305
+ validatedArgs,
2306
+ successStatus: undefined,
2307
+ });
2308
+ }
2309
+ catch (err) {
2310
+ return next(err);
2311
+ }
2312
+ });
2313
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
2221
2314
  const argsSrController_getSrs = {
2222
2315
  req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
2223
2316
  fields: { "in": "query", "name": "fields", "dataType": "string" },
@@ -2938,6 +3031,33 @@ export function RegisterRoutes(app) {
2938
3031
  }
2939
3032
  });
2940
3033
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3034
+ const argsPoolController_getPoolMissingPatches = {
3035
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3036
+ };
3037
+ app.get('/rest/v0/pools/:id/missing_patches', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(PoolController)), ...(fetchMiddlewares(PoolController.prototype.getPoolMissingPatches)), async function PoolController_getPoolMissingPatches(request, response, next) {
3038
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3039
+ let validatedArgs = [];
3040
+ try {
3041
+ validatedArgs = templateService.getValidatedArgs({ args: argsPoolController_getPoolMissingPatches, request, response });
3042
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3043
+ const controller = await container.get(PoolController);
3044
+ if (typeof controller['setStatus'] === 'function') {
3045
+ controller.setStatus(undefined);
3046
+ }
3047
+ await templateService.apiHandler({
3048
+ methodName: 'getPoolMissingPatches',
3049
+ controller,
3050
+ response,
3051
+ next,
3052
+ validatedArgs,
3053
+ successStatus: undefined,
3054
+ });
3055
+ }
3056
+ catch (err) {
3057
+ return next(err);
3058
+ }
3059
+ });
3060
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
2941
3061
  const argsPifController_getPifs = {
2942
3062
  req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
2943
3063
  fields: { "in": "query", "name": "fields", "dataType": "string" },
@@ -3520,6 +3640,33 @@ export function RegisterRoutes(app) {
3520
3640
  }
3521
3641
  });
3522
3642
  // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3643
+ const argsHostController_getMissingPatches = {
3644
+ id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
3645
+ };
3646
+ app.get('/rest/v0/hosts/:id/missing_patches', authenticateMiddleware([{ "*": [] }]), ...(fetchMiddlewares(HostController)), ...(fetchMiddlewares(HostController.prototype.getMissingPatches)), async function HostController_getMissingPatches(request, response, next) {
3647
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3648
+ let validatedArgs = [];
3649
+ try {
3650
+ validatedArgs = templateService.getValidatedArgs({ args: argsHostController_getMissingPatches, request, response });
3651
+ const container = typeof iocContainer === 'function' ? iocContainer(request) : iocContainer;
3652
+ const controller = await container.get(HostController);
3653
+ if (typeof controller['setStatus'] === 'function') {
3654
+ controller.setStatus(undefined);
3655
+ }
3656
+ await templateService.apiHandler({
3657
+ methodName: 'getMissingPatches',
3658
+ controller,
3659
+ response,
3660
+ next,
3661
+ validatedArgs,
3662
+ successStatus: undefined,
3663
+ });
3664
+ }
3665
+ catch (err) {
3666
+ return next(err);
3667
+ }
3668
+ });
3669
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
3523
3670
  const argsGroupController_getGroups = {
3524
3671
  req: { "in": "request", "name": "req", "required": true, "dataType": "object" },
3525
3672
  fields: { "in": "query", "name": "fields", "dataType": "string" },
@@ -17,7 +17,7 @@ import { asynchronousActionResp, createdResp, featureUnauthorized, internalServe
17
17
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
18
18
  import { AlarmService } from '../alarms/alarm.service.mjs';
19
19
  import { genericAlarmsExample } from '../open-api/oa-examples/alarm.oa-example.mjs';
20
- import { createVm, importVm, partialPools, pool, poolDashboard, poolIds, poolStats, } from '../open-api/oa-examples/pool.oa-example.mjs';
20
+ import { createVm, importVm, partialPools, pool, poolDashboard, poolIds, poolMissingPatches, poolStats, } from '../open-api/oa-examples/pool.oa-example.mjs';
21
21
  import { taskLocation } from '../open-api/oa-examples/task.oa-example.mjs';
22
22
  import { createNetwork } from '../open-api/oa-examples/schedule.oa-example.mjs';
23
23
  import { BASE_URL } from '../index.mjs';
@@ -221,6 +221,14 @@ let PoolController = class PoolController extends XapiXoController {
221
221
  });
222
222
  return this.sendObjects(Object.values(alarms), req, 'alarms');
223
223
  }
224
+ /**
225
+ * @example id "355ee47d-ff4c-4924-3db2-fd86ae629676"
226
+ */
227
+ async getPoolMissingPatches(id) {
228
+ const pool = this.getObject(id);
229
+ const { missingPatches } = await this.#poolService.getMissingPatches(pool.id);
230
+ return missingPatches;
231
+ }
224
232
  };
225
233
  __decorate([
226
234
  Example(poolIds),
@@ -335,6 +343,13 @@ __decorate([
335
343
  __param(4, Query()),
336
344
  __param(5, Query())
337
345
  ], PoolController.prototype, "getPoolAlarms", null);
346
+ __decorate([
347
+ Example(poolMissingPatches),
348
+ Get('{id}/missing_patches'),
349
+ Response(notFoundResp.status, notFoundResp.description),
350
+ Response(featureUnauthorized.status, featureUnauthorized.description),
351
+ __param(0, Path())
352
+ ], PoolController.prototype, "getPoolMissingPatches", null);
338
353
  PoolController = __decorate([
339
354
  Route('pools'),
340
355
  Security('*'),
@@ -1,4 +1,5 @@
1
1
  import { createLogger } from '@xen-orchestra/log';
2
+ import { featureUnauthorized } from 'xo-common/api-errors.js';
2
3
  import { HOST_POWER_STATE, VM_POWER_STATE, } from '@vates/types';
3
4
  import { HostService } from '../hosts/host.service.mjs';
4
5
  import { VmService } from '../vms/vm.service.mjs';
@@ -45,13 +46,13 @@ export class PoolService {
45
46
  const alarms = this.#alarmService.getAlarms({ filter: alarm => alarm.$pool === poolId });
46
47
  return Object.keys(alarms);
47
48
  }
48
- async #getMissingPatches(poolId) {
49
- const missingPatchesInfo = await this.#hostService.getMissingPatchesInfo({ filter: host => host.$pool === poolId });
50
- if (!missingPatchesInfo.hasAuthorization) {
51
- return {
52
- hasAuthorization: false,
53
- };
54
- }
49
+ /**
50
+ * Throw if no authorization
51
+ */
52
+ async getMissingPatches(poolId) {
53
+ const missingPatchesInfo = await this.#hostService.getMissingPatchesInfo({
54
+ filter: host => host.$pool === poolId,
55
+ });
55
56
  const { hasAuthorization, missingPatches } = missingPatchesInfo;
56
57
  return {
57
58
  hasAuthorization,
@@ -182,7 +183,16 @@ export class PoolService {
182
183
  promiseWriteInStream({ maybePromise: this.#getHostsStatus(id), path: 'hosts.status', stream }),
183
184
  promiseWriteInStream({ maybePromise: this.#getVmsStatus(id), path: 'vms.status', stream }),
184
185
  promiseWriteInStream({ maybePromise: this.#getAlarms(id), path: 'alarms', stream }),
185
- promiseWriteInStream({ maybePromise: this.#getMissingPatches(id), path: 'hosts.missingPatches', stream }),
186
+ promiseWriteInStream({
187
+ maybePromise: this.getMissingPatches(id).catch(err => {
188
+ if (featureUnauthorized.is(err)) {
189
+ return { hasAuthorization: false };
190
+ }
191
+ throw err;
192
+ }),
193
+ path: 'hosts.missingPatches',
194
+ stream,
195
+ }),
186
196
  promiseWriteInStream({ maybePromise: this.#getTopFiveSrsUsage(id), path: 'srs.topFiveUsage', stream }),
187
197
  promiseWriteInStream({ maybePromise: this.#getTopFiveHostsRamUsage(id), path: 'hosts.topFiveUsage.ram', stream }),
188
198
  promiseWriteInStream({ maybePromise: this.#getTopFiveHostsCpuUsage(id), path: 'hosts.topFiveUsage.cpu', stream }),