@adminforth/background-jobs 1.0.1 → 1.1.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/build.log CHANGED
@@ -11,5 +11,5 @@ custom/StateToIcon.vue
11
11
  custom/tsconfig.json
12
12
  custom/utils.ts
13
13
 
14
- sent 11,198 bytes received 134 bytes 22,664.00 bytes/sec
15
- total size is 10,720 speedup is 0.95
14
+ sent 11,414 bytes received 134 bytes 23,096.00 bytes/sec
15
+ total size is 10,936 speedup is 0.95
@@ -1,6 +1,11 @@
1
1
  <template>
2
2
  <div class="w-1vw md:w-64 bg-white border border-gray-200 rounded-md">
3
- <Modal v-for="job in props.jobs" :key="job.id" :beforeOpenFunction="props.closeDropdown">
3
+ <Modal
4
+ v-for="job in props.jobs" :key="job.id"
5
+ :beforeCloseFunction="onBeforeOpen"
6
+ :beforeOpenFunction="onBeforeClose"
7
+ removeFromDomOnClose
8
+ >
4
9
  <template #trigger>
5
10
  <div class="flex items-center w-full px-4 py-3 border-b border-gray-200 hover:bg-gray-50 transition-colors">
6
11
  <div class="flex flex-col w-full max-w-48">
@@ -22,12 +27,10 @@
22
27
  <StateToIcon :job="job" />
23
28
  </div>
24
29
  </template>
25
- <div>
26
- <JobInfoPopup
27
- :job="job"
28
- :meta="meta"
29
- />
30
- </div>
30
+ <JobInfoPopup
31
+ :job="job"
32
+ :meta="meta"
33
+ />
31
34
  </Modal>
32
35
 
33
36
  </div>
@@ -40,6 +43,7 @@ import { getTimeAgoString } from '@/utils';
40
43
  import { ProgressBar, Modal } from '@/afcl';
41
44
  import JobInfoPopup from './JobInfoPopup.vue';
42
45
  import StateToIcon from './StateToIcon.vue';
46
+ import { ref } from 'vue';
43
47
 
44
48
  const props = defineProps<{
45
49
  jobs: IJob[];
@@ -49,5 +53,16 @@ const props = defineProps<{
49
53
  };
50
54
  }>();
51
55
 
56
+
57
+ const isModalOpen = ref(false);
58
+
59
+ function onBeforeOpen() {
60
+ props.closeDropdown();
61
+ }
62
+
63
+ function onBeforeClose() {
64
+ isModalOpen.value = false;
65
+ }
66
+
52
67
 
53
68
  </script>
@@ -41,7 +41,7 @@
41
41
  import type { AdminUser } from 'adminforth';
42
42
  import { onMounted, onUnmounted, ref, computed } from 'vue';
43
43
  import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
44
- import { Tooltip, Modal } from '@/afcl';
44
+ import { Tooltip } from '@/afcl';
45
45
  import { useI18n } from 'vue-i18n';
46
46
  import JobsList from './JobsList.vue';
47
47
  import type { IJob } from './utils';
@@ -1,6 +1,11 @@
1
1
  <template>
2
2
  <div class="w-1vw md:w-64 bg-white border border-gray-200 rounded-md">
3
- <Modal v-for="job in props.jobs" :key="job.id" :beforeOpenFunction="props.closeDropdown">
3
+ <Modal
4
+ v-for="job in props.jobs" :key="job.id"
5
+ :beforeCloseFunction="onBeforeOpen"
6
+ :beforeOpenFunction="onBeforeClose"
7
+ removeFromDomOnClose
8
+ >
4
9
  <template #trigger>
5
10
  <div class="flex items-center w-full px-4 py-3 border-b border-gray-200 hover:bg-gray-50 transition-colors">
6
11
  <div class="flex flex-col w-full max-w-48">
@@ -22,12 +27,10 @@
22
27
  <StateToIcon :job="job" />
23
28
  </div>
24
29
  </template>
