@adminforth/background-jobs 1.2.4 → 1.4.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,734 bytes received 134 bytes 23,736.00 bytes/sec
15
- total size is 11,249 speedup is 0.95
14
+ sent 12,501 bytes received 134 bytes 25,270.00 bytes/sec
15
+ total size is 12,016 speedup is 0.95
@@ -1,14 +1,26 @@
1
1
  <template>
2
2
  <div class="flex flex-col w-full min-w-96">
3
3
  <div class="flex items-center mb-1">
4
- <h2 class="text-lg font-semibold dark:text-white">{{ job.name }}</h2>
5
- <p class="ml-2 text-xs text-gray-600 dark:text-gray-200 h-full">{{ t('Created:') }} {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
4
+ <div class="flex flex-col items-start">
5
+ <h2 class="text-lg font-semibold dark:text-white">{{ job.name }}</h2>
6
+ <Tooltip>
7
+ <p class="text-xs text-gray-600 dark:text-gray-200 h-full">{{ t('Created:') }} {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
8
+ <template #tooltip>
9
+ {{ t('Created at:') }} {{ new Date(job.createdAt).toLocaleString() }}
10
+ </template>
11
+ </Tooltip>
12
+ </div>
6
13
  <div class="ml-auto flex flex-col items-start">
7
14
  <div class="flex items-center">
8
15
  <p class=" text-gray-800 dark:text-white h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
9
16
  <StateToIcon :job="job" />
10
17
  </div>
11
- <p class="text-xs text-gray-600 dark:text-gray-200 h-full" v-if="job.finishedAt"> {{ t('Finished:') }} {{ getTimeAgoString(new Date(job.finishedAt)) }}</p>
18
+ <Tooltip v-if="job.finishedAt">
19
+ <p class="text-xs text-gray-600 dark:text-gray-200 h-full"> {{ t('Finished:') }} {{ getTimeAgoString(new Date(job.finishedAt)) }}</p>
20
+ <template #tooltip>
21
+ {{ t('Finished at:') }} {{ new Date(job.finishedAt).toLocaleString() }}
22
+ </template>
23
+ </Tooltip>
12
24
  </div>
13
25
  </div>
14
26
  <div class="flex items-center gap-4 w-full">
@@ -40,7 +52,7 @@
40
52
 
41
53
  <script setup lang="ts">
42
54
  import type { IJob } from './utils';
43
- import { ProgressBar, Button } from '@/afcl';
55
+ import { ProgressBar, Button, Tooltip } from '@/afcl';
44
56
  import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils';
45
57
  import { useI18n } from 'vue-i18n';
46
58
  import StateToIcon from './StateToIcon.vue';
@@ -94,11 +94,21 @@
94
94
  if (data.progress !== undefined) {
95
95
  jobs.value[jobIndex].progress = data.progress;
96
96
  }
97
+ if (data.finishedAt) {
98
+ jobs.value[jobIndex].finishedAt = data.finishedAt;
99
+ }
100
+ if (data.state) {
101
+ jobs.value[jobIndex].state = {
102
+ ...jobs.value[jobIndex].state,
103
+ ...data.state,
104
+ };
105
+ }
97
106
  } else {
98
107
  jobs.value.unshift({
99
108
  id: data.jobId,
100
109
  name: data.name || 'Unknown Job',
101
110
  status: data.status || 'IN_PROGRESS',
111
+ state: data.state || {},
102
112
  progress: data.progress || 0,
103
113
  createdAt: data.createdAt || new Date().toISOString(),
104
114
  customComponent: data.customComponent,
package/custom/utils.ts CHANGED
@@ -3,6 +3,7 @@ export interface IJob {
3
3
  id: string;
4
4
  name: string;
5
5
  status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
6
+ state: Record<string, any>;
6
7
  progress: number; // 0 to 100
7
8
  createdAt: Date;
8
9
  finishedAt?: Date;
@@ -1,14 +1,26 @@
1
1
  <template>
2
2
  <div class="flex flex-col w-full min-w-96">
3
3
  <div class="flex items-center mb-1">
4
- <h2 class="text-lg font-semibold dark:text-white">{{ job.name }}</h2>
5
- <p class="ml-2 text-xs text-gray-600 dark:text-gray-200 h-full">{{ t('Created:') }} {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
4
+ <div class="flex flex-col items-start">
5
+ <h2 class="text-lg font-semibold dark:text-white">{{ job.name }}</h2>
6
+ <Tooltip>
7
+ <p class="text-xs text-gray-600 dark:text-gray-200 h-full">{{ t('Created:') }} {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
8
+ <template #tooltip>
9
+ {{ t('Created at:') }} {{ new Date(job.createdAt).toLocaleString() }}
10
+ </template>
11
+ </Tooltip>
12
+ </div>
6
13
  <div class="ml-auto flex flex-col items-start">
7
14
  <div class="flex items-center">
8
15
  <p class=" text-gray-800 dark:text-white h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
9
16
  <StateToIcon :job="job" />
10
17
  </div>
11
- <p class="text-xs text-gray-600 dark:text-gray-200 h-full" v-if="job.finishedAt"> {{ t('Finished:') }} {{ getTimeAgoString(new Date(job.finishedAt)) }}</p>
18
+ <Tooltip v-if="job.finishedAt">
19
+ <p class="text-xs text-gray-600 dark:text-gray-200 h-full"> {{ t('Finished:') }} {{ getTimeAgoString(new Date(job.finishedAt)) }}</p>
20
+ <template #tooltip>
21
+ {{ t('Finished at:') }} {{ new Date(job.finishedAt).toLocaleString() }}
22
+ </template>
23
+ </Tooltip>
12
24
  </div>
13
25
  </div>
14
26
  <div class="flex items-center gap-4 w-full">
@@ -40,7 +52,7 @@
40
52
 
41
53
  <script setup lang="ts">
42
54
  import type { IJob } from './utils';
43
- import { ProgressBar, Button } from '@/afcl';
55
+ import { ProgressBar, Button, Tooltip } from '@/afcl';
44
56
  import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils';
45
57
  import { useI18n } from 'vue-i18n';
46
58
  import StateToIcon from './StateToIcon.vue';
@@ -94,11 +94,21 @@
94
94
  if (data.progress !== undefined) {
95
95
  jobs.value[jobIndex].progress = data.progress;
96
96
  }
97
+ if (data.finishedAt) {
98
+ jobs.value[jobIndex].finishedAt = data.finishedAt;
99
+ }
100
+ if (data.state) {
101
+ jobs.value[jobIndex].state = {
102
+ ...jobs.value[jobIndex].state,
103
+ ...data.state,
104
+ };
105
+ }
97
106
  } else {
98
107
  jobs.value.unshift({
99
108
  id: data.jobId,
100
109
  name: data.name || 'Unknown Job',
101
110
  status: data.status || 'IN_PROGRESS',
111
+ state: data.state || {},
102
112
  progress: data.progress || 0,
103
113
  createdAt: data.createdAt || new Date().toISOString(),
104
114
  customComponent: data.customComponent,
@@ -3,6 +3,7 @@ export interface IJob {
3
3
  id: string;
4
4
  name: string;
5
5
  status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
6
+ state: Record<string, any>;
6
7
  progress: number; // 0 to 100
7
8
  createdAt: Date;
8
9
  finishedAt?: Date;
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ import { afLogger } from "adminforth";
12
12
  import pLimit from 'p-limit';
13
13
  import { Level } from 'level';
14
14
  import fs from 'fs/promises';
15
+ import { Mutex } from 'async-mutex';
16
+ const mutex = new Mutex();
15
17
  export default class BackgroundJobsPlugin extends AdminForthPlugin {
16
18
  constructor(options) {
17
19
  super(options, import.meta.url);
@@ -147,6 +149,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
147
149
  [this.options.progressField]: 0,
148
150
  [this.options.statusField]: 'IN_PROGRESS',
149
151
  [this.options.jobHandlerField]: jobHandlerName,
152
+ [this.options.stateField]: '{}'
150
153
  };
151
154
  const creationResult = yield this.adminforth.resource(this.getResourceId()).create(objectToSave);
152
155
  let createdRecord = null;
@@ -174,6 +177,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
174
177
  });
175
178
  yield Promise.all(createTaskRecordsPromises);
176
179
  this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit);
180
+ return jobId;
177
181
  });
178
182
  }
179
183
  runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit) {
@@ -209,7 +213,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
209
213
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "IN_PROGRESS" });
210
214
  //handling the task
211
215
  try {
212
- yield handleTask({ setTaskStateField, getTaskStateField });
216
+ yield handleTask({ jobId, setTaskStateField, getTaskStateField });
213
217
  //Set task status to completed in level db
214
218
  yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
215
219
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "DONE" });
@@ -242,7 +246,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
242
246
  [this.options.statusField]: 'DONE',
243
247
  [this.options.finishedAtField]: (new Date()).toISOString(),
244
248
  });
