@adminforth/background-jobs 1.0.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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/LICENSE +21 -0
- package/build.log +15 -0
- package/custom/JobInfoPopup.vue +110 -0
- package/custom/JobsList.vue +53 -0
- package/custom/NavbarJobs.vue +154 -0
- package/custom/StateToIcon.vue +40 -0
- package/custom/tsconfig.json +16 -0
- package/custom/utils.ts +9 -0
- package/dist/custom/JobInfoPopup.vue +110 -0
- package/dist/custom/JobsList.vue +53 -0
- package/dist/custom/NavbarJobs.vue +154 -0
- package/dist/custom/StateToIcon.vue +40 -0
- package/dist/custom/tsconfig.json +16 -0
- package/dist/custom/utils.ts +9 -0
- package/dist/index.js +433 -0
- package/dist/types.js +1 -0
- package/index.ts +459 -0
- package/package.json +27 -0
- package/tsconfig.json +13 -0
- package/types.ts +15 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
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">
|
|
4
|
+
<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">
|
|
6
|
+
<div class="flex flex-col w-full max-w-48">
|
|
7
|
+
<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>
|
|
10
|
+
</p>
|
|
11
|
+
<ProgressBar
|
|
12
|
+
class="mt-1"
|
|
13
|
+
:current-value="job.progress"
|
|
14
|
+
:max-value="100"
|
|
15
|
+
:min-value="0"
|
|
16
|
+
:showAnimation="job.status === 'IN_PROGRESS'"
|
|
17
|
+
:showLabels="false"
|
|
18
|
+
:showValues="false"
|
|
19
|
+
:show-progress="false"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
<StateToIcon :job="job" />
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
<div>
|
|
26
|
+
<JobInfoPopup
|
|
27
|
+
:job="job"
|
|
28
|
+
:meta="meta"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</Modal>
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import type { IJob } from './utils';
|
|
39
|
+
import { getTimeAgoString } from '@/utils';
|
|
40
|
+
import { ProgressBar, Modal } from '@/afcl';
|
|
41
|
+
import JobInfoPopup from './JobInfoPopup.vue';
|
|
42
|
+
import StateToIcon from './StateToIcon.vue';
|
|
43
|
+
|
|
44
|
+
const props = defineProps<{
|
|
45
|
+
jobs: IJob[];
|
|
46
|
+
closeDropdown: () => void;
|
|
47
|
+
meta: {
|
|
48
|
+
pluginInstanceId: string;
|
|
49
|
+
};
|
|
50
|
+
}>();
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="dropdownRef">
|
|
3
|
+
<div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
|
|
4
|
+
<div v-if="isAlLeastOneJobRunning" class="relative">
|
|
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">
|
|
9
|
+
<Tooltip>
|
|
10
|
+
<IconCheckCircleOutline class="w-8 h-8 text-green-500" />
|
|
11
|
+
<template #tooltip>
|
|
12
|
+
{{ t('All jobs completed') }}
|
|
13
|
+
</template>
|
|
14
|
+
</Tooltip>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<Transition
|
|
18
|
+
enter-active-class="transition ease-out duration-200"
|
|
19
|
+
enter-from-class="opacity-0 scale-95"
|
|
20
|
+
enter-to-class="opacity-100 scale-100"
|
|
21
|
+
leave-active-class="transition ease-in duration-150"
|
|
22
|
+
leave-from-class="opacity-100 scale-100"
|
|
23
|
+
leave-to-class="opacity-0 scale-95"
|
|
24
|
+
>
|
|
25
|
+
<div v-show="isDropdownOpen" class="absolute right-28 top-14 md:top-12 rounded z-10 overflow-y-auto max-h-96 ">
|
|
26
|
+
<JobsList
|
|
27
|
+
:closeDropdown="() => isDropdownOpen = false"
|
|
28
|
+
:jobs="jobs"
|
|
29
|
+
:meta="meta"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
</Transition>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import type { AdminUser } from 'adminforth';
|
|
42
|
+
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
|
43
|
+
import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
|
|
44
|
+
import { Tooltip, Modal } from '@/afcl';
|
|
45
|
+
import { useI18n } from 'vue-i18n';
|
|
46
|
+
import JobsList from './JobsList.vue';
|
|
47
|
+
import type { IJob } from './utils';
|
|
48
|
+
import { callAdminForthApi } from '@/utils';
|
|
49
|
+
import websocket from '@/websocket';
|
|
50
|
+
import { onClickOutside } from '@vueuse/core'
|
|
51
|
+
|
|
52
|
+
const { t } = useI18n();
|
|
53
|
+
|
|
54
|
+
const props = defineProps<{
|
|
55
|
+
meta: {
|
|
56
|
+
pluginInstanceId: string;
|
|
57
|
+
};
|
|
58
|
+
adminUser: AdminUser;
|
|
59
|
+
}>();
|
|
60
|
+
|
|
61
|
+
const isDropdownOpen = ref(false);
|
|
62
|
+
const jobs = ref<IJob[]>([]);
|
|
63
|
+
const dropdownRef = ref<HTMLElement | null>(null);
|
|
64
|
+
|
|
65
|
+
onClickOutside(dropdownRef, () => {
|
|
66
|
+
isDropdownOpen.value = false;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const isAlLeastOneJobRunning = computed(() => {
|
|
70
|
+
return jobs.value.some(job => job.status === 'IN_PROGRESS');
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const jobsCount = computed(() => {
|
|
74
|
+
return jobs.value.filter(job => job.status === 'IN_PROGRESS').length;
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
onMounted(async () => {
|
|
80
|
+
websocket.subscribe('/background-jobs', (data) => {
|
|
81
|
+
const jobIndex = jobs.value.findIndex(job => job.id === data.jobId);
|
|
82
|
+
if (jobIndex !== -1) {
|
|
83
|
+
if (data.status) {
|
|
84
|
+
jobs.value[jobIndex].status = data.status;
|
|
85
|
+
}
|
|
86
|
+
if (data.progress !== undefined) {
|
|
87
|
+
jobs.value[jobIndex].progress = data.progress;
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
jobs.value.unshift({
|
|
91
|
+
id: data.jobId,
|
|
92
|
+
name: data.name || 'Unknown Job',
|
|
93
|
+
status: data.status || 'IN_PROGRESS',
|
|
94
|
+
progress: data.progress || 0,
|
|
95
|
+
createdAt: data.createdAt || new Date().toISOString(),
|
|
96
|
+
customComponent: data.customComponent,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const res = await callAdminForthApi({
|
|
104
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get-list-of-jobs`,
|
|
105
|
+
method: 'POST',
|
|
106
|
+
});
|
|
107
|
+
jobs.value = res.jobs;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Error fetching jobs:', error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
onUnmounted(() => {
|
|
115
|
+
websocket.unsubscribe('/background-jobs');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
<style scoped lang="scss">
|
|
122
|
+
.loader {
|
|
123
|
+
width: 28px;
|
|
124
|
+
aspect-ratio: 1;
|
|
125
|
+
border-radius: 50%;
|
|
126
|
+
--spinner-color: #1a56db;
|
|
127
|
+
|
|
128
|
+
background:
|
|
129
|
+
conic-gradient(
|
|
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); }
|
|
153
|
+
}
|
|
154
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Tooltip v-if="job.status === 'IN_PROGRESS'">
|
|
3
|
+
<Spinner class="w-5 h-5 ml-2" />
|
|
4
|
+
<template #tooltip>
|
|
5
|
+
{{ t('In progress') }}
|
|
6
|
+
</template>
|
|
7
|
+
</Tooltip>
|
|
8
|
+
<Tooltip v-else-if="job.status === 'DONE'">
|
|
9
|
+
<IconCheckCircleOutline class="w-6 h-6 ml-2 text-green-500" />
|
|
10
|
+
<template #tooltip>
|
|
11
|
+
{{ t('Done') }}
|
|
12
|
+
</template>
|
|
13
|
+
</Tooltip>
|
|
14
|
+
<Tooltip v-else-if="job.status === 'CANCELLED'">
|
|
15
|
+
<IconCloseCircleOutline class="w-6 h-6 ml-2 text-red-500" />
|
|
16
|
+
<template #tooltip>
|
|
17
|
+
{{ t('Cancelled') }}
|
|
18
|
+
</template>
|
|
19
|
+
</Tooltip>
|
|
20
|
+
<Tooltip v-else-if="job.status === 'DONE_WITH_ERRORS'">
|
|
21
|
+
<IconExclamationCircleOutline class="w-6 h-6 ml-2 text-yellow-500" />
|
|
22
|
+
<template #tooltip>
|
|
23
|
+
{{ t('Done with errors') }}
|
|
24
|
+
</template>
|
|
25
|
+
</Tooltip>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import type { IJob } from './utils';
|
|
31
|
+
import { IconCheckCircleOutline, IconCloseCircleOutline, IconExclamationCircleOutline } from '@iconify-prerendered/vue-flowbite';
|
|
32
|
+
import { Spinner, Tooltip } from '@/afcl';
|
|
33
|
+
import { useI18n } from 'vue-i18n';
|
|
34
|
+
|
|
35
|
+
const { t } = useI18n();
|
|
36
|
+
|
|
37
|
+
const props = defineProps<{
|
|
38
|
+
job: IJob;
|
|
39
|
+
}>();
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".", // This should point to your project root
|
|
4
|
+
"paths": {
|
|
5
|
+
"@/*": [
|
|
6
|
+
"../node_modules/adminforth/dist/spa/src/*"
|
|
7
|
+
],
|
|
8
|
+
"*": [
|
|
9
|
+
"../node_modules/adminforth/dist/spa/node_modules/*"
|
|
10
|
+
],
|
|
11
|
+
"@@/*": [
|
|
12
|
+
"."
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AdminForthComponentDeclarationFull } from "adminforth";
|
|
2
|
+
export interface IJob {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
|
|
6
|
+
progress: number; // 0 to 100
|
|
7
|
+
createdAt: Date;
|
|
8
|
+
customComponent?: AdminForthComponentDeclarationFull;
|
|
9
|
+
}
|