25
- <div>
26
- <JobInfoPopup
27
- :job="job"
28
- :meta="meta"
29
- />
30
- </div>
30
+ <JobInfoPopup
31
+ :job="job"
32
+ :meta="meta"
33
+ />
31
34
  </Modal>
32
35
 
33
36
  </div>
@@ -40,6 +43,7 @@ import { getTimeAgoString } from '@/utils';
40
43
  import { ProgressBar, Modal } from '@/afcl';
41
44
  import JobInfoPopup from './JobInfoPopup.vue';
42
45
  import StateToIcon from './StateToIcon.vue';
46
+ import { ref } from 'vue';
43
47
 
44
48
  const props = defineProps<{
45
49
  jobs: IJob[];
@@ -49,5 +53,16 @@ const props = defineProps<{
49
53
  };
50
54
  }>();
51
55
 
56
+
57
+ const isModalOpen = ref(false);
58
+
59
+ function onBeforeOpen() {
60
+ props.closeDropdown();
61
+ }
62
+
63
+ function onBeforeClose() {
64
+ isModalOpen.value = false;
65
+ }
66
+
52
67
 
53
68
  </script>
@@ -41,7 +41,7 @@
41
41
  import type { AdminUser } from 'adminforth';
42
42
  import { onMounted, onUnmounted, ref, computed } from 'vue';
43
43
  import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
44
- import { Tooltip, Modal } from '@/afcl';
44
+ import { Tooltip } from '@/afcl';
45
45
  import { useI18n } from 'vue-i18n';
46
46
  import JobsList from './JobsList.vue';
47
47
  import type { IJob } from './utils';
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { AdminForthPlugin, Filters, Sorts } from "adminforth";
11
11
  import { afLogger } from "adminforth";
12
12
  import pLimit from 'p-limit';
13
13
  import { Level } from 'level';
14
+ import fs from 'fs/promises';
14
15
  export default class extends AdminForthPlugin {
15
16
  constructor(options) {
16
17
  super(options, import.meta.url);
@@ -45,6 +46,30 @@ export default class extends AdminForthPlugin {
45
46
  pluginInstanceId: this.pluginInstanceId,
46
47
  }
47
48
  });
49
+ if (!this.resourceConfig.hooks) {
50
+ this.resourceConfig.hooks = {};
51
+ }
52
+ if (!this.resourceConfig.hooks.delete) {
53
+ this.resourceConfig.hooks.delete = {};
54
+ }
55
+ if (!this.resourceConfig.hooks.delete.beforeSave) {
56
+ this.resourceConfig.hooks.delete.beforeSave = [];
57
+ }
58
+ this.resourceConfig.hooks.delete.beforeSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ record, recordId }) {
59
+ const levelDbPath = `${this.options.levelDbPath || './background-jobs-dbs/'}job_${recordId}`;
60
+ const jobLevelDb = this.levelDbInstances[recordId];
61
+ //close level db instance if it's open and delete the level db folder for the job
62
+ if (jobLevelDb) {
63
+ yield jobLevelDb.close();
64
+ delete this.levelDbInstances[recordId];
65
+ }
66
+ //delete level db folder for the job
67
+ yield fs.rm(levelDbPath, {
68
+ recursive: true,
69
+ force: true,
70
+ });
71
+ return { ok: true };
72
+ }));
48
73
  });
49
74
  }
50
75
  checkIfFieldInResource(resourceConfig, fieldName, fieldString) {
@@ -174,6 +199,7 @@ export default class extends AdminForthPlugin {
174
199
  }
175
200
  //define the setTaskStateField and getTaskStateField functions to pass to the task
176
201
  const setTaskStateField = (state) => __awaiter(this, void 0, void 0, function* () {
202
+ this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, state });
177
203
  yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
178
204
  });
