@adminforth/background-jobs 1.6.2 → 1.8.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 +3 -2
- package/custom/GlobalJobApi.vue +107 -0
- package/custom/JobInfoPopup.vue +13 -2
- package/custom/JobsList.vue +22 -0
- package/dist/custom/GlobalJobApi.vue +107 -0
- package/dist/custom/JobInfoPopup.vue +13 -2
- package/dist/custom/JobsList.vue +22 -0
- package/dist/index.js +29 -0
- package/index.ts +33 -0
- package/package.json +1 -1
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
|
|
15
|
-
total size is
|
|
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>
|
package/custom/JobInfoPopup.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col w-full min-w-96">
|
|
2
|
+
<div class="flex flex-col w-full min-w-96 mt-2">
|
|
3
3
|
<div class="flex items-center mb-1">
|
|
4
4
|
<div class="flex flex-col items-start justify-end h-12">
|
|
5
5
|
<h2 class="text-lg font-semibold dark:text-white">{{ job.name }}</h2>
|
|
@@ -11,9 +11,19 @@
|
|
|
11
11
|
</Tooltip>
|
|
12
12
|
</div>
|
|
13
13
|
<div class="ml-auto flex flex-col items-start justify-end h-12">
|
|
14
|
-
<div class="flex items-center">
|
|
14
|
+
<div class="flex items-center mr-6">
|
|
15
15
|
<p class=" text-gray-800 dark:text-white h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
|
|
16
16
|
<StateToIcon :job="job" />
|
|
17
|
+
<button
|
|
18
|
+
@click="closeModal()"
|
|
19
|
+
type="button"
|
|
20
|
+
class="absolute top-2 right-2 text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
|
|
21
|
+
>
|
|
22
|
+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
23
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
24
|
+
</svg>
|
|
25
|
+
<span class="sr-only">{{ t('Close Modal') }}</span>
|
|
26
|
+
</button>
|
|
17
27
|
</div>
|
|
18
28
|
<Tooltip v-if="job.finishedAt">
|
|
19
29
|
<p class="text-xs text-gray-600 dark:text-gray-200 h-full"> {{ t('Finished:') }} {{ getTimeAgoString(new Date(job.finishedAt)) }}</p>
|
|
@@ -68,6 +78,7 @@ const props = defineProps<{
|
|
|
68
78
|
meta: {
|
|
69
79
|
pluginInstanceId: string;
|
|
70
80
|
};
|
|
81
|
+
closeModal: () => void;
|
|
71
82
|
}>();
|
|
72
83
|
|
|
73
84
|
async function cancelJob() {
|
package/custom/JobsList.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-1vw md:w-64 bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-600 rounded-md">
|
|
3
3
|
<Modal
|
|
4
|
+
ref="modalRef"
|
|
4
5
|
class="p-4"
|
|
5
6
|
v-for="job in props.jobs" :key="job.id"
|
|
6
7
|
:beforeCloseFunction="onBeforeOpen"
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
<JobInfoPopup
|
|
32
33
|
:job="job"
|
|
33
34
|
:meta="meta"
|
|
35
|
+
:closeModal="closeModal"
|
|
34
36
|
/>
|
|
35
37
|
</Modal>
|
|
36
38
|
|
|
@@ -46,6 +48,26 @@ import JobInfoPopup from './JobInfoPopup.vue';
|
|
|
46
48
|
import StateToIcon from './StateToIcon.vue';
|
|
47
49
|
import { ref } from 'vue';
|
|
48
50
|
|
|
51
|
+
const modalRef = ref<any>(null);
|
|
52
|
+
|
|
53
|
+
function closeModal() {
|
|
54
|
+
const m = modalRef.value;
|
|
55
|
+
if (!m) return;
|
|
56
|
+
|
|
57
|
+
if (typeof m.close === 'function') {
|
|
58
|
+
m.close();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(m)) {
|
|
63
|
+
m.forEach((inst: any) => {
|
|
64
|
+
if (inst?.close && typeof inst.close === 'function') {
|
|
65
|
+
inst.close();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
const props = defineProps<{
|
|
50
72
|
jobs: IJob[];
|
|
51
73
|
closeDropdown: () => void;
|
|
@@ -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>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col w-full min-w-96">
|
|
2
|
+
<div class="flex flex-col w-full min-w-96 mt-2">
|
|
3
3
|
<div class="flex items-center mb-1">
|
|
4
4
|
<div class="flex flex-col items-start justify-end h-12">
|
|
5
5
|
<h2 class="text-lg font-semibold dark:text-white">{{ job.name }}</h2>
|
|
@@ -11,9 +11,19 @@
|
|
|
11
11
|
</Tooltip>
|
|
12
12
|
</div>
|
|
13
13
|
<div class="ml-auto flex flex-col items-start justify-end h-12">
|
|
14
|
-
<div class="flex items-center">
|
|
14
|
+
<div class="flex items-center mr-6">
|
|
15
15
|
<p class=" text-gray-800 dark:text-white h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
|
|
16
16
|
<StateToIcon :job="job" />
|
|
17
|
+
<button
|
|
18
|
+
@click="closeModal()"
|
|
19
|
+
type="button"
|
|
20
|
+
class="absolute top-2 right-2 text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
|
|
21
|
+
>
|
|
22
|
+
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
23
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
24
|
+
</svg>
|
|
25
|
+
<span class="sr-only">{{ t('Close Modal') }}</span>
|
|
26
|
+
</button>
|
|
17
27
|
</div>
|
|
18
28
|
<Tooltip v-if="job.finishedAt">
|
|
19
29
|
<p class="text-xs text-gray-600 dark:text-gray-200 h-full"> {{ t('Finished:') }} {{ getTimeAgoString(new Date(job.finishedAt)) }}</p>
|
|
@@ -68,6 +78,7 @@ const props = defineProps<{
|
|
|
68
78
|
meta: {
|
|
69
79
|
pluginInstanceId: string;
|
|
70
80
|
};
|
|
81
|
+
closeModal: () => void;
|
|
71
82
|
}>();
|
|
72
83
|
|
|
73
84
|
async function cancelJob() {
|
package/dist/custom/JobsList.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="w-1vw md:w-64 bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-600 rounded-md">
|
|
3
3
|
<Modal
|
|
4
|
+
ref="modalRef"
|
|
4
5
|
class="p-4"
|
|
5
6
|
v-for="job in props.jobs" :key="job.id"
|
|
6
7
|
:beforeCloseFunction="onBeforeOpen"
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
<JobInfoPopup
|
|
32
33
|
:job="job"
|
|
33
34
|
:meta="meta"
|
|
35
|
+
:closeModal="closeModal"
|
|
34
36
|
/>
|
|
35
37
|
</Modal>
|
|
36
38
|
|
|
@@ -46,6 +48,26 @@ import JobInfoPopup from './JobInfoPopup.vue';
|
|
|
46
48
|
import StateToIcon from './StateToIcon.vue';
|
|
47
49
|
import { ref } from 'vue';
|
|
48
50
|
|
|
51
|
+
const modalRef = ref<any>(null);
|
|
52
|
+
|
|
53
|
+
function closeModal() {
|
|
54
|
+
const m = modalRef.value;
|
|
55
|
+
if (!m) return;
|
|
56
|
+
|
|
57
|
+
if (typeof m.close === 'function') {
|
|
58
|
+
m.close();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(m)) {
|
|
63
|
+
m.forEach((inst: any) => {
|
|
64
|
+
if (inst?.close && typeof inst.close === 'function') {
|
|
65
|
+
inst.close();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
const props = defineProps<{
|
|
50
72
|
jobs: IJob[];
|
|
51
73
|
closeDropdown: () => void;
|
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
|
}
|
|
@@ -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
|
}
|
|
@@ -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`,
|