@adminforth/background-jobs 1.7.0 → 1.8.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/build.log CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  sending incremental file list
6
6
  custom/
7
+ custom/GlobalJobApi.vue
7
8
  custom/JobInfoPopup.vue
8
9
  custom/JobsList.vue
9
10
  custom/NavbarJobs.vue
@@ -11,5 +12,5 @@ custom/StateToIcon.vue
11
12
  custom/tsconfig.json
12
13
  custom/utils.ts
13
14
 
14
- sent 14,434 bytes received 134 bytes 29,136.00 bytes/sec
15
- total size is 13,949 speedup is 0.96
15
+ sent 17,404 bytes received 153 bytes 35,114.00 bytes/sec
16
+ total size is 16,848 speedup is 0.96
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <!-- Hidden component that registers a global function to open job info modal -->
3
+ <div style="display:none">
4
+ <Modal
5
+ ref="dialogRef"
6
+ removeFromDomOnClose
7
+ class="p-4"
8
+ :beforeCloseFunction="() => { currentJob.value = null; }"
9
+ >
10
+ <JobInfoPopup
11
+ v-if="currentJob"
12
+ :job="currentJob"
13
+ :meta="meta"
14
+ :closeModal="closeModal"
15
+ />
16
+ </Modal>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { ref, onMounted, onBeforeUnmount } from 'vue';
22
+ import { callAdminForthApi } from '@/utils';
23
+ import { useAdminforth } from '@/adminforth';
24
+ import { Modal } from '@/afcl';
25
+ import JobInfoPopup from './JobInfoPopup.vue';
26
+ import websocket from '@/websocket';
27
+
28
+ const adminforth = useAdminforth();
29
+
30
+ const props = defineProps<{
31
+ meta: {
32
+ pluginInstanceId: string;
33
+ }
34
+ }>();
35
+
36
+ const dialogRef = ref<any>(null);
37
+ const currentJob = ref<any>(null);
38
+
39
+ function closeModal() {
40
+ if (!dialogRef.value) return;
41
+ if (typeof dialogRef.value.close === 'function') {
42
+ dialogRef.value.close();
43
+ return;
44
+ }
45
+ if (typeof dialogRef.value.hide === 'function') {
46
+ dialogRef.value.hide();
47
+ }
48
+ }
49
+
50
+ async function openJobInfo(jobId: string) {
51
+ if (!jobId) return;
52
+ try {
53
+ const res = await callAdminForthApi({
54
+ path: `/plugin/${props.meta.pluginInstanceId}/get-job-info`,
55
+ method: 'POST',
56
+ body: { jobId },
57
+ });
58
+ if (res && res.ok) {
59
+ currentJob.value = res.job;
60
+ // open dialog
61
+ if (dialogRef.value && typeof dialogRef.value.open === 'function') {
62
+ dialogRef.value.open();
63
+ } else if (dialogRef.value && typeof dialogRef.value.show === 'function') {
64
+ dialogRef.value.show();
65
+ }
66
+ } else {
67
+ adminforth.alert({ variant: 'danger', message: res?.message || 'Failed to load job info' });
68
+ }
69
+ } catch (e) {
70
+ console.error('OpenJobInfoPopup error', e);
71
+ adminforth.alert({ variant: 'danger', message: 'Failed to load job info' });
72
+ }
73
+ }
74
+
75
+ onMounted(() => {
76
+ // expose global function
77
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
78
+ // @ts-ignore
79
+ window.OpenJobInfoPopup = openJobInfo;
80
+
81
+ websocket.subscribe('/background-jobs', (data) => {
82
+ if (data.jobId === currentJob.value?.id) {
83
+ if (data.status) {
84
+ currentJob.value.status = data.status;
85
+ }
86
+ if (data.progress !== undefined) {
87
+ currentJob.value.progress = data.progress;
88
+ }
89
+ if (data.finishedAt) {
90
+ currentJob.value.finishedAt = data.finishedAt;
91
+ }
92
+ if (data.state) {
93
+ currentJob.value.state = {
94
+ ...currentJob.value.state,
95
+ ...data.state,
96
+ };
97
+ }
98
+ }
99
+ });
100
+ });
101
+ onBeforeUnmount(() => {
102
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
103
+ // @ts-ignore
104
+ if (window.OpenJobInfoPopup) delete window.OpenJobInfoPopup;
105
+ websocket.unsubscribe('/background-jobs');
106
+ });
107
+ </script>
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <!-- Hidden component that registers a global function to open job info modal -->
3
+ <div style="display:none">
4
+ <Modal
5
+ ref="dialogRef"
6
+ removeFromDomOnClose
7
+ class="p-4"
8
+ :beforeCloseFunction="() => { currentJob.value = null; }"
9
+ >
10
+ <JobInfoPopup
11
+ v-if="currentJob"
12
+ :job="currentJob"
13
+ :meta="meta"
14
+ :closeModal="closeModal"
15
+ />
16
+ </Modal>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { ref, onMounted, onBeforeUnmount } from 'vue';
22
+ import { callAdminForthApi } from '@/utils';
23
+ import { useAdminforth } from '@/adminforth';
24
+ import { Modal } from '@/afcl';
25
+ import JobInfoPopup from './JobInfoPopup.vue';
26
+ import websocket from '@/websocket';
27
+
28
+ const adminforth = useAdminforth();
29
+
30
+ const props = defineProps<{
31
+ meta: {
32
+ pluginInstanceId: string;
33
+ }
34
+ }>();
35
+
36
+ const dialogRef = ref<any>(null);
37
+ const currentJob = ref<any>(null);
38
+
39
+ function closeModal() {
40
+ if (!dialogRef.value) return;
41
+ if (typeof dialogRef.value.close === 'function') {
42
+ dialogRef.value.close();
43
+ return;
44
+ }
45
+ if (typeof dialogRef.value.hide === 'function') {
46
+ dialogRef.value.hide();
47
+ }
48
+ }
49
+
50
+ async function openJobInfo(jobId: string) {
51
+ if (!jobId) return;
52
+ try {
53
+ const res = await callAdminForthApi({
54
+ path: `/plugin/${props.meta.pluginInstanceId}/get-job-info`,
55
+ method: 'POST',
56
+ body: { jobId },
57
+ });
58
+ if (res && res.ok) {
59
+ currentJob.value = res.job;
60
+ // open dialog
61
+ if (dialogRef.value && typeof dialogRef.value.open === 'function') {
62
+ dialogRef.value.open();
63
+ } else if (dialogRef.value && typeof dialogRef.value.show === 'function') {
64
+ dialogRef.value.show();
65
+ }
66
+ } else {
67
+ adminforth.alert({ variant: 'danger', message: res?.message || 'Failed to load job info' });
68
+ }
69
+ } catch (e) {
70
+ console.error('OpenJobInfoPopup error', e);
71
+ adminforth.alert({ variant: 'danger', message: 'Failed to load job info' });
72
+ }
73
+ }
74
+
75
+ onMounted(() => {
76
+ // expose global function
77
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
78
+ // @ts-ignore
79
+ window.OpenJobInfoPopup = openJobInfo;
80
+
81
+ websocket.subscribe('/background-jobs', (data) => {
82
+ if (data.jobId === currentJob.value?.id) {
83
+ if (data.status) {
84
+ currentJob.value.status = data.status;
85
+ }
86
+ if (data.progress !== undefined) {
87
+ currentJob.value.progress = data.progress;
88
+ }
89
+ if (data.finishedAt) {
90
+ currentJob.value.finishedAt = data.finishedAt;
91
+ }
92
+ if (data.state) {
93
+ currentJob.value.state = {
94
+ ...currentJob.value.state,
95
+ ...data.state,
96
+ };
97
+ }
98
+ }
99
+ });
100
+ });
101
+ onBeforeUnmount(() => {
102
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
103
+ // @ts-ignore
104
+ if (window.OpenJobInfoPopup) delete window.OpenJobInfoPopup;
105
+ websocket.unsubscribe('/background-jobs');
106
+ });
107
+ </script>
package/dist/index.js CHANGED
@@ -48,6 +48,13 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
48
48
  pluginInstanceId: this.pluginInstanceId,
