@adminforth/background-jobs 1.0.1
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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/LICENSE +21 -0
- package/build.log +15 -0
- package/custom/JobInfoPopup.vue +110 -0
- package/custom/JobsList.vue +53 -0
- package/custom/NavbarJobs.vue +154 -0
- package/custom/StateToIcon.vue +40 -0
- package/custom/tsconfig.json +16 -0
- package/custom/utils.ts +9 -0
- package/dist/custom/JobInfoPopup.vue +110 -0
- package/dist/custom/JobsList.vue +53 -0
- package/dist/custom/NavbarJobs.vue +154 -0
- package/dist/custom/StateToIcon.vue +40 -0
- package/dist/custom/tsconfig.json +16 -0
- package/dist/custom/utils.ts +9 -0
- package/dist/index.js +433 -0
- package/dist/types.js +1 -0
- package/index.ts +459 -0
- package/package.json +27 -0
- package/tsconfig.json +13 -0
- package/types.ts +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AdminForthPlugin, Filters, Sorts } from "adminforth";
|
|
11
|
+
import { afLogger } from "adminforth";
|
|
12
|
+
import pLimit from 'p-limit';
|
|
13
|
+
import { Level } from 'level';
|
|
14
|
+
export default class extends AdminForthPlugin {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
super(options, import.meta.url);
|
|
17
|
+
this.taskHandlers = {};
|
|
18
|
+
this.jobCustomComponents = {};
|
|
19
|
+
this.jobParallelLimits = {};
|
|
20
|
+
this.levelDbInstances = {};
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.shouldHaveSingleInstancePerWholeApp = () => true;
|
|
23
|
+
}
|
|
24
|
+
getResourcePk() {
|
|
25
|
+
var _a;
|
|
26
|
+
const resourcePk = (_a = this.resourceConfig.columns.find(c => c.primaryKey)) === null || _a === void 0 ? void 0 : _a.name;
|
|
27
|
+
return resourcePk;
|
|
28
|
+
}
|
|
29
|
+
getResourceId() {
|
|
30
|
+
return this.resourceConfig.resourceId;
|
|
31
|
+
}
|
|
32
|
+
modifyResourceConfig(adminforth, resourceConfig) {
|
|
33
|
+
const _super = Object.create(null, {
|
|
34
|
+
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
35
|
+
});
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
_super.modifyResourceConfig.call(this, adminforth, resourceConfig);
|
|
39
|
+
if (!((_b = (_a = adminforth.config.customization) === null || _a === void 0 ? void 0 : _a.globalInjections) === null || _b === void 0 ? void 0 : _b.header)) {
|
|
40
|
+
adminforth.config.customization.globalInjections.header = [];
|
|
41
|
+
}
|
|
42
|
+
(adminforth.config.customization.globalInjections.header).push({
|
|
43
|
+
file: this.componentPath('NavbarJobs.vue'),
|
|
44
|
+
meta: {
|
|
45
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
checkIfFieldInResource(resourceConfig, fieldName, fieldString) {
|
|
51
|
+
if (!fieldName) {
|
|
52
|
+
throw new Error(`Field name for ${fieldString} is not provided. Please check your plugin options.`);
|
|
53
|
+
}
|
|
54
|
+
const fieldInConfig = resourceConfig.columns.find(f => f.name === fieldName);
|
|
55
|
+
if (!fieldInConfig) {
|
|
56
|
+
throw new Error(`Field ${fieldName} not found in resource config. Please check your plugin options.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
createLevelDbTaskRecord(levelDb, taskId, initialState) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
//create record in level db with task id as key and initial state as value and status SCHEDULED
|
|
62
|
+
yield levelDb.put(taskId, JSON.stringify({ state: initialState, status: 'SCHEDULED' }));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
setLevelDbTaskStateField(levelDb, taskId, state) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
//update record in level db with task id as key and new state as value
|
|
68
|
+
const status = yield this.getLevelDbTaskStatusField(levelDb, taskId);
|
|
69
|
+
yield levelDb.del(taskId);
|
|
70
|
+
yield levelDb.put(taskId, JSON.stringify({ state, status }));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
setLevelDbTaskStatusField(levelDb, taskId, status) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
const state = yield this.getLevelDbTaskStateField(levelDb, taskId);
|
|
76
|
+
yield levelDb.del(taskId);
|
|
77
|
+
yield levelDb.put(taskId, JSON.stringify({ state, status }));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
getLevelDbTaskStateField(levelDb, taskId) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
//get record from level db with task id as key and return the value of the key in the state
|
|
83
|
+
const state = yield levelDb.get(taskId);
|
|
84
|
+
if (state) {
|
|
85
|
+
const parsedState = JSON.parse(state);
|
|
86
|
+
return parsedState.state;
|
|
87
|
+
}
|
|
88
|
+
return Promise.resolve(null);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
getLevelDbTaskStatusField(levelDb, taskId) {
|
|
92
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
const state = yield levelDb.get(taskId);
|
|
94
|
+
if (state) {
|
|
95
|
+
const parsedState = JSON.parse(state);
|
|
96
|
+
return parsedState.status;
|
|
97
|
+
}
|
|
98
|
+
return Promise.resolve(null);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
registerTaskHandler(jobHandlerName, handler, customComponent, parrallelLimit = 3) {
|
|
102
|
+
//register the handler in a map with jobHandlerName as key and handler as value
|
|
103
|
+
this.taskHandlers[jobHandlerName] = handler;
|
|
104
|
+
this.jobParallelLimits[jobHandlerName] = parrallelLimit;
|
|
105
|
+
if (customComponent) {
|
|
106
|
+
this.jobCustomComponents[jobHandlerName] = customComponent;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
startNewJob(jobName_1, adminUser_1, tasks_1) {
|
|
110
|
+
return __awaiter(this, arguments, void 0, function* (jobName, adminUser, tasks, initialFields = {}, jobHandlerName) {
|
|
111
|
+
const handleTask = this.taskHandlers[jobHandlerName];
|
|
112
|
+
if (!handleTask) {
|
|
113
|
+
throw new Error(`No handler registered for jobHandler ${jobHandlerName}. Please register a handler using the registerTaskHandler method before starting a job with this jobHandler.`);
|
|
114
|
+
}
|
|
115
|
+
const customComponent = this.jobCustomComponents[jobHandlerName];
|
|
116
|
+
const parrallelLimit = this.jobParallelLimits[jobHandlerName] || 3;
|
|
117
|
+
//create a record for the job in the database with status in progress
|
|
118
|
+
const objectToSave = {
|
|
119
|
+
[this.options.nameField]: jobName,
|
|
120
|
+
[this.options.startedByField]: adminUser.pk,
|
|
121
|
+
[this.options.stateField]: JSON.stringify(initialFields),
|
|
122
|
+
[this.options.progressField]: 0,
|
|
123
|
+
[this.options.statusField]: 'IN_PROGRESS',
|
|
124
|
+
[this.options.jobHandlerField]: jobHandlerName,
|
|
125
|
+
};
|
|
126
|
+
const creationResult = yield this.adminforth.resource(this.getResourceId()).create(objectToSave);
|
|
127
|
+
let createdRecord = null;
|
|
128
|
+
if (creationResult.ok === true) {
|
|
129
|
+
createdRecord = creationResult.createdRecord;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
throw new Error(`Failed to create a record for the job. Error: ${creationResult.error}`);
|
|
133
|
+
}
|
|
134
|
+
const jobId = createdRecord[this.getResourcePk()];
|
|
135
|
+
this.adminforth.websocket.publish('/background-jobs', {
|
|
136
|
+
jobId,
|
|
137
|
+
status: 'IN_PROGRESS',
|
|
138
|
+
name: jobName,
|
|
139
|
+
progress: 0,
|
|
140
|
+
createdAt: createdRecord[this.options.createdAtField],
|
|
141
|
+
customComponent,
|
|
142
|
+
});
|
|
143
|
+
//create a level db instance for the job with name as jobId
|
|
144
|
+
const jobLevelDb = new Level(`${this.options.levelDbPath || './background-jobs-dbs/'}job_${jobId}`, { valueEncoding: 'json' });
|
|
145
|
+
this.levelDbInstances[jobId] = jobLevelDb;
|
|
146
|
+
const limit2 = pLimit(parrallelLimit);
|
|
147
|
+
const createTaskRecordsPromises = tasks.map((task, index) => {
|
|
148
|
+
return limit2(() => this.createLevelDbTaskRecord(jobLevelDb, index.toString(), task.state));
|
|
149
|
+
});
|
|
150
|
+
yield Promise.all(createTaskRecordsPromises);
|
|
151
|
+
this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit) {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
const totalTasks = tasks.length;
|
|
157
|
+
let completedTasks = 0;
|
|
158
|
+
let failedTasks = 0;
|
|
159
|
+
let lastJobStatus = 'IN_PROGRESS';
|
|
160
|
+
const taskHandler = (taskIndex, task) => __awaiter(this, void 0, void 0, function* () {
|
|
161
|
+
if (task.skip) {
|
|
162
|
+
completedTasks = yield this.handleFinishTask(completedTasks, totalTasks, jobId, true);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (lastJobStatus === 'CANCELLED') {
|
|
166
|
+
afLogger.info(`Job ${jobId} was cancelled. Skipping task ${taskIndex}.`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const currentJobStatus = yield this.getLastJobStatus(jobId);
|
|
170
|
+
if (currentJobStatus === 'CANCELLED') {
|
|
171
|
+
lastJobStatus = currentJobStatus;
|
|
172
|
+
afLogger.info(`Job ${jobId} was cancelled. Skipping task ${taskIndex}.`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
//define the setTaskStateField and getTaskStateField functions to pass to the task
|
|
176
|
+
const setTaskStateField = (state) => __awaiter(this, void 0, void 0, function* () {
|
|
177
|
+
yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
|
|
178
|
+
});
|
|
179
|
+
const getTaskStateField = () => __awaiter(this, void 0, void 0, function* () {
|
|
180
|
+
return yield this.getLevelDbTaskStateField(jobLevelDb, taskIndex.toString());
|
|
181
|
+
});
|
|
182
|
+
yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'IN_PROGRESS');
|
|
183
|
+
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "IN_PROGRESS" });
|
|
184
|
+
//handling the task
|
|
185
|
+
try {
|
|
186
|
+
yield handleTask({ setTaskStateField, getTaskStateField });
|
|
187
|
+
//Set task status to completed in level db
|
|
188
|
+
yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
|
|
189
|
+
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "DONE" });
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${error}`);
|
|
193
|
+
yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
|
|
194
|
+
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
|
|
195
|
+
failedTasks++;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
//Update progress
|
|
200
|
+
const currentJobStatus = yield this.getLastJobStatus(jobId);
|
|
201
|
+
if (currentJobStatus === 'CANCELLED') {
|
|
202
|
+
lastJobStatus = currentJobStatus;
|
|
203
|
+
afLogger.debug(`Job ${jobId} was cancelled during processing of task ${taskIndex}. Progress will not be updated.`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
completedTasks = yield this.handleFinishTask(completedTasks, totalTasks, jobId);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const limit = pLimit(parrallelLimit);
|
|
210
|
+
const tasksToExecute = tasks.map((task, taskIndex) => {
|
|
211
|
+
return limit(() => taskHandler(taskIndex, task));
|
|
212
|
+
});
|
|
213
|
+
yield Promise.all(tasksToExecute);
|
|
214
|
+
if (lastJobStatus !== 'CANCELLED' && failedTasks === 0) {
|
|
215
|
+
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
216
|
+
[this.options.statusField]: 'DONE',
|
|
217
|
+
});
|
|
218
|
+
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
|
|
219
|
+
}
|
|
220
|
+
else if (failedTasks > 0) {
|
|
221
|
+
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
222
|
+
[this.options.statusField]: 'DONE_WITH_ERRORS',
|
|
223
|
+
});
|
|
224
|
+
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
getLastJobStatus(jobId) {
|
|
229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
const currentJobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
231
|
+
const currentJobStatus = currentJobRecord[this.options.statusField];
|
|
232
|
+
return currentJobStatus;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
handleFinishTask(completedTasks_1, totalTasks_1, jobId_1) {
|
|
236
|
+
return __awaiter(this, arguments, void 0, function* (completedTasks, totalTasks, jobId, wasTaskSkipped = false) {
|
|
237
|
+
completedTasks++;
|
|
238
|
+
if (wasTaskSkipped) {
|
|
239
|
+
return completedTasks;
|
|
240
|
+
}
|
|
241
|
+
const progress = Math.round((completedTasks / totalTasks) * 100);
|
|
242
|
+
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
243
|
+
[this.options.progressField]: progress,
|
|
244
|
+
});
|
|
245
|
+
this.adminforth.websocket.publish('/background-jobs', { jobId, progress });
|
|
246
|
+
return completedTasks;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
runProcessingUnfinishedTasks(job) {
|
|
250
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const levelDbPath = `${this.options.levelDbPath || './background-jobs-dbs/'}job_${job[this.getResourcePk()]}`;
|
|
252
|
+
const jobLevelDb = new Level(levelDbPath, { valueEncoding: 'json' });
|
|
253
|
+
this.levelDbInstances[job[this.getResourcePk()]] = jobLevelDb;
|
|
254
|
+
const jobHandlerName = job[this.options.jobHandlerField];
|
|
255
|
+
const handleTask = this.taskHandlers[jobHandlerName];
|
|
256
|
+
if (!handleTask) {
|
|
257
|
+
afLogger.error(`No handler registered for jobHandler ${jobHandlerName}. Cannot process unfinished tasks for job ${job[this.getResourcePk()]}.`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const parrallelLimit = this.jobParallelLimits[jobHandlerName] || 3;
|
|
261
|
+
const unfinishedTasks = [];
|
|
262
|
+
let taskIndex = 0;
|
|
263
|
+
while (true) {
|
|
264
|
+
const taskData = yield jobLevelDb.get(taskIndex.toString());
|
|
265
|
+
if (!taskData) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
let parsedTaskData;
|
|
269
|
+
try {
|
|
270
|
+
parsedTaskData = JSON.parse(taskData);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
afLogger.error(`Error parsing task data for task ${taskIndex} of job ${job[this.getResourcePk()]}: ${error}`);
|
|
274
|
+
taskIndex++;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (parsedTaskData.status === 'IN_PROGRESS' || parsedTaskData.status === 'SCHEDULED') {
|
|
278
|
+
unfinishedTasks.push({ state: parsedTaskData.state });
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
unfinishedTasks.push({ state: parsedTaskData.state, skip: true });
|
|
282
|
+
}
|
|
283
|
+
taskIndex++;
|
|
284
|
+
}
|
|
285
|
+
yield this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
setJobField(jobId, key, value) {
|
|
289
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
290
|
+
const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
291
|
+
const state = jobRecord[this.options.stateField];
|
|
292
|
+
const parsedState = JSON.parse(state);
|
|
293
|
+
parsedState[key] = value;
|
|
294
|
+
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
295
|
+
[this.options.stateField]: JSON.stringify(parsedState),
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
getJobField(jobId, key) {
|
|
300
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
301
|
+
const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
302
|
+
const state = jobRecord[this.options.stateField];
|
|
303
|
+
const parsedState = JSON.parse(state);
|
|
304
|
+
return parsedState[key];
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
getJobState(jobId) {
|
|
308
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
309
|
+
const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
310
|
+
const state = jobRecord[this.options.stateField];
|
|
311
|
+
return JSON.parse(state);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
processAllUnfinishedJobs() {
|
|
315
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
316
|
+
const resourceId = this.getResourceId();
|
|
317
|
+
const unprocessedJobs = yield this.adminforth.resource(resourceId).list(Filters.EQ(this.options.statusField, 'IN_PROGRESS'));
|
|
318
|
+
for (const job of unprocessedJobs) {
|
|
319
|
+
const jobName = job[this.options.nameField];
|
|
320
|
+
afLogger.info(`Processing unfinished job with name ${jobName} on startup.`);
|
|
321
|
+
this.runProcessingUnfinishedTasks(job);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
326
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
327
|
+
// optional method where you can safely check field types after database discovery was performed
|
|
328
|
+
this.checkIfFieldInResource(resourceConfig, this.options.createdAtField, 'createdAtField');
|
|
329
|
+
this.checkIfFieldInResource(resourceConfig, this.options.startedByField, 'startedByField');
|
|
330
|
+
this.checkIfFieldInResource(resourceConfig, this.options.stateField, 'stateField');
|
|
331
|
+
this.checkIfFieldInResource(resourceConfig, this.options.progressField, 'progressField');
|
|
332
|
+
this.checkIfFieldInResource(resourceConfig, this.options.statusField, 'statusField');
|
|
333
|
+
this.checkIfFieldInResource(resourceConfig, this.options.nameField, 'nameField');
|
|
334
|
+
//Add temp delay to make sure, that all resources active. Probably should be fixed
|
|
335
|
+
yield new Promise(resolve => setTimeout(resolve, 1000));
|
|
336
|
+
this.processAllUnfinishedJobs();
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
instanceUniqueRepresentation(pluginOptions) {
|
|
340
|
+
return `BackgroundJobsPlugin`;
|
|
341
|
+
}
|
|
342
|
+
setupEndpoints(server) {
|
|
343
|
+
server.endpoint({
|
|
344
|
+
method: 'POST',
|
|
345
|
+
path: `/plugin/${this.pluginInstanceId}/get-list-of-jobs`,
|
|
346
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
347
|
+
const user = adminUser;
|
|
348
|
+
const startedByField = this.options.startedByField;
|
|
349
|
+
const resourcePk = this.getResourcePk();
|
|
350
|
+
const listOfJobs = yield this.adminforth.resource(this.resourceConfig.resourceId).list(Filters.EQ(startedByField, user.pk), 100, 0, Sorts.DESC(this.options.createdAtField));
|
|
351
|
+
const jobsToReturn = listOfJobs.map(job => {
|
|
352
|
+
return {
|
|
353
|
+
id: job[resourcePk],
|
|
354
|
+
name: job[this.options.nameField],
|
|
355
|
+
createdAt: job[this.options.createdAtField],
|
|
356
|
+
status: job[this.options.statusField],
|
|
357
|
+
progress: job[this.options.progressField],
|
|
358
|
+
customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
return { jobs: jobsToReturn };
|
|
362
|
+
})
|
|
363
|
+
});
|
|
364
|
+
server.endpoint({
|
|
365
|
+
method: 'POST',
|
|
366
|
+
path: `/plugin/${this.pluginInstanceId}/cancel-job`,
|
|
367
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
368
|
+
const jobId = body.jobId;
|
|
369
|
+
const currentJob = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
370
|
+
const oldStatus = currentJob[this.options.statusField];
|
|
371
|
+
if (oldStatus === 'DONE' || oldStatus === 'DONE_WITH_ERRORS' || oldStatus === 'CANCELLED') {
|
|
372
|
+
return { ok: false, message: `Cannot cancel a job with status ${oldStatus}.` };
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
376
|
+
[this.options.statusField]: 'CANCELLED',
|
|
377
|
+
});
|
|
378
|
+
this.adminforth.websocket.publish('/background-jobs', {
|
|
379
|
+
jobId,
|
|
380
|
+
status: 'CANCELLED',
|
|
381
|
+
});
|
|
382
|
+
return { ok: true };
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
return { ok: false, message: `Failed to cancel job with id ${jobId}.` };
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
});
|
|
389
|
+
server.endpoint({
|
|
390
|
+
method: 'POST',
|
|
391
|
+
path: `/plugin/${this.pluginInstanceId}/get-tasks`,
|
|
392
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
393
|
+
const { jobId, limit, offset } = body;
|
|
394
|
+
const levelDbPath = `${this.options.levelDbPath || './background-jobs-dbs/'}job_${jobId}`;
|
|
395
|
+
let jobLevelDb;
|
|
396
|
+
if (this.levelDbInstances[jobId]) {
|
|
397
|
+
jobLevelDb = this.levelDbInstances[jobId];
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
try {
|
|
401
|
+
jobLevelDb = new Level(levelDbPath, { valueEncoding: 'json' });
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
return { ok: false, message: `Failed to access tasks for job with id ${jobId}.` };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const tasks = [];
|
|
408
|
+
let taskIndex = 0 + offset;
|
|
409
|
+
while (true) {
|
|
410
|
+
if (limit && tasks.length >= limit) {
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
const taskData = yield jobLevelDb.get(taskIndex.toString());
|
|
414
|
+
if (!taskData) {
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
let parsedTaskData;
|
|
418
|
+
try {
|
|
419
|
+
parsedTaskData = JSON.parse(taskData);
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
afLogger.error(`Error parsing task data for task ${taskIndex} of job ${jobId}: ${error}`);
|
|
423
|
+
taskIndex++;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
tasks.push(parsedTaskData);
|
|
427
|
+
taskIndex++;
|
|
428
|
+
}
|
|
429
|
+
return { ok: true, tasks };
|
|
430
|
+
})
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|