@xen-orchestra/rest-api 0.14.0 → 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 (49) hide show
  1. package/dist/abstract-classes/base-controller.mjs +4 -2
  2. package/dist/abstract-classes/xapi-xo-controller.mjs +2 -6
  3. package/dist/alarms/alarm.service.mjs +2 -1
  4. package/dist/backup-jobs/backup-job.controller.mjs +284 -0
  5. package/dist/backup-jobs/backup-job.service.mjs +25 -0
  6. package/dist/backup-jobs/backup-job.type.mjs +1 -0
  7. package/dist/backup-logs/backup-log.controller.mjs +75 -0
  8. package/dist/backup-logs/backup-log.service.mjs +5 -0
  9. package/dist/groups/group.controller.mjs +37 -1
  10. package/dist/helpers/error.helper.mjs +10 -0
  11. package/dist/hosts/host.controller.mjs +24 -4
  12. package/dist/hosts/host.service.mjs +6 -7
  13. package/dist/hosts/host.type.mjs +1 -0
  14. package/dist/index.mjs +4 -0
  15. package/dist/ioc/ioc.mjs +36 -0
  16. package/dist/middlewares/generic-error-handler.middleware.mjs +5 -1
  17. package/dist/open-api/common/response.common.mjs +8 -0
  18. package/dist/open-api/oa-examples/backup-job.oa-example.mjs +128 -0
  19. package/dist/open-api/oa-examples/backup-log.oa-example.mjs +93 -0
  20. package/dist/open-api/oa-examples/host.oa-example.mjs +30 -0
  21. package/dist/open-api/oa-examples/pool.oa-example.mjs +30 -0
  22. package/dist/open-api/oa-examples/proxy.oa-example.mjs +25 -0
  23. package/dist/open-api/oa-examples/restore-log.oa-example.mjs +53 -0
  24. package/dist/open-api/oa-examples/task.oa-example.mjs +46 -0
  25. package/dist/open-api/oa-examples/user.oa-example.mjs +32 -0
  26. package/dist/open-api/oa-examples/vdi.oa-example.mjs +3 -0
  27. package/dist/open-api/routes/routes.js +2079 -490
  28. package/dist/pools/pool.controller.mjs +16 -1
  29. package/dist/pools/pool.service.mjs +18 -8
  30. package/dist/proxies/proxy.controller.mjs +61 -0
  31. package/dist/rest-api/rest-api.mjs +6 -2
  32. package/dist/restore-logs/restore-log.controller.mjs +144 -0
  33. package/dist/srs/sr.controller.mjs +39 -2
  34. package/dist/tasks/task.controller.mjs +167 -0
  35. package/dist/tasks/task.service.mjs +24 -0
  36. package/dist/users/user.controller.mjs +91 -12
  37. package/dist/users/user.service.mjs +21 -0
  38. package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +46 -4
  39. package/dist/vdis/vdi.controller.mjs +43 -4
  40. package/dist/vdis/vdi.service.mjs +21 -0
  41. package/dist/vm-snapshots/vm-snapshot.controller.mjs +72 -3
  42. package/dist/vm-templates/vm-template.controller.mjs +43 -2
  43. package/dist/vms/vm.controller.mjs +135 -5
  44. package/dist/vms/vm.service.mjs +18 -0
  45. package/dist/xoa/xoa.service.mjs +14 -4
  46. package/open-api/spec/swagger.json +11733 -6660
  47. package/package.json +6 -5
  48. package/tsconfig.json +1 -0
  49. package/tsoa.json +27 -0
@@ -53,8 +53,10 @@ export class BaseController extends Controller {
53
53
  }