49
49
  }
50
50
  });
51
+ // Global API injection: exposes OpenJobInfoPopup(jobId) to open job details from anywhere
52
+ (adminforth.config.customization.globalInjections.header).push({
53
+ file: this.componentPath('GlobalJobApi.vue'),
54
+ meta: {
55
+ pluginInstanceId: this.pluginInstanceId,
56
+ }
57
+ });
51
58
  if (!this.adminforth.config.componentsToExplicitRegister) {
52
59
  this.adminforth.config.componentsToExplicitRegister = [];
53
60
  }
@@ -366,13 +373,13 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
366
373
  return JSON.parse(state);
367
374
  });
368
375
  }
369
- updateJobFieldsAtomicly(jobId, updateFunction) {
376
+ updateJobFieldsAtomically(jobId, updateFunction) {
370
377
  return __awaiter(this, void 0, void 0, function* () {
371
378
  if (!jobId) {
372
- throw new Error('updateJobFieldsAtomicly: jobId is required');
379
+ throw new Error('updateJobFieldsAtomically: jobId is required');
373
380
  }
374
381
  if (typeof updateFunction !== 'function') {
375
- throw new Error('updateJobFieldsAtomicly: updateFunction must be a function');
382
+ throw new Error('updateJobFieldsAtomically: updateFunction must be a function');
376
383
  }
377
384
  // Ensure updates are atomic per jobId.
378
385
  // Different jobs are not blocked by each other.
@@ -440,6 +447,28 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
440
447
  return { jobs: jobsToReturn };
441
448
  })
442
449
  });