245
- this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
249
+ this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
246
250
  }
247
251
  else if (failedTasks > 0) {
248
252
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
@@ -319,6 +323,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
319
323
  const state = jobRecord[this.options.stateField];
320
324
  const parsedState = JSON.parse(state);
321
325
  parsedState[key] = value;
326
+ this.adminforth.websocket.publish(`/background-jobs`, { jobId, state: parsedState });
322
327
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
323
328
  [this.options.stateField]: JSON.stringify(parsedState),
324
329
  });
@@ -385,6 +390,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
385
390
  createdAt: job[this.options.createdAtField],
386
391
  finishedAt: job[this.options.finishedAtField] || null,
387
392
  status: job[this.options.statusField],
393
+ state: JSON.parse(job[this.options.stateField]),
388
394
  progress: job[this.options.progressField],
389
395
  customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
390
396
  };
package/index.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  import { AdminForthPlugin, Filters, Sorts } from "adminforth";
2
- import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource, AdminUser, AdminForthComponentDeclarationFull } from "adminforth";
2
+ import type { IAdminForth, IHttpServer, AdminForthResource, AdminUser, AdminForthComponentDeclarationFull } from "adminforth";
3
3
  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
7
  import fs from 'fs/promises';
8
+ import {Mutex, MutexInterface, Semaphore, SemaphoreInterface, withTimeout} from 'async-mutex';
9
+
10
+ const mutex = new Mutex();
8
11
 
