@xen-orchestra/rest-api 0.9.0 → 0.10.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.
@@ -14,12 +14,16 @@ import { json } from 'express';
14
14
  import { RestApi } from '../rest-api/rest-api.mjs';
15
15
  import { asynchronousActionResp, createdResp, featureUnauthorized, internalServerErrorResp, noContentResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
16
16
  import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
17
- import { partialPools, pool, poolIds } from '../open-api/oa-examples/pool.oa-example.mjs';
17
+ import { createVm, importVm, partialPools, pool, poolIds } from '../open-api/oa-examples/pool.oa-example.mjs';
18
18
  import { taskLocation } from '../open-api/oa-examples/task.oa-example.mjs';
19
19
  import { createNetwork } from '../open-api/oa-examples/schedule.oa-example.mjs';
20
+ import { BASE_URL } from '../index.mjs';
21
+ import { VmService } from '../vms/vm.service.mjs';
20
22
  let PoolController = class PoolController extends XapiXoController {
21
- constructor(restApi) {
23
+ #vmService;
24
+ constructor(restApi, vmService) {
22
25
  super('pool', restApi);
26
+ this.#vmService = vmService;
23
27
  }
24
28
  /**
25
29
  *
@@ -120,6 +124,52 @@ let PoolController = class PoolController extends XapiXoController {
120
124
  },
121
125
  });
122
126
  }
127
+ // For this endpoint, the requestBody type is written directly to `tsoa.json` because TSOA does not provide a decorator for "octet-stream" file uploads
128
+ /**
129
+ * Import an XVA VM into a pool
130
+ *
131
+ * @example id "355ee47d-ff4c-4924-3db2-fd86ae629677"
132
+ * @example sr "c787b75c-3e0d-70fa-d0c3-cbfd382d7e33"
133
+ *
134
+ */
135
+ async importVm(req, id, sr) {
136
+ const pool = this.getXapiObject(id);
137
+ const xapi = pool.$xapi;
138
+ let srRef;
139
+ if (sr !== undefined) {
140
+ srRef = this.restApi.getXapiObject(sr, 'SR').$ref;
141
+ }
142
+ const vmRef = await xapi.VM_import(req, srRef);
143
+ const vmId = await xapi.getField('VM', vmRef, 'uuid');
144
+ this.setHeader('Location', `${BASE_URL}/vms/${vmId}`);
145
+ return { id: vmId };
146
+ }
147
+ /**
148
+ * @example id "355ee47d-ff4c-4924-3db2-fd86ae629677"
149
+ * @example body {
150
+ * "name_label": "new VM from REST API",
151
+ * "template": "9bbcc5d1-ad4b-06f1-18f6-03125e809c38",
152
+ * "boot": true
153
+ * }
154
+ */
155
+ async createVm(id, body, sync) {
156
+ const poolId = id;
157
+ const action = async () => {
158
+ const { affinity, template, ...rest } = body;
159
+ const params = { affinityHost: affinity, ...rest };
160
+ const vmId = await this.#vmService.create({ pool: poolId, template, ...params });
161
+ return { id: vmId };
162
+ };
163
+ return this.createAction(action, {
164
+ sync,
165
+ statusCode: createdResp.status,
166
+ taskProperties: {
167
+ args: body,
168
+ name: 'create VM',
169
+ objectId: poolId,
170
+ },
171
+ });
172
+ }
123
173
  };
124
174
  __decorate([
125
175
  Example(poolIds),
@@ -181,12 +231,38 @@ __decorate([
181
231
  __param(0, Path()),
182
232
  __param(1, Query())
183
233
  ], PoolController.prototype, "rollingUpdate", null);
234
+ __decorate([
235
+ Example(importVm),
236
+ Post('{id}/vms'),
237
+ Tags('vms'),
238
+ SuccessResponse(createdResp.status, 'VM imported'),
239
+ Response(notFoundResp.status, notFoundResp.description),
240
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
241
+ __param(0, Request()),
242
+ __param(1, Path()),
243
+ __param(2, Query())
244
+ ], PoolController.prototype, "importVm", null);
245
+ __decorate([
246
+ Example(taskLocation),
247
+ Example(createVm),
248
+ Post('{id}/actions/create_vm'),
249
+ Middlewares(json()),
250
+ Tags('vms'),
251
+ SuccessResponse(createdResp.status, createdResp.description),
252
+ Response(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
253
+ Response(notFoundResp.status, notFoundResp.description),
254
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
255
+ __param(0, Path()),
256
+ __param(1, Body()),
257
+ __param(2, Query())
258
+ ], PoolController.prototype, "createVm", null);
184
259
  PoolController = __decorate([
185
260
  Route('pools'),
186
261
  Security('*'),
187
262
  Response(unauthorizedResp.status, unauthorizedResp.description),
188
263
  Tags('pools'),
189
264
  provide(PoolController),
190
- __param(0, inject(RestApi))
265
+ __param(0, inject(RestApi)),
266
+ __param(1, inject(VmService))
191
267
  ], PoolController);
192
268
  export { PoolController };
@@ -12,6 +12,9 @@ export class RestApi {
12
12
  authenticateUser(...args) {
13
13
  return this.#xoApp.authenticateUser(...args);
14
14
  }
15
+ getCurrentUser() {
16
+ return this.#xoApp.apiContext.user;
17
+ }
15
18
  getObject(id, type) {
16
19
  return this.#xoApp.getObject(id, type);
17
20
  }
@@ -0,0 +1,60 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
11
+ import { inject } from 'inversify';
12
+ import { provide } from 'inversify-binding-decorators';
13
+ import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
14
+ import { partialSms, sm, smIds } from '../open-api/oa-examples/sm.oa-example.mjs';
15
+ import { RestApi } from '../rest-api/rest-api.mjs';
16
+ import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs';
17
+ let SmController = class SmController extends XapiXoController {
18
+ constructor(restApi) {
19
+ super('SM', restApi);
20
+ }
21
+ /**
22
+ * @example fields "uuid,name_label,SM_type"
23
+ * @example filter "SM_type:ext"
24
+ * @example limit 42
25
+ */
26
+ getSrs(req, fields, ndjson, filter, limit) {
27
+ return this.sendObjects(Object.values(this.getObjects({ filter, limit })), req);
28
+ }
29
+ /**
30
+ * @example id "c4284e12-37c9-7967-b9e8-83ef229c3e03"
31
+ */
32
+ getSr(id) {
33
+ return this.getObject(id);
34
+ }
35
+ };
36
+ __decorate([
37
+ Example(smIds),
38
+ Example(partialSms),
39
+ Get(''),
40
+ __param(0, Request()),
41
+ __param(1, Query()),
42
+ __param(2, Query()),
43
+ __param(3, Query()),
44
+ __param(4, Query())
45
+ ], SmController.prototype, "getSrs", null);
46
+ __decorate([
47
+ Example(sm),
48
+ Get('{id}'),
49
+ Response(notFoundResp.status, notFoundResp.description),
50
+ __param(0, Path())
51
+ ], SmController.prototype, "getSr", null);
52
+ SmController = __decorate([
53
+ Route('sms'),
54
+ Security('*'),
55
+ Response(unauthorizedResp.status, unauthorizedResp.description),
56
+ Tags('sms'),
57
+ provide(SmController),
58
+ __param(0, inject(RestApi))
59
+ ], SmController);
60
+ export { SmController };
@@ -104,15 +104,21 @@ let VmController = class VmController extends XapiXoController {
104
104
  await this.getXapiObject(id).$call('forget_data_source_archives', dataSource);
105
105
  }
106
106
  /**
107
+ * The VM must be halted
108
+ *
107
109
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
110
+ * @example body { "hostId": "b61a5c92-700e-4966-a13b-00633f03eea8" }
108
111
  */
109
- async startVm(id, sync) {
112
+ async startVm(id, body, sync) {
110
113
  const vmId = id;
111
- const action = () => this.getXapiObject(vmId).$callAsync('start', false, false);
114
+ const action = async () => {
115
+ await this.getXapi(vmId).startVm(vmId, { startOnly: true, hostId: body?.hostId });
116
+ };
112
117
  return this.createAction(action, {
113
118
  sync,
114
119
  statusCode: noContentResp.status,
115
120
  taskProperties: {
121
+ args: body,
116
122
  name: 'start VM',
117
123
  objectId: vmId,
118
124
  },
@@ -188,6 +194,82 @@ let VmController = class VmController extends XapiXoController {
188
194
  },
189
195
  });
190
196
  }
197
+ /**
198
+ * The VM must be running
199
+ *
200
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
201
+ */
202
+ async pauseVm(id, sync) {
203
+ const vmId = id;
204
+ const action = async () => {
205
+ await this.getXapiObject(vmId).$callAsync('pause');
206
+ };
207
+ return this.createAction(action, {
208
+ sync,
209
+ statusCode: noContentResp.status,
210
+ taskProperties: {
211
+ name: 'pause VM',
212
+ objectId: vmId,
213
+ },
214
+ });
215
+ }
216
+ /**
217
+ * The VM must be running
218
+ *
219
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
220
+ */
221
+ async suspendVm(id, sync) {
222
+ const vmId = id;
223
+ const action = async () => {
224
+ await this.getXapiObject(vmId).$callAsync('suspend');
225
+ };
226
+ return this.createAction(action, {
227
+ sync,
228
+ statusCode: noContentResp.status,
229
+ taskProperties: {
230
+ name: 'suspend VM',
231
+ objectId: vmId,
232
+ },
233
+ });
234
+ }
235
+ /**
236
+ * The VM must be suspended
237
+ *
238
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
239
+ */
240
+ async resumeVm(id, sync) {
241
+ const vmId = id;
242
+ const action = async () => {
243
+ await this.getXapi(vmId).resumeVm(vmId);
244
+ };
245
+ return this.createAction(action, {
246
+ sync,
247
+ statusCode: noContentResp.status,
248
+ taskProperties: {
249
+ name: 'resume VM',
250
+ objectId: vmId,
251
+ },
252
+ });
253
+ }
254
+ /**
255
+ * The VM must be paused
256
+ *
257
+ * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
258
+ */
259
+ async unpauseVm(id, sync) {
260
+ const vmId = id;
261
+ const action = async () => {
262
+ await this.getXapi(vmId).unpauseVm(vmId);
263
+ };
264
+ return this.createAction(action, {
265
+ sync,
266
+ statusCode: noContentResp.status,
267
+ taskProperties: {
268
+ name: 'unpause VM',
269
+ objectId: vmId,
270
+ },
271
+ });
272
+ }
191
273
  /**
192
274
  * @example id "f07ab729-c0e8-721c-45ec-f11276377030"
193
275
  * @example body { "name_label": "my_awesome_snapshot" }
@@ -261,7 +343,8 @@ __decorate([
261
343
  Response(notFoundResp.status, notFoundResp.description),
262
344
  Response(internalServerErrorResp.status, internalServerErrorResp.description),
263
345
  __param(0, Path()),
264
- __param(1, Query())
346
+ __param(1, Body()),
347
+ __param(2, Query())
265
348
  ], VmController.prototype, "startVm", null);
266
349
  __decorate([
267
350
  Example(taskLocation),
@@ -299,6 +382,46 @@ __decorate([
299
382
  __param(0, Path()),
300
383
  __param(1, Query())
301
384
  ], VmController.prototype, "hardRebootVm", null);
385
+ __decorate([
386
+ Example(taskLocation),
387
+ Post('{id}/actions/pause'),
388
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
389
+ Response(noContentResp.status, noContentResp.description),
390
+ Response(notFoundResp.status, notFoundResp.description),
391
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
392
+ __param(0, Path()),
393
+ __param(1, Query())
394
+ ], VmController.prototype, "pauseVm", null);
395
+ __decorate([
396
+ Example(taskLocation),
397
+ Post('{id}/actions/suspend'),
398
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
399
+ Response(noContentResp.status, noContentResp.description),
400
+ Response(notFoundResp.status, notFoundResp.description),
401
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
402
+ __param(0, Path()),
403
+ __param(1, Query())
404
+ ], VmController.prototype, "suspendVm", null);
405
+ __decorate([
406
+ Example(taskLocation),
407
+ Post('{id}/actions/resume'),
408
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
409
+ Response(noContentResp.status, noContentResp.description),
410
+ Response(notFoundResp.status, notFoundResp.description),
411
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
412
+ __param(0, Path()),
413
+ __param(1, Query())
414
+ ], VmController.prototype, "resumeVm", null);
415
+ __decorate([
416
+ Example(taskLocation),
417
+ Post('{id}/actions/unpause'),
418
+ SuccessResponse(asynchronousActionResp.status, asynchronousActionResp.description, asynchronousActionResp.produce),
419
+ Response(noContentResp.status, noContentResp.description),
420
+ Response(notFoundResp.status, notFoundResp.description),
421
+ Response(internalServerErrorResp.status, internalServerErrorResp.description),
422
+ __param(0, Path()),
423
+ __param(1, Query())
424
+ ], VmController.prototype, "unpauseVm", null);
302
425
  __decorate([
303
426
  Example(taskLocation),
304
427
  Post('{id}/actions/snapshot'),
@@ -0,0 +1,47 @@
1
+ import { createLogger } from '@xen-orchestra/log';
2
+ import { defer } from 'golike-defer';
3
+ import { Task } from '@vates/task';
4
+ const log = createLogger('xo:rest-api:vm-service');
5
+ export class VmService {
6
+ #restApi;
7
+ constructor(restApi) {
8
+ this.#restApi = restApi;
9
+ }
10
+ async #create($defer, params) {
11
+ const { pool, template, cloud_config, boot, destroy_cloud_config_vdi, network_config, ...rest } = params;
12
+ const xoApp = this.#restApi.xoApp;
13
+ const xapi = xoApp.getXapi(pool);
14
+ const currentUser = this.#restApi.getCurrentUser();
15
+ const xapiVm = await xapi.createVm(template, rest, undefined, currentUser?.id);
16
+ $defer.onFailure(() => xapi.VM_destroy(xapiVm.$ref));
17
+ const xoVm = this.#restApi.getObject(xapiVm.uuid, 'VM');
18
+ let cloudConfigVdi;
19
+ if (cloud_config !== undefined) {
20
+ const cloudConfigVdiUuid = await xapi.VM_createCloudInitConfig(xapiVm.$ref, cloud_config, {
21
+ networkConfig: network_config,
22
+ });
23
+ cloudConfigVdi = xoApp.getXapiObject(cloudConfigVdiUuid, 'VDI');
24
+ }
25
+ let timeLimit;
26
+ if (boot) {
27
+ timeLimit = Date.now() + 10 * 60 * 1000;
28
+ await xapiVm.$callAsync('start', false, false);
29
+ }
30
+ if (destroy_cloud_config_vdi && cloudConfigVdi !== undefined && boot) {
31
+ Task.info('Destruction of the cloud config VDI is planned and will be done as soon as possible');
32
+ xapi.VDI_destroyCloudInitConfig(cloudConfigVdi.$ref, { timeLimit }).catch(error => {
33
+ log.error('destroy cloud init config VDI failed', {
34
+ error,
35
+ vdi: {
36
+ uuid: cloudConfigVdi.uuid,
37
+ },
38
+ vm: {
39
+ uuid: xoVm.uuid,
40
+ },
41
+ });
42
+ });
43
+ }
44
+ return xoVm.id;
45
+ }
46
+ create = defer(this.#create);
47
+ }
@@ -7,26 +7,42 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { Controller, Example, Get, Response, Route, Security, Tags } from 'tsoa';
10
+ import { Controller, Example, Get, Query, Request, Response, Route, Security, Tags } from 'tsoa';
11
11
  import { inject } from 'inversify';
12
+ import { PassThrough } from 'node:stream';
12
13
  import { provide } from 'inversify-binding-decorators';
13
14
  import { unauthorizedResp } from '../open-api/common/response.common.mjs';
14
15
  import { xoaDashboard } from '../open-api/oa-examples/xoa.oa-example.mjs';
15
16
  import { XoaService } from './xoa.service.mjs';
17
+ import { NDJSON_CONTENT_TYPE } from '../helpers/utils.helper.mjs';
16
18
  let XoaController = class XoaController extends Controller {
17
19
  #xoaService;
18
20
  constructor(xoaService) {
19
21
  super();
20
22
  this.#xoaService = xoaService;
21
23
  }
22
- async getDashboard() {
23
- const dashboard = await this.#xoaService.getDashboard();
24
- return dashboard;
24
+ async getDashboard(req, ndjson) {
25
+ const stream = ndjson ? new PassThrough() : undefined;
26
+ const isStream = ndjson && stream !== undefined;
27
+ if (isStream) {
28
+ const res = req.res;
29
+ res.setHeader('Content-Type', NDJSON_CONTENT_TYPE);
30
+ stream.pipe(res);
31
+ }
32
+ const dashboard = await this.#xoaService.getDashboard({ stream });
33
+ if (isStream) {
34
+ stream.end();
35
+ }
36
+ else {
37
+ return dashboard;
38
+ }
25
39
  }
26
40
  };
27
41
  __decorate([
28
42
  Example(xoaDashboard),
29
- Get('dashboard')
43
+ Get('dashboard'),
44
+ __param(0, Request()),
45
+ __param(1, Query())
30
46
  ], XoaController.prototype, "getDashboard", null);
31
47
  XoaController = __decorate([
32
48
  Route(''),
@@ -1,10 +1,11 @@
1
1
  import groupBy from 'lodash/groupBy.js';
2
2
  import semver from 'semver';
3
- import { BACKUP_TYPE, } from '@vates/types';
3
+ import { BACKUP_TYPE, HOST_POWER_STATE, VM_POWER_STATE, } from '@vates/types';
4
4
  import { asyncEach } from '@vates/async-each';
5
5
  import { createLogger } from '@xen-orchestra/log';
6
6
  import { createPredicate } from 'value-matcher';
7
7
  import { extractIdsFromSimplePattern } from '@xen-orchestra/backups/extractIdsFromSimplePattern.mjs';
8
+ import { isPromise } from 'node:util/types';
8
9
  import { noSuchObject } from 'xo-common/api-errors.js';
9
10
  import { parse } from 'xo-remote-parser';
10
11
  import { getFromAsyncCache } from '../helpers/cache.helper.mjs';
@@ -17,7 +18,7 @@ export class XoaService {
17
18
  constructor(restApi) {
18
19
  this.#restApi = restApi;
19
20
  this.#dashboardCacheOpts = {
20
- timeout: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheTimeout'),
21
+ timeout: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheTimeout') ?? 60000,
21
22
  expiresIn: this.#restApi.xoApp.config.getOptionalDuration('rest-api.dashboardCacheExpiresIn'),
22
23
  };
23
24
  }
@@ -372,26 +373,104 @@ export class XoaService {
372
373
  return { ...backupsResult.value, isExpired: backupsResult.isExpired };
373
374
  }
374
375
  }
375
- async getDashboard() {
376
- const nPools = this.#getNumberOfPools();
377
- const nHosts = this.#getNumberOfHosts();
378
- const resourcesOverview = this.#getResourcesOverview();
379
- const storageRepositories = this.#getStorageRepositoriesSizeInfo();
380
- const poolsStatus = await this.#getPoolsStatus();
381
- const missingPatches = await this.#getMissingPatchesInfo();
382
- const backupRepositories = await this.#getBackupRepositoriesSizeInfo().catch(err => {
383
- log.error('#getBackupRepositoriesSizeInfo failed', err);
384
- // explicitly return undefined because typescript understand it as void instead of undefined
385
- return undefined;
386
- });
387
- const nHostsEol = await this.#getNumberOfEolHosts().catch(err => {
388
- log.err('#getNumberOfEolHosts failed', err);
389
- return undefined;
390
- });
391
- const backups = await this.#getbackupsInfo().catch(err => {
392
- log.error('#getbackupsInfo failed', err);
393
- return undefined;
394
- });
376
+ #getHostsStatus() {
377
+ const hosts = this.#restApi.getObjectsByType('host');
378
+ let nRunning = 0;
379
+ let nHalted = 0;
380
+ let nUnknown = 0;
381
+ let total = 0;
382
+ for (const id in hosts) {
383
+ total++;
384
+ const host = hosts[id];
385
+ switch (host.power_state) {
386
+ case HOST_POWER_STATE.RUNNING:
387
+ nRunning++;
388
+ break;
389
+ case HOST_POWER_STATE.HALTED:
390
+ nHalted++;
391
+ break;
392
+ default:
393
+ nUnknown++;
394
+ break;
395
+ }
396
+ }
397
+ return {
398
+ running: nRunning,
399
+ halted: nHalted,
400
+ unknown: nUnknown,
401
+ total,
402
+ };
403
+ }
404
+ #getVmsStatus() {
405
+ const vms = this.#restApi.getObjectsByType('VM');
406
+ let nActive = 0;
407
+ let nInactive = 0;
408
+ let nUnknown = 0;
409
+ let total = 0;
410
+ for (const id in vms) {
411
+ total++;
412
+ const vm = vms[id];
413
+ switch (vm.power_state) {
414
+ case VM_POWER_STATE.RUNNING:
415
+ case VM_POWER_STATE.PAUSED:
416
+ nActive++;
417
+ break;
418
+ case VM_POWER_STATE.HALTED:
419
+ case VM_POWER_STATE.SUSPENDED:
420
+ nInactive++;
421
+ break;
422
+ default:
423
+ nUnknown++;
424
+ break;
425
+ }
426
+ }
427
+ return {
428
+ active: nActive,
429
+ inactive: nInactive,
430
+ unknown: nUnknown,
431
+ total,
432
+ };
433
+ }
434
+ async getDashboard({ stream } = {}) {
435
+ async function promiseWriteInStream(maybePromise, key) {
436
+ let data;
437
+ if (isPromise(maybePromise)) {
438
+ data = await maybePromise;
439
+ }
440
+ else {
441
+ data = maybePromise;
442
+ }
443
+ if (stream !== undefined) {
444
+ if (stream.writableNeedDrain) {
445
+ await new Promise(resolve => stream.once('drain', resolve));
446
+ }
447
+ stream.write(JSON.stringify({ [key]: data }) + '\n');
448
+ }
449
+ return data;
450
+ }
451
+ const [nPools, nHosts, hostsStatus, resourcesOverview, vmsStatus, storageRepositories, poolsStatus, missingPatches, backupRepositories, nHostsEol, backups,] = await Promise.all([
452
+ promiseWriteInStream(this.#getNumberOfPools(), 'nPools'),
453
+ promiseWriteInStream(this.#getNumberOfHosts(), 'nHosts'),
454
+ promiseWriteInStream(this.#getHostsStatus(), 'hostsStatus'),
455
+ promiseWriteInStream(this.#getResourcesOverview(), 'resourcesOverview'),
456
+ promiseWriteInStream(this.#getVmsStatus(), 'vmsStatus'),
457
+ promiseWriteInStream(this.#getStorageRepositoriesSizeInfo(), 'storageRepositories'),
458
+ promiseWriteInStream(this.#getPoolsStatus(), 'poolsStatus'),
459
+ promiseWriteInStream(this.#getMissingPatchesInfo(), 'missingPatches'),
460
+ promiseWriteInStream(this.#getBackupRepositoriesSizeInfo().catch(err => {
461
+ log.error('#getBackupRepositoriesSizeInfo failed', err);
462
+ // explicitly return undefined because typescript understand it as void instead of undefined
463
+ return undefined;
464
+ }), 'backupRepositories'),
465
+ promiseWriteInStream(this.#getNumberOfEolHosts().catch(err => {
466
+ log.error('#getNumberOfEolHosts failed', err);
467
+ return undefined;
468
+ }), 'nHostsEol'),
469
+ promiseWriteInStream(this.#getbackupsInfo().catch(err => {
470
+ log.error('#getbackupsInfo failed', err);
471
+ return undefined;
472
+ }), 'backups'),
473
+ ]);
395
474
  return {
396
475
  nPools,
397
476
  nHosts,
@@ -402,6 +481,8 @@ export class XoaService {
402
481
  missingPatches,
403
482
  storageRepositories,
404
483
  backups,
484
+ hostsStatus,
485
+ vmsStatus,
405
486
  };
406
487
  }
407
488
  }