@adminforth/background-jobs 1.0.1 → 1.2.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 +2 -2
- package/custom/JobInfoPopup.vue +3 -3
- package/custom/JobsList.vue +26 -11
- package/custom/NavbarJobs.vue +25 -39
- package/dist/custom/JobInfoPopup.vue +3 -3
- package/dist/custom/JobsList.vue +26 -11
- package/dist/custom/NavbarJobs.vue +25 -39
- package/dist/index.js +32 -0
- package/index.ts +37 -0
- package/package.json +1 -1
- package/types.ts +3 -2
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,
|
|
15
|
-
total size is 10,
|
|
14
|
+
sent 11,397 bytes received 134 bytes 23,062.00 bytes/sec
|
|
15
|
+
total size is 10,912 speedup is 0.95
|
package/custom/JobInfoPopup.vue
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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">{{ job.name }}</h2>
|
|
5
|
-
<p class="ml-2 text-xs text-gray-600 h-full"> {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
|
|
6
|
-
<p class="ml-auto text-gray-800 h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
|
|
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"> {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
|
|
6
|
+
<p class="ml-auto text-gray-800 dark:text-white h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
|
|
7
7
|
<StateToIcon :job="job" />
|
|
8
8
|
</div>
|
|
9
9
|
<div class="flex items-center gap-4 w-full">
|
package/custom/JobsList.vue
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="w-1vw md:w-64 bg-white border border-gray-200 rounded-md">
|
|
3
|
-
<Modal
|
|
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
|
+
<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
|
-
<div class="flex items-center w-full px-4 py-3 border-b border-gray-200 hover:bg-gray-50 transition-colors">
|
|
10
|
+
<div class="flex items-center w-full px-4 py-3 bg-white dark:bg-gray-700 dark:border-gray-600 border-b border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors">
|
|
6
11
|
<div class="flex flex-col w-full max-w-48">
|
|
7
12
|
<p class="flex gap-2 items-end justify-between text-nowrap">
|
|
8
|
-
<span class="text-sm h-full text truncate">{{ job.name }}</span>
|
|
9
|
-
<span class="text-xs text-gray-600">{{ getTimeAgoString(new Date(job.createdAt)) }}</span>
|
|
13
|
+
<span class="text-sm h-full text truncate dark:text-white">{{ job.name }}</span>
|
|
14
|
+
<span class="text-xs dark:text-gray-200 text-gray-600">{{ getTimeAgoString(new Date(job.createdAt)) }}</span>
|
|
10
15
|
</p>
|
|
11
16
|
<ProgressBar
|
|
12
17
|
class="mt-1"
|
|
@@ -22,12 +27,10 @@
|
|
|
22
27
|
<StateToIcon :job="job" />
|
|
23
28
|
</div>
|
|
24
29
|
</template>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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>
|
package/custom/NavbarJobs.vue
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="dropdownRef">
|
|
3
3
|
<div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
|
|
4
|
-
<div v-if="
|
|
5
|
-
<div class="loader "></div>
|
|
6
|
-
<div class="absolute -bottom-1 -right-1 rounded-full bg-lightPrimary w-4 h-4 text-xs flex items-center justify-center text-white"> {{ jobsCount }}</div>
|
|
7
|
-
</div>
|
|
8
|
-
<div class="flex items-center justify-center" v-else-if="jobs.length > 0">
|
|
4
|
+
<div class="relative flex items-center justify-center" v-if="jobs.length > 0">
|
|
9
5
|
<Tooltip>
|
|
10
|
-
<
|
|
6
|
+
<IconBriefcaseSolid class="w-7 h-7 text-gray-600 hover:text-gray-700" />
|
|
11
7
|
<template #tooltip>
|
|
12
8
|
{{ t('All jobs completed') }}
|
|
13
9
|
</template>
|
|
14
10
|
</Tooltip>
|
|
11
|
+
<div
|
|
12
|
+
v-if="isAlLeastOneJobRunning"
|
|
13
|
+
class="ping-animation absolute -bottom-1 -right-1 rounded-full bg-lightPrimary w-4 h-4 text-xs flex items-center justify-center text-white"
|
|
14
|
+
>
|
|
15
|
+
{{ jobsCount }}
|
|
16
|
+
</div>
|
|
17
|
+
<div
|
|
18
|
+
v-if="isAlLeastOneJobRunning"
|
|
19
|
+
class="absolute -bottom-1 -right-1 rounded-full bg-lightPrimary w-4 h-4 text-xs flex items-center justify-center text-white"
|
|
20
|
+
>
|
|
21
|
+
{{ jobsCount }}
|
|
22
|
+
</div>
|
|
15
23
|
</div>
|
|
16
24
|
</div>
|
|
17
25
|
<Transition
|
|
@@ -40,8 +48,8 @@
|
|
|
40
48
|
<script setup lang="ts">
|
|
41
49
|
import type { AdminUser } from 'adminforth';
|
|
42
50
|
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
|
43
|
-
import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
|
|
44
|
-
import { Tooltip
|
|
51
|
+
import { IconCheckCircleOutline, IconBriefcaseSolid } from '@iconify-prerendered/vue-flowbite';
|
|
52
|
+
import { Tooltip } from '@/afcl';
|
|
45
53
|
import { useI18n } from 'vue-i18n';
|
|
46
54
|
import JobsList from './JobsList.vue';
|
|
47
55
|
import type { IJob } from './utils';
|
|
@@ -119,36 +127,14 @@
|
|
|
119
127
|
|
|
120
128
|
|
|
121
129
|
<style scoped lang="scss">
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
from 120deg,
|
|
131
|
-
var(--spinner-color) 0deg 40deg,
|
|
132
|
-
transparent 40deg
|
|
133
|
-
),
|
|
134
|
-
|
|
135
|
-
conic-gradient(#ccc 0deg 360deg);
|
|
136
|
-
|
|
137
|
-
-webkit-mask: radial-gradient(
|
|
138
|
-
farthest-side,
|
|
139
|
-
transparent calc(100% - 6px),
|
|
140
|
-
#000 calc(100% - 5px)
|
|
141
|
-
);
|
|
142
|
-
mask: radial-gradient(
|
|
143
|
-
farthest-side,
|
|
144
|
-
transparent calc(100% - 6px),
|
|
145
|
-
#000 calc(100% - 5px)
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
animation: stepRotate 2s infinite;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
@keyframes stepRotate {
|
|
152
|
-
to { transform: rotate(1turn); }
|
|
130
|
+
.ping-animation {
|
|
131
|
+
animation: ping 1s cubic-bezier(0, 0, 1, 1) infinite;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@keyframes ping {
|
|
135
|
+
75%, 100% {
|
|
136
|
+
transform: scale(2);
|
|
137
|
+
opacity: 0;
|
|
153
138
|
}
|
|
139
|
+
}
|
|
154
140
|
</style>
|
|
@@ -1,9 +1,9 @@
|
|
|
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">{{ job.name }}</h2>
|
|
5
|
-
<p class="ml-2 text-xs text-gray-600 h-full"> {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
|
|
6
|
-
<p class="ml-auto text-gray-800 h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
|
|
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"> {{ getTimeAgoString(new Date(job.createdAt)) }}</p>
|
|
6
|
+
<p class="ml-auto text-gray-800 dark:text-white h-full"> {{ t('Progress:') }} <span class="font-semibold" >{{ job.progress }}%</span></p>
|
|
7
7
|
<StateToIcon :job="job" />
|
|
8
8
|
</div>
|
|
9
9
|
<div class="flex items-center gap-4 w-full">
|
package/dist/custom/JobsList.vue
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="w-1vw md:w-64 bg-white border border-gray-200 rounded-md">
|
|
3
|
-
<Modal
|
|
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
|
+
<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
|
-
<div class="flex items-center w-full px-4 py-3 border-b border-gray-200 hover:bg-gray-50 transition-colors">
|
|
10
|
+
<div class="flex items-center w-full px-4 py-3 bg-white dark:bg-gray-700 dark:border-gray-600 border-b border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors">
|
|
6
11
|
<div class="flex flex-col w-full max-w-48">
|
|
7
12
|
<p class="flex gap-2 items-end justify-between text-nowrap">
|
|
8
|
-
<span class="text-sm h-full text truncate">{{ job.name }}</span>
|
|
9
|
-
<span class="text-xs text-gray-600">{{ getTimeAgoString(new Date(job.createdAt)) }}</span>
|
|
13
|
+
<span class="text-sm h-full text truncate dark:text-white">{{ job.name }}</span>
|
|
14
|
+
<span class="text-xs dark:text-gray-200 text-gray-600">{{ getTimeAgoString(new Date(job.createdAt)) }}</span>
|
|
10
15
|
</p>
|
|
11
16
|
<ProgressBar
|
|
12
17
|
class="mt-1"
|
|
@@ -22,12 +27,10 @@
|
|
|
22
27
|
<StateToIcon :job="job" />
|
|
23
28
|
</div>
|
|
24
29
|
</template>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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>
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="dropdownRef">
|
|
3
3
|
<div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
|
|
4
|
-
<div v-if="
|
|
5
|
-
<div class="loader "></div>
|
|
6
|
-
<div class="absolute -bottom-1 -right-1 rounded-full bg-lightPrimary w-4 h-4 text-xs flex items-center justify-center text-white"> {{ jobsCount }}</div>
|
|
7
|
-
</div>
|
|
8
|
-
<div class="flex items-center justify-center" v-else-if="jobs.length > 0">
|
|
4
|
+
<div class="relative flex items-center justify-center" v-if="jobs.length > 0">
|
|
9
5
|
<Tooltip>
|
|
10
|
-
<
|
|
6
|
+
<IconBriefcaseSolid class="w-7 h-7 text-gray-600 hover:text-gray-700" />
|
|
11
7
|
<template #tooltip>
|
|
12
8
|
{{ t('All jobs completed') }}
|
|
13
9
|
</template>
|
|
14
10
|
</Tooltip>
|
|
11
|
+
<div
|
|
12
|
+
v-if="isAlLeastOneJobRunning"
|
|
13
|
+
class="ping-animation absolute -bottom-1 -right-1 rounded-full bg-lightPrimary w-4 h-4 text-xs flex items-center justify-center text-white"
|
|
14
|
+
>
|
|
15
|
+
{{ jobsCount }}
|
|
16
|
+
</div>
|
|
17
|
+
<div
|
|
18
|
+
v-if="isAlLeastOneJobRunning"
|
|
19
|
+
class="absolute -bottom-1 -right-1 rounded-full bg-lightPrimary w-4 h-4 text-xs flex items-center justify-center text-white"
|
|
20
|
+
>
|
|
21
|
+
{{ jobsCount }}
|
|
22
|
+
</div>
|
|
15
23
|
</div>
|
|
16
24
|
</div>
|
|
17
25
|
<Transition
|
|
@@ -40,8 +48,8 @@
|
|
|
40
48
|
<script setup lang="ts">
|
|
41
49
|
import type { AdminUser } from 'adminforth';
|
|
42
50
|
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
|
43
|
-
import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
|
|
44
|
-
import { Tooltip
|
|
51
|
+
import { IconCheckCircleOutline, IconBriefcaseSolid } from '@iconify-prerendered/vue-flowbite';
|
|
52
|
+
import { Tooltip } from '@/afcl';
|
|
45
53
|
import { useI18n } from 'vue-i18n';
|
|
46
54
|
import JobsList from './JobsList.vue';
|
|
47
55
|
import type { IJob } from './utils';
|
|
@@ -119,36 +127,14 @@
|
|
|
119
127
|
|
|
120
128
|
|
|
121
129
|
<style scoped lang="scss">
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
from 120deg,
|
|
131
|
-
var(--spinner-color) 0deg 40deg,
|
|
132
|
-
transparent 40deg
|
|
133
|
-
),
|
|
134
|
-
|
|
135
|
-
conic-gradient(#ccc 0deg 360deg);
|
|
136
|
-
|
|
137
|
-
-webkit-mask: radial-gradient(
|
|
138
|
-
farthest-side,
|
|
139
|
-
transparent calc(100% - 6px),
|
|
140
|
-
#000 calc(100% - 5px)
|
|
141
|
-
);
|
|
142
|
-
mask: radial-gradient(
|
|
143
|
-
farthest-side,
|
|
144
|
-
transparent calc(100% - 6px),
|
|
145
|
-
#000 calc(100% - 5px)
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
animation: stepRotate 2s infinite;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
@keyframes stepRotate {
|
|
152
|
-
to { transform: rotate(1turn); }
|
|
130
|
+
.ping-animation {
|
|
131
|
+
animation: ping 1s cubic-bezier(0, 0, 1, 1) infinite;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@keyframes ping {
|
|
135
|
+
75%, 100% {
|
|
136
|
+
transform: scale(2);
|
|
137
|
+
opacity: 0;
|
|
153
138
|
}
|
|
139
|
+
}
|
|
154
140
|
</style>
|
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
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
|
|
9
|
-
jobHandlerField
|
|
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/
|