9
12
  type TaskStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
10
13
  type setStateFieldParams = (state: Record<string, any>) => void;
11
14
  type getStateFieldParams = () => any;
12
- type taskHandlerType = ( { setTaskStateField, getTaskStateField }: { setTaskStateField: setStateFieldParams; getTaskStateField: getStateFieldParams } ) => Promise<void>;
15
+ type taskHandlerType = ( { jobId, setTaskStateField, getTaskStateField }: { jobId: string; setTaskStateField: setStateFieldParams; getTaskStateField: getStateFieldParams } ) => Promise<void>;
13
16
  type taskType = {
14
17
  skip?: boolean;
15
18
  state: Record<string, any>;
@@ -146,7 +149,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
146
149
  adminUser: AdminUser,
147
150
  tasks: taskType[],
148
151
  jobHandlerName: string,
149
- ) {
152
+ ): Promise<string> {
150
153
 
151
154
  const handleTask: taskHandlerType = this.taskHandlers[jobHandlerName];
152
155
  if (!handleTask) {
@@ -161,6 +164,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
161
164
  [this.options.progressField]: 0,
162
165
  [this.options.statusField]: 'IN_PROGRESS',
163
166
  [this.options.jobHandlerField]: jobHandlerName,
167
+ [this.options.stateField]: '{}'
164
168
  }
165
169
 
166
170
  const creationResult = await this.adminforth.resource(this.getResourceId()).create(objectToSave);
@@ -193,6 +197,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
193
197
  await Promise.all(createTaskRecordsPromises);
194
198
 
195
199
  this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit);
200
+ return jobId;
196
201
  }
197
202
 
198
203
  private async runProcessingTasks(
@@ -238,7 +243,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
238
243
 
239
244
  //handling the task
240
245
  try {
241
- await handleTask({ setTaskStateField, getTaskStateField });
246
+ await handleTask({ jobId, setTaskStateField, getTaskStateField });
242
247
 
243
248
  //Set task status to completed in level db
244
249
  await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
@@ -273,7 +278,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
273
278
  [this.options.statusField]: 'DONE',
274
279
  [this.options.finishedAtField]: (new Date()).toISOString(),
275
280
  })
276
- this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
281
+ this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
277
282
  } else if (failedTasks > 0) {
278
283
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
279
284
  [this.options.statusField]: 'DONE_WITH_ERRORS',
@@ -348,6 +353,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
348
353
  const state = jobRecord[this.options.stateField];
349
354
  const parsedState = JSON.parse(state);
350
355
  parsedState[key] = value;
356
+ this.adminforth.websocket.publish(`/background-jobs`, { jobId, state: parsedState });
351
357
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
352
358
  [this.options.stateField]: JSON.stringify(parsedState),
353
359
  });
@@ -415,6 +421,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
415
421
  createdAt: job[this.options.createdAtField],
416
422
  finishedAt: job[this.options.finishedAtField] || null,
417
423
  status: job[this.options.statusField],
424
+ state: JSON.parse(job[this.options.stateField]),
418
425
  progress: job[this.options.progressField],
419
426
  customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
420
427
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/background-jobs",
3
- "version": "1.2.4",
3
+ "version": "1.4.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "dependencies": {
22
22
  "@vueuse/core": "^14.2.1",
23
23
  "adminforth": "latest",
24
+ "async-mutex": "^0.5.0",
24
25
  "level": "^10.0.0",
25
26
  "p-limit": "^7.3.0"
26
27
  }