@xen-orchestra/rest-api 0.28.2 → 0.30.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.
- package/README.md +108 -1
- package/dist/abstract-classes/base-controller.mjs +28 -3
- package/dist/abstract-classes/listener.mjs +124 -15
- package/dist/acl-privileges/acl-privilege.controller.mjs +172 -0
- package/dist/acl-roles/acl-role.controller.mjs +384 -0
- package/dist/alarms/alarm.controller.mjs +25 -11
- package/dist/alarms/alarm.service.mjs +8 -0
- package/dist/backup-archives/backup-archive.controller.mjs +33 -23
- package/dist/backup-archives/backup-archive.service.mjs +21 -0
- package/dist/backup-jobs/backup-job.controller.mjs +74 -25
- package/dist/backup-jobs/backup-job.service.mjs +7 -0
- package/dist/backup-logs/backup-log.controller.mjs +28 -13
- package/dist/backup-logs/backup-log.service.mjs +19 -0
- package/dist/backup-repositories/backup-repositories.controller.mjs +24 -5
- package/dist/events/event.class.mjs +36 -18
- package/dist/events/event.controller.mjs +3 -0
- package/dist/events/event.service.mjs +4 -4
- package/dist/groups/group.controller.mjs +99 -12
- package/dist/helpers/markdown.helper.mjs +20 -0
- package/dist/helpers/object-wrapper.helper.mjs +3 -3
- package/dist/hosts/host.controller.mjs +90 -15
- package/dist/ioc/ioc.mjs +13 -4
- package/dist/messages/message.controller.mjs +32 -10
- package/dist/middlewares/acl.middleware.mjs +202 -0
- package/dist/middlewares/authentication.middleware.mjs +15 -6
- package/dist/middlewares/tsoa-to-xo-error.middleware.mjs +19 -1
- package/dist/networks/network.controller.mjs +72 -17
- package/dist/open-api/oa-examples/acl-privilege.oa-example.mjs +25 -0
- package/dist/open-api/oa-examples/acl-role.oa-example.mjs +22 -0
- package/dist/open-api/oa-examples/backup-archive.oa-example.mjs +6 -6
- package/dist/open-api/oa-examples/common.oa-example.mjs +3 -0
- package/dist/open-api/routes/routes.js +856 -172
- package/dist/pbds/pbd.controller.mjs +20 -5
- package/dist/pcis/pci.controller.mjs +19 -5
- package/dist/pgpus/pgpu.controller.mjs +19 -5
- package/dist/pifs/pif.controller.mjs +56 -16
- package/dist/pools/pool.controller.mjs +166 -17
- package/dist/proxies/proxy.controller.mjs +25 -6
- package/dist/restore-logs/restore-log.controller.mjs +42 -23
- package/dist/schedules/schedule.controller.mjs +36 -5
- package/dist/servers/server.controller.mjs +71 -9
- package/dist/sms/sm.controller.mjs +17 -4
- package/dist/srs/sr.controller.mjs +74 -18
- package/dist/tasks/task.controller.mjs +74 -13
- package/dist/users/user.controller.mjs +124 -22
- package/dist/vbds/vbd.controller.mjs +76 -38
- package/dist/vdi-snapshots/vdi-snapshot.controller.mjs +48 -14
- package/dist/vdis/vdi.controller.mjs +81 -16
- package/dist/vifs/vif.controller.mjs +118 -16
- package/dist/vm-controller/vm-controller.controller.mjs +77 -19
- package/dist/vm-snapshots/vm-snapshot.controller.mjs +85 -18
- package/dist/vm-templates/vm-template.controller.mjs +86 -18
- package/dist/vms/vm.controller.mjs +182 -24
- package/open-api/spec/swagger.json +12112 -3537
- package/package.json +12 -11
|
@@ -12,9 +12,10 @@ import { inject } from 'inversify';
|
|
|
12
12
|
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
13
13
|
import { Deprecated, Example, Get, Hidden, Middlewares, Path, Query, Request, Response, Route, Security, Tags, } from 'tsoa';
|
|
14
14
|
import { provide } from 'inversify-binding-decorators';
|
|
15
|
+
import { acl, autoBindService } from '../middlewares/acl.middleware.mjs';
|
|
15
16
|
import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
|
|
16
17
|
import { BackupLogService } from '../backup-logs/backup-log.service.mjs';
|
|
17
|
-
import { badRequestResp, notFoundResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
18
|
+
import { badRequestResp, forbiddenOperationResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
18
19
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
19
20
|
import { limitAndFilterArray, safeParseComplexMatcher } from '../helpers/utils.helper.mjs';
|
|
20
21
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
@@ -33,24 +34,30 @@ let BackupJobController = class BackupJobController extends XoController {
|
|
|
33
34
|
const backupJobs = allJobs.filter(job => this.#backupJobService.isBackupJob(job));
|
|
34
35
|
return backupJobs;
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!this.#backupJobService.isBackupJob(job)) {
|
|
39
|
-
throw noSuchObject(id, 'backup-job');
|
|
40
|
-
}
|
|
41
|
-
return job;
|
|
37
|
+
getCollectionObject(id) {
|
|
38
|
+
return this.#backupJobService.getBackupJob(id);
|
|
42
39
|
}
|
|
43
40
|
/**
|
|
41
|
+
* Returns all backup jobs that match the following privilege:
|
|
42
|
+
* - resource: backup-job, action: read
|
|
44
43
|
*
|
|
45
44
|
* @example fields "name,mode,type,id"
|
|
46
45
|
* @example filter "type:backup"
|
|
47
46
|
* @example limit 42
|
|
48
47
|
*/
|
|
49
|
-
async getBackupJobs(req, fields, ndjson, filter, limit) {
|
|
50
|
-
const backupJobs = await this.getObjects({ filter
|
|
51
|
-
return this.sendObjects(Object.values(backupJobs), req,
|
|
48
|
+
async getBackupJobs(req, fields, ndjson, markdown, filter, limit) {
|
|
49
|
+
const backupJobs = await this.getObjects({ filter });
|
|
50
|
+
return this.sendObjects(Object.values(backupJobs), req, {
|
|
51
|
+
path: 'backup-jobs',
|
|
52
|
+
limit,
|
|
53
|
+
privilege: { action: 'read', resource: 'backup-job' },
|
|
54
|
+
});
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
57
|
+
*
|
|
58
|
+
* Required privilege:
|
|
59
|
+
* - resource: backup-job, action: read
|
|
60
|
+
*
|
|
54
61
|
* @example id "d33f3dc1-92b4-469c-ad58-4c2a106a4721"
|
|
55
62
|
*/
|
|
56
63
|
getBackupJob(id) {
|
|
@@ -61,16 +68,25 @@ __decorate([
|
|
|
61
68
|
Example(vmBackupJobIds),
|
|
62
69
|
Example(partialVmBackupJobs),
|
|
63
70
|
Get(''),
|
|
71
|
+
Security('*', ['acl']),
|
|
64
72
|
__param(0, Request()),
|
|
65
73
|
__param(1, Query()),
|
|
66
74
|
__param(2, Query()),
|
|
67
75
|
__param(3, Query()),
|
|
68
|
-
__param(4, Query())
|
|
76
|
+
__param(4, Query()),
|
|
77
|
+
__param(5, Query())
|
|
69
78
|
], BackupJobController.prototype, "getBackupJobs", null);
|
|
70
79
|
__decorate([
|
|
71
80
|
Example(vmBackupJob),
|
|
72
|
-
Response(notFoundResp.status, notFoundResp.description),
|
|
73
81
|
Get('{id}'),
|
|
82
|
+
Middlewares(acl({
|
|
83
|
+
resource: 'backup-job',
|
|
84
|
+
action: 'read',
|
|
85
|
+
objectId: 'params.id',
|
|
86
|
+
getObject: autoBindService(BackupJobService, 'getBackupJob'),
|
|
87
|
+
})),
|
|
88
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
89
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
74
90
|
__param(0, Path())
|
|
75
91
|
], BackupJobController.prototype, "getBackupJob", null);
|
|
76
92
|
BackupJobController = __decorate([
|
|
@@ -112,14 +128,20 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
|
|
|
112
128
|
return backupJob;
|
|
113
129
|
}
|
|
114
130
|
/**
|
|
131
|
+
* Returns all VM backup jobs that match the following privilege:
|
|
132
|
+
* - resource: backup-job, action: read
|
|
115
133
|
*
|
|
116
134
|
* @example fields "name,mode,id"
|
|
117
135
|
* @example filter "mode:delta"
|
|
118
136
|
* @example limit 42
|
|
119
137
|
*/
|
|
120
|
-
async getVmBackupJobs(req, fields, ndjson, filter, limit) {
|
|
138
|
+
async getVmBackupJobs(req, fields, ndjson, markdown, filter, limit) {
|
|
121
139
|
const vmBackupJobs = await this.restApi.xoApp.getAllJobs('backup');
|
|
122
|
-
return this.sendObjects(limitAndFilterArray(vmBackupJobs, { filter
|
|
140
|
+
return this.sendObjects(limitAndFilterArray(vmBackupJobs, { filter }), req, {
|
|
141
|
+
path: 'backup-jobs',
|
|
142
|
+
limit,
|
|
143
|
+
privilege: { action: 'read', resource: 'backup-job' },
|
|
144
|
+
});
|
|
123
145
|
}
|
|
124
146
|
// For compatibility, redirect /backup/jobs/:id to /backup/jobs/vm/:id
|
|
125
147
|
async redirectToVmBackupJob(req, id) {
|
|
@@ -133,14 +155,20 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
|
|
|
133
155
|
return this.getObject(id, 'backup');
|
|
134
156
|
}
|
|
135
157
|
/**
|
|
158
|
+
* Returns all metadata backup jobs that match the following privilege:
|
|
159
|
+
* - resource: backup-job, action: read
|
|
136
160
|
*
|
|
137
161
|
* @example fields "name,xoMetadata,id"
|
|
138
162
|
* @example filter "xoMetadata?"
|
|
139
163
|
* @example limit 42
|
|
140
164
|
*/
|
|
141
|
-
async getMetadataBackupJobs(req, fields, ndjson, filter, limit) {
|
|
165
|
+
async getMetadataBackupJobs(req, fields, ndjson, markdown, filter, limit) {
|
|
142
166
|
const metadataBackupJobs = await this.restApi.xoApp.getAllJobs('metadataBackup');
|
|
143
|
-
return this.sendObjects(limitAndFilterArray(metadataBackupJobs, { filter
|
|
167
|
+
return this.sendObjects(limitAndFilterArray(metadataBackupJobs, { filter }), req, {
|
|
168
|
+
path: 'backup-jobs',
|
|
169
|
+
limit,
|
|
170
|
+
privilege: { action: 'read', resource: 'backup-job' },
|
|
171
|
+
});
|
|
144
172
|
}
|
|
145
173
|
/**
|
|
146
174
|
* @example id "b50f95fd-f6b7-4027-87b6-6a02c7dcd5f5"
|
|
@@ -149,14 +177,20 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
|
|
|
149
177
|
return this.getObject(id, 'metadataBackup');
|
|
150
178
|
}
|
|
151
179
|
/**
|
|
180
|
+
* Returns all mirror backup jobs that match the following privilege:
|
|
181
|
+
* - resource: backup-job, action: read
|
|
152
182
|
*
|
|
153
183
|
* @example fields "name,mode,id"
|
|
154
184
|
* @example filter "mode:delta"
|
|
155
185
|
* @example limit 42
|
|
156
186
|
*/
|
|
157
|
-
async getMirrorBackupJobs(req, fields, ndjson, filter, limit) {
|
|
187
|
+
async getMirrorBackupJobs(req, fields, ndjson, markdown, filter, limit) {
|
|
158
188
|
const mirrorBackupJobs = await this.restApi.xoApp.getAllJobs('mirrorBackup');
|
|
159
|
-
return this.sendObjects(limitAndFilterArray(mirrorBackupJobs, { filter
|
|
189
|
+
return this.sendObjects(limitAndFilterArray(mirrorBackupJobs, { filter }), req, {
|
|
190
|
+
path: 'backup-jobs',
|
|
191
|
+
limit,
|
|
192
|
+
privilege: { action: 'read', resource: 'backup-job' },
|
|
193
|
+
});
|
|
160
194
|
}
|
|
161
195
|
/**
|
|
162
196
|
* @example id "e680c14c-ab52-45c8-bb0e-bd4ca12ea8f9"
|
|
@@ -165,11 +199,14 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
|
|
|
165
199
|
return this.getObject(id, 'mirrorBackup');
|
|
166
200
|
}
|
|
167
201
|
/**
|
|
202
|
+
* Returns all backup logs that match the following privilege:
|
|
203
|
+
* - resource: backup-log, action: read
|
|
204
|
+
*
|
|
168
205
|
* @example fields "jobName,status,data"
|
|
169
206
|
* @example filter "status:success"
|
|
170
207
|
* @example limit 42
|
|
171
208
|
*/
|
|
172
|
-
async getDeprecatedBackupLogs(req, fields, ndjson, filter, limit) {
|
|
209
|
+
async getDeprecatedBackupLogs(req, fields, ndjson, markdown, filter, limit) {
|
|
173
210
|
const userFilter = filter === undefined ? () => true : safeParseComplexMatcher(filter).createPredicate();
|
|
174
211
|
const predicate = (log) => {
|
|
175
212
|
if (!this.#backupLogService.isBackupLog(log)) {
|
|
@@ -177,8 +214,12 @@ let DeprecatedBackupController = class DeprecatedBackupController extends XoCont
|
|
|
177
214
|
}
|
|
178
215
|
return userFilter(log);
|
|
179
216
|
};
|
|
180
|
-
const logs = (await this.restApi.xoApp.getBackupNgLogsSorted({ filter: predicate
|
|
181
|
-
return this.sendObjects(logs, req,
|
|
217
|
+
const logs = (await this.restApi.xoApp.getBackupNgLogsSorted({ filter: predicate }));
|
|
218
|
+
return this.sendObjects(logs, req, {
|
|
219
|
+
path: 'backup-logs',
|
|
220
|
+
limit,
|
|
221
|
+
privilege: { action: 'read', resource: 'backup-log' },
|
|
222
|
+
});
|
|
182
223
|
}
|
|
183
224
|
/**
|
|
184
225
|
* @example id "1753776067468"
|
|
@@ -196,12 +237,14 @@ __decorate([
|
|
|
196
237
|
Example(partialVmBackupJobs),
|
|
197
238
|
Deprecated(),
|
|
198
239
|
Get('jobs/vm'),
|
|
240
|
+
Security('*', ['acl']),
|
|
199
241
|
Tags('backup-jobs'),
|
|
200
242
|
__param(0, Request()),
|
|
201
243
|
__param(1, Query()),
|
|
202
244
|
__param(2, Query()),
|
|
203
245
|
__param(3, Query()),
|
|
204
|
-
__param(4, Query())
|
|
246
|
+
__param(4, Query()),
|
|
247
|
+
__param(5, Query())
|
|
205
248
|
], DeprecatedBackupController.prototype, "getVmBackupJobs", null);
|
|
206
249
|
__decorate([
|
|
207
250
|
Hidden(),
|
|
@@ -223,12 +266,14 @@ __decorate([
|
|
|
223
266
|
Example(partialMetadataBackupJobs),
|
|
224
267
|
Deprecated(),
|
|
225
268
|
Get('jobs/metadata'),
|
|
269
|
+
Security('*', ['acl']),
|
|
226
270
|
Tags('backup-jobs'),
|
|
227
271
|
__param(0, Request()),
|
|
228
272
|
__param(1, Query()),
|
|
229
273
|
__param(2, Query()),
|
|
230
274
|
__param(3, Query()),
|
|
231
|
-
__param(4, Query())
|
|
275
|
+
__param(4, Query()),
|
|
276
|
+
__param(5, Query())
|
|
232
277
|
], DeprecatedBackupController.prototype, "getMetadataBackupJobs", null);
|
|
233
278
|
__decorate([
|
|
234
279
|
Example(metadataBackupJob),
|
|
@@ -243,12 +288,14 @@ __decorate([
|
|
|
243
288
|
Example(partialMirrorBackupJobs),
|
|
244
289
|
Deprecated(),
|
|
245
290
|
Get('jobs/mirror'),
|
|
291
|
+
Security('*', ['acl']),
|
|
246
292
|
Tags('backup-jobs'),
|
|
247
293
|
__param(0, Request()),
|
|
248
294
|
__param(1, Query()),
|
|
249
295
|
__param(2, Query()),
|
|
250
296
|
__param(3, Query()),
|
|
251
|
-
__param(4, Query())
|
|
297
|
+
__param(4, Query()),
|
|
298
|
+
__param(5, Query())
|
|
252
299
|
], DeprecatedBackupController.prototype, "getMirrorBackupJobs", null);
|
|
253
300
|
__decorate([
|
|
254
301
|
Example(mirrorBackupJob),
|
|
@@ -263,12 +310,14 @@ __decorate([
|
|
|
263
310
|
Example(partialBackupLogs),
|
|
264
311
|
Deprecated(),
|
|
265
312
|
Get('logs'),
|
|
313
|
+
Security('*', ['acl']),
|
|
266
314
|
Tags('backup-logs'),
|
|
267
315
|
__param(0, Request()),
|
|
268
316
|
__param(1, Query()),
|
|
269
317
|
__param(2, Query()),
|
|
270
318
|
__param(3, Query()),
|
|
271
|
-
__param(4, Query())
|
|
319
|
+
__param(4, Query()),
|
|
320
|
+
__param(5, Query())
|
|
272
321
|
], DeprecatedBackupController.prototype, "getDeprecatedBackupLogs", null);
|
|
273
322
|
__decorate([
|
|
274
323
|
Example(backupLog),
|
|
@@ -8,6 +8,13 @@ export class BackupJobService {
|
|
|
8
8
|
constructor(restApi) {
|
|
9
9
|
this.#restApi = restApi;
|
|
10
10
|
}
|
|
11
|
+
async getBackupJob(id) {
|
|
12
|
+
const job = await this.#restApi.xoApp.getJob(id);
|
|
13
|
+
if (!this.isBackupJob(job)) {
|
|
14
|
+
throw noSuchObject(id, 'backup-job');
|
|
15
|
+
}
|
|
16
|
+
return job;
|
|
17
|
+
}
|
|
11
18
|
isBackupJob(anyJob) {
|
|
12
19
|
return this.#backupJobTypes.includes(anyJob.type);
|
|
13
20
|
}
|
|
@@ -7,15 +7,15 @@ 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 { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
10
|
+
import { Example, Get, Middlewares, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
11
11
|
import { inject } from 'inversify';
|
|
12
|
-
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
13
12
|
import { provide } from 'inversify-binding-decorators';
|
|
14
13
|
import { backupLog, backupLogIds, partialBackupLogs } from '../open-api/oa-examples/backup-log.oa-example.mjs';
|
|
15
14
|
import { BackupLogService } from './backup-log.service.mjs';
|
|
16
15
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
17
|
-
import { badRequestResp, unauthorizedResp } from '../open-api/common/response.common.mjs';
|
|
16
|
+
import { badRequestResp, forbiddenOperationResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
18
17
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
18
|
+
import { acl, autoBindService } from '../middlewares/acl.middleware.mjs';
|
|
19
19
|
let BackupLogController = class BackupLogController extends XoController {
|
|
20
20
|
#backupLogService;
|
|
21
21
|
constructor(restApi, backupLogService) {
|
|
@@ -25,23 +25,28 @@ let BackupLogController = class BackupLogController extends XoController {
|
|
|
25
25
|
getAllCollectionObjects() {
|
|
26
26
|
return this.restApi.xoApp.getBackupNgLogsSorted({ filter: this.#backupLogService.isBackupLog });
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!this.#backupLogService.isBackupLog(log)) {
|
|
31
|
-
throw noSuchObject('backup-log');
|
|
32
|
-
}
|
|
33
|
-
return log;
|
|
28
|
+
getCollectionObject(id) {
|
|
29
|
+
return this.#backupLogService.getBackupLog(id);
|
|
34
30
|
}
|
|
35
31
|
/**
|
|
32
|
+
* Returns all backup logs that match the following privilege:
|
|
33
|
+
* - resource: backup-log, action: read
|
|
34
|
+
*
|
|
36
35
|
* @example fields "jobName,status,data"
|
|
37
36
|
* @example filter "status:success"
|
|
38
37
|
* @example limit 42
|
|
39
38
|
*/
|
|
40
|
-
async getBackupLogs(req, fields, ndjson, filter, limit) {
|
|
41
|
-
const backupLogs = await this.getObjects({ filter
|
|
42
|
-
return this.sendObjects(Object.values(backupLogs), req
|
|
39
|
+
async getBackupLogs(req, fields, ndjson, markdown, filter, limit) {
|
|
40
|
+
const backupLogs = await this.getObjects({ filter });
|
|
41
|
+
return this.sendObjects(Object.values(backupLogs), req, {
|
|
42
|
+
limit,
|
|
43
|
+
privilege: { action: 'read', resource: 'backup-log' },
|
|
44
|
+
});
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
47
|
+
* Required privilege:
|
|
48
|
+
* - resource: backup-log, action: read
|
|
49
|
+
*
|
|
45
50
|
* @example id "1753776067468"
|
|
46
51
|
*/
|
|
47
52
|
getBackupLog(id) {
|
|
@@ -52,15 +57,25 @@ __decorate([
|
|
|
52
57
|
Example(backupLogIds),
|
|
53
58
|
Example(partialBackupLogs),
|
|
54
59
|
Get(''),
|
|
60
|
+
Security('*', ['acl']),
|
|
55
61
|
__param(0, Request()),
|
|
56
62
|
__param(1, Query()),
|
|
57
63
|
__param(2, Query()),
|
|
58
64
|
__param(3, Query()),
|
|
59
|
-
__param(4, Query())
|
|
65
|
+
__param(4, Query()),
|
|
66
|
+
__param(5, Query())
|
|
60
67
|
], BackupLogController.prototype, "getBackupLogs", null);
|
|
61
68
|
__decorate([
|
|
62
69
|
Example(backupLog),
|
|
63
70
|
Get('{id}'),
|
|
71
|
+
Middlewares(acl({
|
|
72
|
+
resource: 'backup-log',
|
|
73
|
+
action: 'read',
|
|
74
|
+
objectId: 'params.id',
|
|
75
|
+
getObject: autoBindService(BackupLogService, 'getBackupLog'),
|
|
76
|
+
})),
|
|
77
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
78
|
+
Response(notFoundResp.status, notFoundResp.description),
|
|
64
79
|
__param(0, Path())
|
|
65
80
|
], BackupLogController.prototype, "getBackupLog", null);
|
|
66
81
|
BackupLogController = __decorate([
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
import { noSuchObject } from 'xo-common/api-errors.js';
|
|
1
2
|
export class BackupLogService {
|
|
3
|
+
#restApi;
|
|
4
|
+
constructor(restApi) {
|
|
5
|
+
this.#restApi = restApi;
|
|
6
|
+
}
|
|
2
7
|
isBackupLog(log) {
|
|
3
8
|
return log.message === 'backup';
|
|
4
9
|
}
|
|
10
|
+
async getBackupLog(id) {
|
|
11
|
+
const log = await this.#restApi.xoApp.getBackupNgLogs(id);
|
|
12
|
+
if (!this.isBackupLog(log)) {
|
|
13
|
+
throw noSuchObject('backup-log');
|
|
14
|
+
}
|
|
15
|
+
return log;
|
|
16
|
+
}
|
|
17
|
+
async getRestoreLog(id) {
|
|
18
|
+
const log = await this.#restApi.xoApp.getBackupNgLogs(id);
|
|
19
|
+
if (this.isBackupLog(log)) {
|
|
20
|
+
throw noSuchObject('restore-log');
|
|
21
|
+
}
|
|
22
|
+
return log;
|
|
23
|
+
}
|
|
5
24
|
getVmBackupTaskLog(log, vmId) {
|
|
6
25
|
return log.tasks?.find(task => task.data?.id === vmId);
|
|
7
26
|
}
|
|
@@ -7,10 +7,11 @@ 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 { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
10
|
+
import { Example, Get, Middlewares, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa';
|
|
11
11
|
import { inject } from 'inversify';
|
|
12
12
|
import { provide } from 'inversify-binding-decorators';
|
|
13
|
-
import {
|
|
13
|
+
import { acl } from '../middlewares/acl.middleware.mjs';
|
|
14
|
+
import { badRequestResp, forbiddenOperationResp, notFoundResp, unauthorizedResp, } from '../open-api/common/response.common.mjs';
|
|
14
15
|
import { backupRepositoryIds, partialBackupRepositories, backupRepository, } from '../open-api/oa-examples/backup-repository.oa-example.mjs';
|
|
15
16
|
import { XoController } from '../abstract-classes/xo-controller.mjs';
|
|
16
17
|
import { RestApi } from '../rest-api/rest-api.mjs';
|
|
@@ -26,14 +27,23 @@ let BackupRepositoryController = class BackupRepositoryController extends XoCont
|
|
|
26
27
|
return this.restApi.xoApp.getRemote(id);
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
30
|
+
* Returns all backup repositories that match the following privilege:
|
|
31
|
+
* - resource: backup-repository, action: read
|
|
32
|
+
*
|
|
29
33
|
* @example fields "id,name,enabled"
|
|
30
34
|
* @example filter "enabled?"
|
|
31
35
|
* @example limit 42
|
|
32
36
|
*/
|
|
33
|
-
async getRepositories(req, fields, ndjson, filter, limit) {
|
|
34
|
-
return this.sendObjects(Object.values(await this.getObjects({ filter
|
|
37
|
+
async getRepositories(req, fields, ndjson, markdown, filter, limit) {
|
|
38
|
+
return this.sendObjects(Object.values(await this.getObjects({ filter })), req, {
|
|
39
|
+
limit,
|
|
40
|
+
privilege: { action: 'read', resource: 'backup-repository' },
|
|
41
|
+
});
|
|
35
42
|
}
|
|
36
43
|
/**
|
|
44
|
+
* Required privilege:
|
|
45
|
+
* - resource: backup-repository, action: read
|
|
46
|
+
*
|
|
37
47
|
* @example id "c4284e12-37c9-7967-b9e8-83ef229c3e03"
|
|
38
48
|
*/
|
|
39
49
|
getRepository(id) {
|
|
@@ -44,15 +54,24 @@ __decorate([
|
|
|
44
54
|
Example(backupRepositoryIds),
|
|
45
55
|
Example(partialBackupRepositories),
|
|
46
56
|
Get(''),
|
|
57
|
+
Security('*', ['acl']),
|
|
47
58
|
__param(0, Request()),
|
|
48
59
|
__param(1, Query()),
|
|
49
60
|
__param(2, Query()),
|
|
50
61
|
__param(3, Query()),
|
|
51
|
-
__param(4, Query())
|
|
62
|
+
__param(4, Query()),
|
|
63
|
+
__param(5, Query())
|
|
52
64
|
], BackupRepositoryController.prototype, "getRepositories", null);
|
|
53
65
|
__decorate([
|
|
54
66
|
Example(backupRepository),
|
|
55
67
|
Get('{id}'),
|
|
68
|
+
Middlewares(acl({
|
|
69
|
+
resource: 'backup-repository',
|
|
70
|
+
action: 'read',
|
|
71
|
+
objectId: 'params.id',
|
|
72
|
+
getObject: ({ restApi }) => restApi.xoApp.getRemote,
|
|
73
|
+
})),
|
|
74
|
+
Response(forbiddenOperationResp.status, forbiddenOperationResp.description),
|
|
56
75
|
Response(notFoundResp.status, notFoundResp.description),
|
|
57
76
|
__param(0, Path())
|
|
58
77
|
], BackupRepositoryController.prototype, "getRepository", null);
|
|
@@ -10,6 +10,8 @@ export class Subscriber {
|
|
|
10
10
|
#manager;
|
|
11
11
|
#connection;
|
|
12
12
|
#isAlive;
|
|
13
|
+
#cleanupCallbacks = new Set();
|
|
14
|
+
#userId;
|
|
13
15
|
get id() {
|
|
14
16
|
return this.#id;
|
|
15
17
|
}
|
|
@@ -19,13 +21,17 @@ export class Subscriber {
|
|
|
19
21
|
get connection() {
|
|
20
22
|
return this.#connection;
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
get userId() {
|
|
25
|
+
return this.#userId;
|
|
26
|
+
}
|
|
27
|
+
constructor(connection, manager, userId) {
|
|
23
28
|
this.#id = crypto.randomUUID();
|
|
24
29
|
connection.on('close', () => this.clear());
|
|
25
30
|
manager.addSubscriber(this);
|
|
26
31
|
this.#connection = connection;
|
|
27
32
|
this.#manager = manager;
|
|
28
33
|
this.#isAlive = true;
|
|
34
|
+
this.#userId = userId;
|
|
29
35
|
}
|
|
30
36
|
#safeWrite(payload) {
|
|
31
37
|
const ok = this.#connection.write(payload);
|
|
@@ -34,6 +40,10 @@ export class Subscriber {
|
|
|
34
40
|
this.clear();
|
|
35
41
|
}
|
|
36
42
|
}
|
|
43
|
+
onClear(callback) {
|
|
44
|
+
this.#cleanupCallbacks.add(callback);
|
|
45
|
+
return () => this.#cleanupCallbacks.delete(callback);
|
|
46
|
+
}
|
|
37
47
|
broadcast(event, data) {
|
|
38
48
|
if (!this.#isAlive) {
|
|
39
49
|
log.warn('broadcast called on a subscriber that is not alive, but still in memory! Force clear and do nothing');
|
|
@@ -44,32 +54,34 @@ export class Subscriber {
|
|
|
44
54
|
this.#safeWrite(payload);
|
|
45
55
|
}
|
|
46
56
|
clear() {
|
|
47
|
-
this.#isAlive
|
|
48
|
-
|
|
49
|
-
this.#connection.destroy();
|
|
57
|
+
if (!this.#isAlive) {
|
|
58
|
+
return;
|
|
50
59
|
}
|
|
60
|
+
this.#isAlive = false;
|
|
61
|
+
this.#connection.destroy();
|
|
62
|
+
this.#cleanupCallbacks.forEach(cb => cb());
|
|
63
|
+
this.#cleanupCallbacks.clear();
|
|
51
64
|
this.#manager.removeSubscriber(this.id);
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
67
|
export class XoListener extends Listener {
|
|
55
|
-
#type;
|
|
56
68
|
#alarmService;
|
|
57
69
|
constructor(type, eventEmitter, alarmService) {
|
|
58
|
-
super(eventEmitter, ['add', 'update', 'remove']);
|
|
59
|
-
this.#type = type;
|
|
70
|
+
super(eventEmitter, ['add', 'update', 'remove'], type);
|
|
60
71
|
this.#alarmService = alarmService;
|
|
61
72
|
}
|
|
62
|
-
handleData({ fields, event }, object, previousObj) {
|
|
73
|
+
async handleData({ fields, event, subscriber }, object, previousObj) {
|
|
63
74
|
let _object = object;
|
|
64
75
|
let _prevObject = previousObj;
|
|
65
|
-
|
|
76
|
+
///
|
|
77
|
+
if (this.type === 'alarm' || this.type === 'message') {
|
|
66
78
|
const isAlarm = (object) => object !== undefined && 'type' in object && object.type === 'message' && this.#alarmService.isAlarm(object);
|
|
67
79
|
const objectIsAlarm = isAlarm(object);
|
|
68
80
|
const prevObjectIsAlarm = isAlarm(previousObj);
|
|
69
81
|
// If we are in an alarm listener and the objects are messages
|
|
70
82
|
// we clean them to ensure they are not sent via the SSE
|
|
71
83
|
// Same if we are in a message listener and the objects are alarms
|
|
72
|
-
if (this
|
|
84
|
+
if (this.type === 'alarm') {
|
|
73
85
|
_object = objectIsAlarm ? this.#alarmService.parseAlarm(object) : undefined;
|
|
74
86
|
_prevObject = prevObjectIsAlarm ? this.#alarmService.parseAlarm(previousObj) : undefined;
|
|
75
87
|
}
|
|
@@ -81,6 +93,17 @@ export class XoListener extends Listener {
|
|
|
81
93
|
if (_object === undefined && _prevObject === undefined) {
|
|
82
94
|
return;
|
|
83
95
|
}
|
|
96
|
+
const aclEvent = await this.getAclEvent({
|
|
97
|
+
event,
|
|
98
|
+
object: _object,
|
|
99
|
+
previousObject: _prevObject,
|
|
100
|
+
userId: subscriber.userId,
|
|
101
|
+
});
|
|
102
|
+
// If the user has no 'read' privileges for the changes, don't send the update
|
|
103
|
+
if (aclEvent === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
event = aclEvent;
|
|
84
107
|
if (fields !== '*') {
|
|
85
108
|
if (_object !== undefined) {
|
|
86
109
|
_object = pick(_object, fields);
|
|
@@ -94,7 +117,7 @@ export class XoListener extends Listener {
|
|
|
94
117
|
return;
|
|
95
118
|
}
|
|
96
119
|
// if _object === undefined, this means we are on a remove event, so _prevObject will not be undefined
|
|
97
|
-
return { $subscription: this
|
|
120
|
+
return { $subscription: this.type, event, ...(_object ?? _prevObject) };
|
|
98
121
|
}
|
|
99
122
|
}
|
|
100
123
|
export class PingListener extends Listener {
|
|
@@ -105,7 +128,7 @@ export class PingListener extends Listener {
|
|
|
105
128
|
this.eventEmitter.emit('ping');
|
|
106
129
|
}, 1000 * 30);
|
|
107
130
|
}
|
|
108
|
-
handleData() {
|
|
131
|
+
async handleData() {
|
|
109
132
|
return { ping: Date.now() };
|
|
110
133
|
}
|
|
111
134
|
clear() {
|
|
@@ -132,11 +155,6 @@ export class SubscriberManager {
|
|
|
132
155
|
this.#subscribers.delete(id);
|
|
133
156
|
}
|
|
134
157
|
clear() {
|
|
135
|
-
this.#subscribers.forEach(subscriber =>
|
|
136
|
-
if (!subscriber.connection.closed) {
|
|
137
|
-
subscriber.connection.destroy();
|
|
138
|
-
}
|
|
139
|
-
this.#subscribers.delete(subscriber.id);
|
|
140
|
-
});
|
|
158
|
+
this.#subscribers.forEach(subscriber => subscriber.clear());
|
|
141
159
|
}
|
|
142
160
|
}
|
|
@@ -70,12 +70,14 @@ let EventController = class EventController extends Controller {
|
|
|
70
70
|
};
|
|
71
71
|
__decorate([
|
|
72
72
|
Get(''),
|
|
73
|
+
Security('*', ['acl']),
|
|
73
74
|
SuccessResponse(200, 'OK'),
|
|
74
75
|
__param(0, Request())
|
|
75
76
|
], EventController.prototype, "openSseConnection", null);
|
|
76
77
|
__decorate([
|
|
77
78
|
Example(addSubscription),
|
|
78
79
|
Post('{id}/subscriptions'),
|
|
80
|
+
Security('*', ['acl']),
|
|
79
81
|
Middlewares(json()),
|
|
80
82
|
SuccessResponse(createdResp.status, createdResp.description),
|
|
81
83
|
Response(notFoundResp.status, notFoundResp.description),
|
|
@@ -84,6 +86,7 @@ __decorate([
|
|
|
84
86
|
], EventController.prototype, "addSubscription", null);
|
|
85
87
|
__decorate([
|
|
86
88
|
Delete('{id}/subscriptions/{subscriptionId}'),
|
|
89
|
+
Security('*', ['acl']),
|
|
87
90
|
SuccessResponse(noContentResp.status, noContentResp.description),
|
|
88
91
|
Response(notFoundResp.status, notFoundResp.description),
|
|
89
92
|
__param(0, Path()),
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
1
|
import { createLogger } from '@xen-orchestra/log';
|
|
3
2
|
import { PassThrough, pipeline } from 'node:stream';
|
|
4
3
|
import { PingListener, Subscriber, SubscriberManager, XoListener } from './event.class.mjs';
|
|
@@ -50,16 +49,17 @@ export class EventService {
|
|
|
50
49
|
'Cache-Control': 'no-cache, no-transform',
|
|
51
50
|
});
|
|
52
51
|
res.setHeaders(headers);
|
|
53
|
-
const maxRam = this.#restApi.xoApp.config.get('rest-api.
|
|
52
|
+
const maxRam = this.#restApi.xoApp.config.get('rest-api.maxRamAllocatedPerSseClient');
|
|
54
53
|
const connection = new PassThrough({
|
|
55
|
-
highWaterMark: Math.round(
|
|
54
|
+
highWaterMark: Math.round(1024 * 1024 * maxRam),
|
|
56
55
|
});
|
|
57
56
|
pipeline(connection, res, error => {
|
|
58
57
|
if (error?.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
|
|
59
58
|
log.error(error);
|
|
60
59
|
}
|
|
61
60
|
});
|
|
62
|
-
const
|
|
61
|
+
const user = this.#restApi.getCurrentUser();
|
|
62
|
+
const subscriber = new Subscriber(connection, this.#subscriberManager, user.id);
|
|
63
63
|
subscriber.broadcast('init', { id: subscriber.id });
|
|
64
64
|
this.addListenerFor(subscriber.id, { type: 'ping' });
|
|
65
65
|
log.debug(`new SSE subscriber added: ${subscriber.id}`);
|