179
205
  const getTaskStateField = () => __awaiter(this, void 0, void 0, function* () {
@@ -214,12 +240,14 @@ export default class extends AdminForthPlugin {
214
240
  if (lastJobStatus !== 'CANCELLED' && failedTasks === 0) {
215
241
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
216
242
  [this.options.statusField]: 'DONE',
243
+ [this.options.finishedAtField]: (new Date()).toISOString(),
217
244
  });
218
245
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
219
246
  }
220
247
  else if (failedTasks > 0) {
221
248
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
222
249
  [this.options.statusField]: 'DONE_WITH_ERRORS',
250
+ [this.options.finishedAtField]: (new Date()).toISOString(),
223
251
  });
224
252
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
225
253
  }
@@ -326,11 +354,13 @@ export default class extends AdminForthPlugin {
326
354
  return __awaiter(this, void 0, void 0, function* () {
327
355
  // optional method where you can safely check field types after database discovery was performed
328
356
  this.checkIfFieldInResource(resourceConfig, this.options.createdAtField, 'createdAtField');
357
+ this.checkIfFieldInResource(resourceConfig, this.options.finishedAtField, 'finishedAtField');
329
358
  this.checkIfFieldInResource(resourceConfig, this.options.startedByField, 'startedByField');
330
359
  this.checkIfFieldInResource(resourceConfig, this.options.stateField, 'stateField');
331
360
  this.checkIfFieldInResource(resourceConfig, this.options.progressField, 'progressField');
332
361
  this.checkIfFieldInResource(resourceConfig, this.options.statusField, 'statusField');
333
362
  this.checkIfFieldInResource(resourceConfig, this.options.nameField, 'nameField');
363
+ this.checkIfFieldInResource(resourceConfig, this.options.jobHandlerField, 'jobHandlerField');
334
364
  //Add temp delay to make sure, that all resources active. Probably should be fixed
335
365
  yield new Promise(resolve => setTimeout(resolve, 1000));
336
366
  this.processAllUnfinishedJobs();
@@ -374,6 +404,7 @@ export default class extends AdminForthPlugin {
374
404
  try {
375
405
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
376
406
  [this.options.statusField]: 'CANCELLED',
407
+ [this.options.finishedAtField]: (new Date()).toISOString(),
377
408
  });
378
409
  this.adminforth.websocket.publish('/background-jobs', {
379
410
  jobId,
@@ -399,6 +430,7 @@ export default class extends AdminForthPlugin {
399
430
  else {
400
431
  try {
401
432
  jobLevelDb = new Level(levelDbPath, { valueEncoding: 'json' });
433
+ this.levelDbInstances[jobId] = jobLevelDb;
402
434
  }
403
435
  catch (error) {
404
436
  return { ok: false, message: `Failed to access tasks for job with id ${jobId}.` };
package/index.ts CHANGED
@@ -4,6 +4,7 @@ import type { PluginOptions } from './types.js';
4
4
  import { afLogger } from "adminforth";
5
5
  import pLimit from 'p-limit';
6
6
  import { Level } from 'level';
7
+ import fs from 'fs/promises';
7
8
 
8
9
  type TaskStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
9
10
  type setStateFieldParams = (state: Record<string, any>) => void;
@@ -48,6 +49,35 @@ export default class extends AdminForthPlugin {
48
49
  pluginInstanceId: this.pluginInstanceId,
49
50
  }
50
51
  });
52
+
53
+ if (!this.resourceConfig.hooks) {
54
+ this.resourceConfig.hooks = {};
55
+ }
56
+ if (!this.resourceConfig.hooks.delete) {
57
+ this.resourceConfig.hooks.delete = {};
58
+ }
59
+ if (!this.resourceConfig.hooks.delete.beforeSave) {
60
+ this.resourceConfig.hooks.delete.beforeSave = [];
61
+ }
62
+ this.resourceConfig.hooks.delete.beforeSave.push(async ({record, recordId}: {record: any, recordId: any}) => {
63
+
64
+ const levelDbPath = `${this.options.levelDbPath || './background-jobs-dbs/'}job_${recordId}`;
65
+ const jobLevelDb = this.levelDbInstances[recordId];
66
+
67
+ //close level db instance if it's open and delete the level db folder for the job
68
+ if (jobLevelDb) {
69
+ await jobLevelDb.close();
70
+ delete this.levelDbInstances[recordId];
71
+ }
72
+
73
+ //delete level db folder for the job
74
+ await fs.rm(levelDbPath, {
75
+ recursive: true,
76
+ force: true,
77
+ });
78
+
79
+ return {ok: true};
80
+ })
51
81
  }
52
82
 
53
83
  private checkIfFieldInResource(resourceConfig: AdminForthResource, fieldName: string, fieldString?: string) {
@@ -198,6 +228,7 @@ export default class extends AdminForthPlugin {
198
228
 
199
229
  //define the setTaskStateField and getTaskStateField functions to pass to the task
200
230
  const setTaskStateField = async (state: Record<string, any>) => {
231
+ this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, state });
201
232
  await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
202
233
  }
203
234
  const getTaskStateField = async () => {
@@ -242,11 +273,13 @@ export default class extends AdminForthPlugin {
242
273
  if (lastJobStatus !== 'CANCELLED' && failedTasks === 0) {
243
274
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
244
275
  [this.options.statusField]: 'DONE',
276
+ [this.options.finishedAtField]: (new Date()).toISOString(),
245
277
  })
246
278
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
247
279
  } else if (failedTasks > 0) {
248
280
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
249
281
  [this.options.statusField]: 'DONE_WITH_ERRORS',
282
+ [this.options.finishedAtField]: (new Date()).toISOString(),
250
283
  })
251
284
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
252
285
  }
@@ -349,11 +382,13 @@ export default class extends AdminForthPlugin {
349
382
  async validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
350
383
  // optional method where you can safely check field types after database discovery was performed
351
384
  this.checkIfFieldInResource(resourceConfig, this.options.createdAtField, 'createdAtField');
385
+ this.checkIfFieldInResource(resourceConfig, this.options.finishedAtField, 'finishedAtField');
352
386
  this.checkIfFieldInResource(resourceConfig, this.options.startedByField, 'startedByField');
353
387
  this.checkIfFieldInResource(resourceConfig, this.options.stateField, 'stateField');
354
388
  this.checkIfFieldInResource(resourceConfig, this.options.progressField, 'progressField');
355
389
  this.checkIfFieldInResource(resourceConfig, this.options.statusField, 'statusField');
356
390
  this.checkIfFieldInResource(resourceConfig, this.options.nameField, 'nameField');
391
+ this.checkIfFieldInResource(resourceConfig, this.options.jobHandlerField, 'jobHandlerField');
357
392
 
358
393
 
359
394
  //Add temp delay to make sure, that all resources active. Probably should be fixed
@@ -402,6 +437,7 @@ export default class extends AdminForthPlugin {
402
437
  try {
403
438
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
404
439
  [this.options.statusField]: 'CANCELLED',
440
+ [this.options.finishedAtField]: (new Date()).toISOString(),
405
441
  });
406
442
  this.adminforth.websocket.publish('/background-jobs', {
407
443
  jobId,
@@ -426,6 +462,7 @@ export default class extends AdminForthPlugin {
426
462
  } else {
427
463
  try {
428
464
  jobLevelDb = new Level(levelDbPath, { valueEncoding: 'json' });
465
+ this.levelDbInstances[jobId] = jobLevelDb;
429
466
  } catch (error) {
430
467
  return { ok: false, message: `Failed to access tasks for job with id ${jobId}.` };
431
468
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/background-jobs",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
package/types.ts CHANGED
@@ -1,12 +1,13 @@
1
1
 
2
2
  export interface PluginOptions {
3
3
  createdAtField: string;
4
+ finishedAtField: string;
4
5
  startedByField: string;
5
6
  stateField: string;
6
7
  progressField: string;
7
8
  statusField: string;
8
- nameField?: string;
9
- jobHandlerField?: string;
9
+ nameField: string;
10
+ jobHandlerField: string;
10
11
 
11
12
  /**
12
13
  * Path to the level db folder. If not provided, a default path is ./background-jobs-dbs/