450
+ server.endpoint({
451
+ method: 'POST',
452
+ path: `/plugin/${this.pluginInstanceId}/get-job-info`,
453
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body }) {
454
+ const jobId = body.jobId;
455
+ const job = yield this.adminforth.resource(this.resourceConfig.resourceId).get(Filters.EQ(this.getResourcePk(), jobId));
456
+ if (!job) {
457
+ return { ok: false, message: `Job with id ${jobId} not found.` };
458
+ }
459
+ const jobToReturn = {
460
+ id: job[this.getResourcePk()],
461
+ name: job[this.options.nameField],
462
+ createdAt: job[this.options.createdAtField],
463
+ finishedAt: job[this.options.finishedAtField] || null,
464
+ status: job[this.options.statusField],
465
+ state: JSON.parse(job[this.options.stateField]),
466
+ progress: job[this.options.progressField],
467
+ customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
468
+ };
469
+ return { ok: true, job: jobToReturn };
470
+ })
471
+ });
443
472
  server.endpoint({
444
473
  method: 'POST',
445
474
  path: `/plugin/${this.pluginInstanceId}/cancel-job`,
package/index.ts CHANGED
@@ -51,6 +51,14 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
51
51
  }
52
52
  });
53
53
 
54
+ // Global API injection: exposes OpenJobInfoPopup(jobId) to open job details from anywhere
55
+ (adminforth.config.customization.globalInjections.header).push({
56
+ file: this.componentPath('GlobalJobApi.vue'),
57
+ meta: {
58
+ pluginInstanceId: this.pluginInstanceId,
59
+ }
60
+ });
61
+
54
62
  if (!this.adminforth.config.componentsToExplicitRegister) {
55
63
  this.adminforth.config.componentsToExplicitRegister = [];
56
64
  }
@@ -396,12 +404,12 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
396
404
  return JSON.parse(state);
397
405
  }
398
406
 
399
- public async updateJobFieldsAtomicly(jobId: string, updateFunction: () => Promise<void>) {
407
+ public async updateJobFieldsAtomically(jobId: string, updateFunction: () => Promise<void>) {
400
408
  if (!jobId) {
401
- throw new Error('updateJobFieldsAtomicly: jobId is required');
409
+ throw new Error('updateJobFieldsAtomically: jobId is required');
402
410
  }
403
411
  if (typeof updateFunction !== 'function') {
404
- throw new Error('updateJobFieldsAtomicly: updateFunction must be a function');
412
+ throw new Error('updateJobFieldsAtomically: updateFunction must be a function');
405
413
  }
406
414
 
407
415
  // Ensure updates are atomic per jobId.
@@ -475,6 +483,31 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
475
483
  }
476
484
  });
477
485
 
486
+ server.endpoint({
487
+ method: 'POST',
488
+ path: `/plugin/${this.pluginInstanceId}/get-job-info`,
489
+ handler: async ({ adminUser, body }) => {
490
+ const jobId = body.jobId;
491
+
492
+ const job = await this.adminforth.resource(this.resourceConfig.resourceId).get(Filters.EQ(this.getResourcePk(), jobId));
493
+ if (!job) {
494
+ return { ok: false, message: `Job with id ${jobId} not found.` };
495
+ }
496
+ const jobToReturn = {
497
+ id: job[this.getResourcePk()],
498
+ name: job[this.options.nameField],
499
+ createdAt: job[this.options.createdAtField],
500
+ finishedAt: job[this.options.finishedAtField] || null,
501
+ status: job[this.options.statusField],
502
+ state: JSON.parse(job[this.options.stateField]),
503
+ progress: job[this.options.progressField],
504
+ customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
505
+ };
506
+ return { ok: true, job: jobToReturn };
507
+ }
508
+ });
509
+
510
+
478
511
  server.endpoint({
479
512
  method: 'POST',
480
513
  path: `/plugin/${this.pluginInstanceId}/cancel-job`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/background-jobs",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",