Skip to content

Commit 06feedf

Browse files
committed
feat: implement job info modal and global API injection for job details access
1 parent ce1fb32 commit 06feedf

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

custom/GlobalJobApi.vue

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
5151
}
5252
});
5353

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+
5462
if (!this.adminforth.config.componentsToExplicitRegister) {
5563
this.adminforth.config.componentsToExplicitRegister = [];
5664
}
@@ -475,6 +483,31 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
475483
}
476484
});
477485

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+
478511
server.endpoint({
479512
method: 'POST',
480513
path: `/plugin/${this.pluginInstanceId}/cancel-job`,

0 commit comments

Comments
 (0)