54
54
  maybeCompressResponse(req, res) {
55
55
  let transform;
56
- let acceptEncoding = req.headers['accept-encoding'];
57
- acceptEncoding = Array.isArray(acceptEncoding) ? acceptEncoding : acceptEncoding?.split(',');
56
+ const _acceptEncoding = req.headers['accept-encoding'];
57
+ const acceptEncoding = Array.isArray(_acceptEncoding)
58
+ ? _acceptEncoding
59
+ : _acceptEncoding?.split(',');
58
60
  if (acceptEncoding !== undefined &&
59
61
  acceptEncoding.some(encoding => {
60
62
  const value = encoding.split(';')[0].trim().toLowerCase();
@@ -1,4 +1,3 @@
1
- import * as CM from 'complex-matcher';
2
1
  import { BaseController } from './base-controller.mjs';
3
2
  export class XapiXoController extends BaseController {
4
3
  #type;
@@ -6,11 +5,8 @@ export class XapiXoController extends BaseController {
6
5
  super(restApi);
7
6
  this.#type = type;
8
7
  }
9
- getObjects({ filter, limit } = {}) {
10
- if (filter !== undefined && typeof filter === 'string') {
11
- filter = CM.parse(filter).createPredicate();
12
- }
13
- return this.restApi.getObjectsByType(this.#type, { filter: filter, limit });
8
+ getObjects(opts) {
9
+ return this.restApi.getObjectsByType(this.#type, opts);
14
10
  }
15
11
  getObject(id) {
16
12
  return this.restApi.getObject(id, this.#type);
@@ -3,7 +3,8 @@ import { BASE_URL } from '../index.mjs';
3
3
  // E.g: 'value: 0.6\nconfig:\n<variable>\n<name value="cpu_usage"/>\n<alarm_trigger_level value="0.4"/>\n<alarm_trigger_period value ="60"/>\n</variable>';
4
4
  const ALARM_BODY_REGEX = /^value:\s*(Infinity|NaN|-Infinity|\d+(?:\.\d+)?)\s*config:\s*<variable>\s*<name value="(.*?)"/;
5
5
  const ALARM_NAMES = ['ALARM', 'BOND_STATUS_CHANGED', 'MULTIPATH_PERIODIC_ALERT'];
6
- export const alarmPredicate = CM.parse(`name:|(${ALARM_NAMES.join(' ')})`).createPredicate();
6
+ export const RAW_ALARM_FILTER = `name:|(${ALARM_NAMES.join(' ')})`;
7
+ export const alarmPredicate = CM.parse(RAW_ALARM_FILTER).createPredicate();
7
8
  export class AlarmService {
8
9
  #restApi;
9
10
  constructor(restApi) {
@@ -0,0 +1,284 @@
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 * as CM from 'complex-matcher';
11
+ import { createLogger } from '@xen-orchestra/log';
12
+ import { inject } from 'inversify';
13
+ import { noSuchObject } from 'xo-common/api-errors.js';
14
+ import { Deprecated, Example, Get, Hidden, Middlewares, Path, Query, Request, Response, Route, Security, Tags, } from 'tsoa';
15
+ import { provide } from 'inversify-binding-decorators';
16
+ import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
17
+ import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
18
+ import { notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
19
+ import { RestApi } from '../rest-api/rest-api.mjs';
20
+ import { limitAndFilterArray } from '../helpers/utils.helper.mjs';
21
+ import { XoController } from '../abstract-classes/xo-controller.mjs';
22
+ import { metadataBackupJob, metadataBackupJobIds, mirrorBackupJob, mirrorBackupJobIds, partialMetadataBackupJobs, partialMirrorBackupJobs, partialVmBackupJobs, vmBackupJob, vmBackupJobIds, } from '../open-api/oa-examples/backup-job.oa-example.mjs';
23
+ import { BASE_URL } from '../index.mjs';
24
+ const log = createLogger('xo:rest-api:backupJob-controller');
25
+ let BackupJobController = class BackupJobController extends XoController {
26
+ async getAllCollectionObjects() {
27
+ const allJobs = await this.restApi.xoApp.getAllJobs();
28
+ const backupJobs = allJobs.filter(job => 'type' in job);
29
+ return backupJobs;
30
+ }
31
+ async getCollectionObject(id) {
32
+ const job = await this.restApi.xoApp.getJob(id);
33
+ if (!('type' in job)) {
34
+ // not a backup job
35
+ throw noSuchObject(id, 'backup-job');
36
+ }
37
+ return job;
38
+ }
39
+ /**
40
+ *
41
+ * @example fields "name,mode,type,id"
42
+ * @example filter "type:backup"
43
+ * @example limit 42
44
+ */
45
+ async getBackupJobs(req, fields, ndjson, filter, limit) {
46
+ const backupJobs = await this.getObjects({ filter, limit });
47
+ return this.sendObjects(Object.values(backupJobs), req, 'backup-jobs');
48
+ }
49
+ /**
50
+ * @example id "d33f3dc1-92b4-469c-ad58-4c2a106a4721"
51
+ */
52
+ getBackupJob(id) {
53
+ return this.getObject(id);
54
+ }
55
+ };
56
+ __decorate([
57
+ Example(vmBackupJobIds),
58
+ Example(partialVmBackupJobs),
59
+ Get(''),
60
+ __param(0, Request()),
61
+ __param(1, Query()),
62
+ __param(2, Query()),
63
+ __param(3, Query()),
64
+ __param(4, Query())
65
+ ], BackupJobController.prototype, "getBackupJobs", null);
66
+ __decorate([
67
+ Example(vmBackupJob),
68
+ Response(notFoundResp.status, notFoundResp.description),
69
+ Get('{id}'),
70
+ __param(0, Path())
71
+ ], BackupJobController.prototype, "getBackupJob", null);
72
+ BackupJobController = __decorate([
73
+ Route('backup-jobs'),
74
+ Security('*'),
75
+ Response(unauthorizedResp.status, unauthorizedResp.description),
76
+ Tags('backup-jobs'),
77
+ provide(BackupJobController)
78
+ ], BackupJobController);
79
+ export { BackupJobController };
80
+ // ----------- DEPRECATED TO BE REMOVED IN ONE YEAR (09-12-2026)--------------------
81
+ let DeprecatedBackupController = class DeprecatedBackupController extends XoController {
82
+ #backupLogService;
83
+ constructor(restApi, backupLogService) {
84
+ super(restApi);
85
+ this.#backupLogService = backupLogService;
86
+ }
87
+ async getAllCollectionObjects() {
88
+ const backupJobs = await this.restApi.xoApp.getAllJobs();
89
+ return backupJobs.filter(job => 'type' in job);
90
+ }
91
+ async getCollectionObject(id) {
92
+ const backupJob = await this.restApi.xoApp.getJob(id);
93
+ if (!('type' in backupJob)) {
94
+ throw noSuchObject(id, 'backup-job');
95
+ }
96
+ return backupJob;
97
+ }
98
+ async getObject(id, type) {
99
+ const backupJob = await super.getObject(id);
100
+ if (backupJob.type !== type) {
101
+ throw noSuchObject(id, type);
102
+ }
103
+ return backupJob;
104
+ }
105
+ /**
106
+ *
107
+ * @example fields "name,mode,id"
108
+ * @example filter "mode:delta"
109
+ * @example limit 42
110
+ */
111
+ async getVmBackupJobs(req, fields, ndjson, filter, limit) {
112
+ const vmBackupJobs = await this.restApi.xoApp.getAllJobs('backup');
113
+ return this.sendObjects(limitAndFilterArray(vmBackupJobs, { filter, limit }), req, 'backup-jobs');
114
+ }
115
+ // For compatibility, redirect /backup/jobs/:id to /backup/jobs/vm/:id
116
+ async redirectToVmBackupJob(req, id) {
117
+ const res = req.res;
118
+ res.redirect(308, BASE_URL + '/backup/jobs/vm/' + id);
119
+ }
120
+ /**
121
+ * @example id "d33f3dc1-92b4-469c-ad58-4c2a106a4721"
122
+ */
123
+ getVmBackupJob(id) {
124
+ return this.getObject(id, 'backup');
125
+ }
126
+ /**
127
+ *
128
+ * @example fields "name,xoMetadata,id"
129
+ * @example filter "xoMetadata?"
130
+ * @example limit 42
131
+ */
132
+ async getMetadataBackupJobs(req, fields, ndjson, filter, limit) {
133
+ const metadataBackupJobs = await this.restApi.xoApp.getAllJobs('metadataBackup');
134
+ return this.sendObjects(limitAndFilterArray(metadataBackupJobs, { filter, limit }), req, 'backup-jobs');
135
+ }
136
+ /**
137
+ * @example id "b50f95fd-f6b7-4027-87b6-6a02c7dcd5f5"
138
+ */
139
+ getMetadataBackupJob(id) {
140
+ return this.getObject(id, 'metadataBackup');
141
+ }
142
+ /**
143
+ *
144
+ * @example fields "name,mode,id"
145
+ * @example filter "mode:delta"
146
+ * @example limit 42
147
+ */
148
+ async getMirrorBackupJobs(req, fields, ndjson, filter, limit) {
149
+ const mirrorBackupJobs = await this.restApi.xoApp.getAllJobs('mirrorBackup');
150
+ return this.sendObjects(limitAndFilterArray(mirrorBackupJobs, { filter, limit }), req, 'backup-jobs');
151
+ }
152
+ /**
153
+ * @example id "e680c14c-ab52-45c8-bb0e-bd4ca12ea8f9"
154
+ */
155
+ getMirrorBackupJob(id) {
156
+ return this.getObject(id, 'mirrorBackup');
157
+ }
158
+ /**
159
+ * @example fields "jobName,status,data"
160
+ * @example filter "status:success"
161
+ * @example limit 42
162
+ */
163
+ async getDeprecatedBackupLogs(req, fields, ndjson, filter, limit) {
164
+ const userFilter = filter === undefined ? () => true : CM.parse(filter).createPredicate();
165
+ const predicate = (log) => {
166
+ if (!this.#backupLogService.isBackupLog(log)) {
167
+ return false;
168
+ }
169
+ return userFilter(log);
170
+ };
171
+ const logs = (await this.restApi.xoApp.getBackupNgLogsSorted({ filter: predicate, limit }));
172
+ return this.sendObjects(logs, req, 'backup-logs');
173
+ }
174
+ /**
175
+ * @example id "1753776067468"
176
+ */
177
+ async getDeprecatedBackupLog(id) {
178
+ const log = await this.restApi.xoApp.getBackupNgLogs(id);
179
+ if (!this.#backupLogService.isBackupLog(log)) {
180
+ throw noSuchObject('backup-log');
181
+ }
182
+ return log;
183
+ }
184
+ };
185
+ __decorate([
186
+ Example(vmBackupJobIds),
187
+ Example(partialVmBackupJobs),
188
+ Deprecated(),
189
+ Get('jobs/vm'),
190
+ Tags('backup-jobs'),
191
+ __param(0, Request()),
192
+ __param(1, Query()),
193
+ __param(2, Query()),
194
+ __param(3, Query()),
195
+ __param(4, Query())
196
+ ], DeprecatedBackupController.prototype, "getVmBackupJobs", null);
197
+ __decorate([
198
+ Hidden(),
199
+ Get('jobs/{id}'),
200
+ Tags('backup-jobs'),
201
+ __param(0, Request()),
202
+ __param(1, Path())
203
+ ], DeprecatedBackupController.prototype, "redirectToVmBackupJob", null);
204
+ __decorate([
205
+ Example(vmBackupJob),
206
+ Deprecated(),
207
+ Response(notFoundResp.status, notFoundResp.description),
208
+ Get('jobs/vm/{id}'),
209
+ Tags('backup-jobs'),
210
+ __param(0, Path())
211
+ ], DeprecatedBackupController.prototype, "getVmBackupJob", null);
212
+ __decorate([
213
+ Example(metadataBackupJobIds),
214
+ Example(partialMetadataBackupJobs),
215
+ Deprecated(),
216
+ Get('jobs/metadata'),
217
+ Tags('backup-jobs'),
218
+ __param(0, Request()),
219
+ __param(1, Query()),
220
+ __param(2, Query()),
221
+ __param(3, Query()),
222
+ __param(4, Query())
223
+ ], DeprecatedBackupController.prototype, "getMetadataBackupJobs", null);
224
+ __decorate([
225
+ Example(metadataBackupJob),
226
+ Deprecated(),
227
+ Response(notFoundResp.status, notFoundResp.description),
228
+ Get('jobs/metadata/{id}'),
229
+ Tags('backup-jobs'),
230
+ __param(0, Path())
231
+ ], DeprecatedBackupController.prototype, "getMetadataBackupJob", null);
232
+ __decorate([
233
+ Example(mirrorBackupJobIds),
234
+ Example(partialMirrorBackupJobs),
235
+ Deprecated(),
236
+ Get('jobs/mirror'),
237
+ Tags('backup-jobs'),
238
+ __param(0, Request()),
239
+ __param(1, Query()),
240
+ __param(2, Query()),
241
+ __param(3, Query()),
242
+ __param(4, Query())
243
+ ], DeprecatedBackupController.prototype, "getMirrorBackupJobs", null);
244
+ __decorate([
245
+ Example(mirrorBackupJob),
246
+ Deprecated(),
247
+ Response(notFoundResp.status, notFoundResp.description),
248
+ Get('jobs/mirror/{id}'),
249
+ Tags('backup-jobs'),
250
+ __param(0, Path())
251
+ ], DeprecatedBackupController.prototype, "getMirrorBackupJob", null);
252
+ __decorate([
253
+ Example(backupLogIds),
254
+ Example(partialBackupLogs),
255
+ Deprecated(),
256
+ Get('logs'),
257
+ Tags('backup-logs'),
258
+ __param(0, Request()),
259
+ __param(1, Query()),
260
+ __param(2, Query()),
261
+ __param(3, Query()),
262
+ __param(4, Query())
263
+ ], DeprecatedBackupController.prototype, "getDeprecatedBackupLogs", null);
264
+ __decorate([
265
+ Example(backupLog),
266
+ Deprecated(),
267
+ Get('logs/{id}'),
268
+ Tags('backup-logs'),
269
+ __param(0, Path())
270
+ ], DeprecatedBackupController.prototype, "getDeprecatedBackupLog", null);
271
+ DeprecatedBackupController = __decorate([
272
+ Route('backup'),
273
+ Security('*'),
274
+ Response(unauthorizedResp.status, unauthorizedResp.description),
275
+ Middlewares((_req, _res, next) => {
276
+ log.warn('You are calling a deprecated route. It will be removed in the futur. Please use `/rest/v0/backup-jobs` or `/rest/v0/backup-logs` instead');
277
+ next();
278
+ }),
279
+ provide(DeprecatedBackupController),
280
+ __param(0, inject(RestApi)),
281
+ __param(1, inject(BackupLogService))
282
+ ], DeprecatedBackupController);
283
+ export { DeprecatedBackupController };
284
+ // ----------- DEPRECATED TO BE REMOVED IN ONE YEAR (09-12-2026)--------------------
@@ -0,0 +1,25 @@
1
+ import { createPredicate } from 'value-matcher';
2
+ import { extractIdsFromSimplePattern } from '@xen-orchestra/backups/extractIdsFromSimplePattern.mjs';
3
+ const NO_BAK_TAG = 'xo:no-bak';
4
+ export class BackupJobService {
5
+ #restApi;
6
+ constructor(restApi) {
7
+ this.#restApi = restApi;
8
+ }
9
+ async isVmInBackupJob(backupJobId, vmId) {
10
+ const backupJob = await this.#restApi.xoApp.getJob(backupJobId);
11
+ const vm = this.#restApi.getObject(vmId, 'VM');
12
+ if (vm.tags.includes(NO_BAK_TAG)) {
13
+ return false;
14
+ }
15
+ try {
16
+ const vmIds = extractIdsFromSimplePattern(backupJob.vms);
17
+ return vmIds.some(id => id === vm.id);
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
+ }
20
+ catch (_) {
21
+ const predicate = createPredicate(backupJob.vms);
22
+ return predicate(vm);
23
+ }
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
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 { noSuchObject } from 'xo-common/api-errors.js';
13
+ import { provide } from 'inversify-binding-decorators';
14
+ import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
15
+ import { BackupLogService } from './backup-log.service.mjs';
16
+ import { RestApi } from '../rest-api/rest-api.mjs';
17
+ import { unauthorizedResp } from '../open-api/common/response.common.mjs';
18
+ import { XoController } from '../abstract-classes/xo-controller.mjs';
19
+ let BackupLogController = class BackupLogController extends XoController {
20
+ #backupLogService;
21
+ constructor(restApi, backupLogService) {
22
+ super(restApi);
23
+ this.#backupLogService = backupLogService;
24
+ }
25
+ getAllCollectionObjects() {
26
+ return this.restApi.xoApp.getBackupNgLogsSorted({ filter: this.#backupLogService.isBackupLog });
27
+ }
28
+ async getCollectionObject(id) {
29
+ const log = await this.restApi.xoApp.getBackupNgLogs(id);
30
+ if (!this.#backupLogService.isBackupLog(log)) {
31
+ throw noSuchObject('backup-log');
32
+ }
33
+ return log;
34
+ }
35
+ /**
36
+ * @example fields "jobName,status,data"
37
+ * @example filter "status:success"
38
+ * @example limit 42
39
+ */
40
+ async getBackupLogs(req, fields, ndjson, filter, limit) {
41
+ const backupLogs = await this.getObjects({ filter, limit });
42
+ return this.sendObjects(Object.values(backupLogs), req);
43
+ }
44
+ /**
45
+ * @example id "1753776067468"
46
+ */
47
+ getBackupLog(id) {
48
+ return this.getObject(id);
49
+ }
50
+ };
51
+ __decorate([
52
+ Example(backupLogIds),
53
+ Example(partialBackupLogs),
54
+ Get(''),
55
+ __param(0, Request()),
56
+ __param(1, Query()),
57
+ __param(2, Query()),
58
+ __param(3, Query()),
59
+ __param(4, Query())
60
+ ], BackupLogController.prototype, "getBackupLogs", null);
61
+ __decorate([
62
+ Example(backupLog),
63
+ Get('{id}'),
64
+ __param(0, Path())
65
+ ], BackupLogController.prototype, "getBackupLog", null);
66
+ BackupLogController = __decorate([
67
+ Route('backup-logs'),
68
+ Security('*'),
69
+ Response(unauthorizedResp.status, unauthorizedResp.description),
70
+ Tags('backup-logs'),
71
+ provide(BackupLogController),
72
+ __param(0, inject(RestApi)),
73
+ __param(1, inject(BackupLogService))
74
+ ], BackupLogController);
75
+ export { BackupLogController };
@@ -0,0 +1,5 @@
1
+ export class BackupLogService {
2
+ isBackupLog(log) {
3
+ return log.message === 'backup' || log.message === 'metadata';
4
+ }
5
+ }
@@ -8,13 +8,23 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { Body, Delete, Example, Get, Middlewares, Patch, Path, Post, Put, Query, Request, Response, Route, Security, SuccessResponse, Tags, } from 'tsoa';
11
+ import { inject } from 'inversify';
11
12
  import { json } from 'express';
12
13
  import { provide } from 'inversify-binding-decorators';
13
14
  import { forbiddenOperation } from 'xo-common/api-errors.js';
14
15
  import { createdResp, forbiddenOperationResp, invalidParameters, noContentResp, notFoundResp, resourceAlreadyExists, unauthorizedResp, } from '../open-api/common/response.common.mjs';
15
16
  import { group, groupId, groupIds, partialGroups } from '../open-api/oa-examples/group.oa-example.mjs';
16
17
  import { XoController } from '../abstract-classes/xo-controller.mjs';
18
+ import { UserService } from '../users/user.service.mjs';
19
+ import { RestApi } from '../rest-api/rest-api.mjs';
20
+ import { limitAndFilterArray } from '../helpers/utils.helper.mjs';
21
+ import { partialUsers, userIds } from '../open-api/oa-examples/user.oa-example.mjs';
17
22
  let GroupController = class GroupController extends XoController {
23
+ #userService;
24
+ constructor(restApi, userService) {
25
+ super(restApi);
26
+ this.#userService = userService;
27
+ }
18
28
  // --- abstract methods
19
29
  getAllCollectionObjects() {
20
30
  return this.restApi.xoApp.getAllGroups();
@@ -85,6 +95,17 @@ let GroupController = class GroupController extends XoController {
85
95
  }
86
96
  await this.restApi.xoApp.addUserToGroup(userId, group.id);
87
97
  }
98
+ /**
99
+ * @example id "6c81b5e1-afc1-43ea-8f8d-939ceb5f3f90"
100
+ * @example fields "permission,name,id"
101
+ * @example filter "permission:none"
102
+ * @example limit 42
103
+ */
104
+ async getGroupUsers(req, id, fields, ndjson, filter, limit) {
105
+ const group = await this.getObject(id);
106
+ const users = await Promise.all(group.users.map(id => this.#userService.getUser(id)));
107
+ return this.sendObjects(limitAndFilterArray(users, { filter, limit }), req, 'users');
108
+ }
88
109
  };
89
110
  __decorate([
90
111
  Example(groupIds),
@@ -143,11 +164,26 @@ __decorate([
143
164
  __param(0, Path()),
144
165
  __param(1, Path())
145
166
  ], GroupController.prototype, "addUserToGroup", null);
167
+ __decorate([
168
+ Example(userIds),
169
+ Example(partialUsers),
170
+ Get('{id}/users'),
171
+ Tags('users'),
172
+ Response(notFoundResp.status, notFoundResp.description),
173
+ __param(0, Request()),
174
+ __param(1, Path()),
175
+ __param(2, Query()),
176
+ __param(3, Query()),
177
+ __param(4, Query()),
178
+ __param(5, Query())
179
+ ], GroupController.prototype, "getGroupUsers", null);
146
180
  GroupController = __decorate([
147
181
  Route('groups'),
148
182
  Security('*'),
149
183
  Response(unauthorizedResp.status, unauthorizedResp.description),
150
184
  Tags('groups'),
151
- provide(GroupController)
185
+ provide(GroupController),
186
+ __param(0, inject(RestApi)),
187
+ __param(1, inject(UserService))
152
188
  ], GroupController);
153
189
  export { GroupController };
@@ -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'))
package/dist/ioc/ioc.mjs CHANGED
@@ -7,6 +7,11 @@ import { XoaService } from '../xoa/xoa.service.mjs';
7
7
  import { HostService } from '../hosts/host.service.mjs';
8
8
  import { PoolService } from '../pools/pool.service.mjs';
9
9
  import { AlarmService } from '../alarms/alarm.service.mjs';
10
+ import { VdiService } from '../vdis/vdi.service.mjs';
11
+ import { TaskService } from '../tasks/task.service.mjs';
12
+ import { UserService } from '../users/user.service.mjs';
13
+ import { BackupJobService } from '../backup-jobs/backup-job.service.mjs';
14
+ import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
10
15
  const iocContainer = new Container();
11
16
  decorate(injectable(), Controller);
12
17
  iocContainer.load(buildProviderModule());
@@ -53,5 +58,36 @@ export function setupContainer(xoApp) {
53
58
  return new AlarmService(restApi);
54
59
  })
55
60
  .inSingletonScope();
61
+ iocContainer.bind(TaskService).toDynamicValue(ctx => {
62
+ const restApi = ctx.container.get(RestApi);
63
+ return new TaskService(restApi);
64
+ });
65
+ iocContainer
66
+ .bind(VdiService)
67
+ .toDynamicValue(ctx => {
68
+ const restApi = ctx.container.get(RestApi);
69
+ return new VdiService(restApi);
70
+ })
71
+ .inSingletonScope();
72
+ iocContainer
73
+ .bind(UserService)
74
+ .toDynamicValue(ctx => {
75
+ const restApi = ctx.container.get(RestApi);
76
+ return new UserService(restApi);
77
+ })
78
+ .inSingletonScope();
79
+ iocContainer
80
+ .bind(BackupJobService)
81
+ .toDynamicValue(ctx => {
82
+ const restApi = ctx.container.get(RestApi);
83
+ return new BackupJobService(restApi);
84
+ })
85
+ .inSingletonScope();
86
+ iocContainer
87
+ .bind(BackupLogService)
88
+ .toDynamicValue(() => {
89
+ return new BackupLogService();
90
+ })
91
+ .inSingletonScope();
56
92
  }
57
93
  export { iocContainer };