Skip to content

Commit 157613e

Browse files
committed
progress[3]
1 parent 2447769 commit 157613e

6 files changed

Lines changed: 108 additions & 29 deletions

File tree

custom/JobInfoPopup.vue

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
:show-progress="false"
1919
:height="6"
2020
/>
21-
<Button class="h-8"> Stop </Button>
21+
<Button class="h-8" v-if="job.status === 'IN_PROGRESS'" @click="cancelJob"> {{ t('Cancel') }} </Button>
2222
</div>
2323
</div>
2424
<slot></slot>
@@ -30,14 +30,47 @@
3030
<script setup lang="ts">
3131
import type { IJob } from './utils';
3232
import { ProgressBar, Button } from '@/afcl';
33-
import { getTimeAgoString } from '@/utils';
33+
import { getTimeAgoString, callAdminForthApi} from '@/utils';
3434
import { useI18n } from 'vue-i18n';
3535
import StateToIcon from './StateToIcon.vue';
36+
import { useAdminforth } from '@/adminforth';
37+
3638
3739
const { t } = useI18n();
3840
41+
const adminforth = useAdminforth();
3942
4043
const props = defineProps<{
4144
job: IJob;
45+
meta: {
46+
pluginInstanceId: string;
47+
};
4248
}>();
49+
50+
async function cancelJob() {
51+
// Implement job cancellation logic here
52+
const isConfirmed = await adminforth.confirm({ message: t('Are you sure you want to cancel this job?') });
53+
if (!isConfirmed) {
54+
return;
55+
}
56+
const failedToCancelText = t('Failed to cancel job');
57+
console.log(`Canceling job with ID: ${props.job.id}`);
58+
try {
59+
const res = await callAdminForthApi({
60+
path: `/plugin/${props.meta.pluginInstanceId}/cancel-job`,
61+
method: 'POST',
62+
body: {
63+
jobId: props.job.id,
64+
},
65+
});
66+
if (res.ok) {
67+
adminforth.alert({ message: t('Job cancelled successfully'), variant: 'success' });
68+
} else {
69+
adminforth.alert({ message: failedToCancelText, variant: 'danger' });
70+
}
71+
} catch (error) {
72+
adminforth.alert({ message: failedToCancelText, variant: 'danger' });
73+
console.error('Error canceling job:', error);
74+
}
75+
}
4376
</script>

custom/JobsList.vue

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
</div>
2424
</template>
2525
<div>
26-
<JobInfoPopup :job="job" />
26+
<JobInfoPopup
27+
:job="job"
28+
:meta="meta"
29+
/>
2730
</div>
2831
</Modal>
2932

@@ -35,15 +38,15 @@
3538
import type { IJob } from './utils';
3639
import { getTimeAgoString } from '@/utils';
3740
import { ProgressBar, Modal } from '@/afcl';
38-
import { useI18n } from 'vue-i18n';
3941
import JobInfoPopup from './JobInfoPopup.vue';
4042
import StateToIcon from './StateToIcon.vue';
4143
42-
const { t } = useI18n();
43-
4444
const props = defineProps<{
4545
jobs: IJob[];
4646
closeDropdown: () => void;
47+
meta: {
48+
pluginInstanceId: string;
49+
};
4750
}>();
4851
4952

custom/NavbarJobs.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<JobsList
2727
:closeDropdown="() => isDropdownOpen = false"
2828
:jobs="jobs"
29+
:meta="meta"
2930
/>
3031
</div>
3132
</Transition>

custom/StateToIcon.vue

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
<template>
22
<Tooltip v-if="job.status === 'IN_PROGRESS'">
3-
<Spinner class="w-4 h-4 ml-2" />
3+
<Spinner class="w-5 h-5 ml-2" />
44
<template #tooltip>
55
{{ t('In progress') }}
66
</template>
77
</Tooltip>
88
<Tooltip v-else-if="job.status === 'DONE'">
9-
<IconCheckCircleOutline class="w-5 h-5 ml-2 text-green-500" />
10-
<template #tooltip>
11-
{{ t('Done') }}
12-
</template>
9+
<IconCheckCircleOutline class="w-6 h-6 ml-2 text-green-500" />
10+
<template #tooltip>
11+
{{ t('Done') }}
12+
</template>
1313
</Tooltip>
14-
<Tooltip v-else-if="job.status === 'CANCELED'">
15-
<IconCloseCircleOutline class="w-5 h-5 ml-2 text-red-500" />
16-
<template #tooltip>
17-
{{ t('Canceled') }}
18-
</template>
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>
1919
</Tooltip>
2020
<Tooltip v-else-if="job.status === 'DONE_WITH_ERRORS'">
21-
<IconExclamationCircleOutline class="w-5 h-5 ml-2 text-yellow-500" />
22-
<template #tooltip>
23-
{{ t('Done with errors') }}
24-
</template>
21+
<IconExclamationCircleOutline class="w-6 h-6 ml-2 text-yellow-500" />
22+
<template #tooltip>
23+
{{ t('Done with errors') }}
24+
</template>
2525
</Tooltip>
2626
</template>
2727

custom/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export interface IJob {
22
id: string;
33
name: string;
4-
status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELED';
4+
status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
55
progress: number; // 0 to 100
66
createdAt: Date;
77
}

index.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Level } from 'level';
77
import { resolve } from "node:dns";
88
import { set } from "@vueuse/core";
99

10-
type JobStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
10+
type TaskStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
1111
type setStateFieldParams = (state: Record<string, any>) => void;
1212
type getStateFieldParams = () => any;
1313

@@ -65,7 +65,7 @@ export default class extends AdminForthPlugin {
6565
await levelDb.put(taskId, JSON.stringify({ state, status }));
6666
}
6767

68-
private async setLevelDbTaskStatusField(levelDb: Level, taskId: string, status: JobStatus) {
68+
private async setLevelDbTaskStatusField(levelDb: Level, taskId: string, status: TaskStatus) {
6969
const state = await this.getLevelDbTaskStateField(levelDb, taskId);
7070
await levelDb.del(taskId);
7171
await levelDb.put(taskId, JSON.stringify({ state, status }));
@@ -81,7 +81,7 @@ export default class extends AdminForthPlugin {
8181
return Promise.resolve(null);
8282
}
8383

84-
private async getLevelDbTaskStatusField(levelDb: Level, taskId: string): Promise<JobStatus> {
84+
private async getLevelDbTaskStatusField(levelDb: Level, taskId: string): Promise<TaskStatus> {
8585
const state = await levelDb.get(taskId);
8686
if (state) {
8787
const parsedState = JSON.parse(state);
@@ -141,8 +141,23 @@ export default class extends AdminForthPlugin {
141141

142142
const totalTasks = tasks.length;
143143
let completedTasks = 0;
144+
let failedTasks = 0;
145+
let lastJobStatus = 'IN_PROGRESS';
144146

145147
const taskHandler = async ( taskIndex: number, taskState ) => {
148+
if (lastJobStatus === 'CANCELLED') {
149+
afLogger.info(`Job ${jobId} was cancelled. Skipping task ${taskIndex}.`);
150+
return;
151+
}
152+
153+
const currentJobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
154+
const currentJobStatus = currentJobRecord[this.options.statusField];
155+
156+
if (currentJobStatus === 'CANCELLED') {
157+
lastJobStatus = 'CANCELLED';
158+
afLogger.info(`Job ${jobId} was cancelled. Skipping task ${taskIndex}.`);
159+
return;
160+
}
146161

147162
//define the setTaskStateField and getTaskStateField functions to pass to the task
148163
const setTaskStateField = async (state: Record<string, any>) => {
@@ -163,6 +178,7 @@ export default class extends AdminForthPlugin {
163178
} catch (error) {
164179
afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${error}`, );
165180
await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
181+
failedTasks++;
166182
return;
167183
} finally {
168184
//Update progress
@@ -181,11 +197,17 @@ export default class extends AdminForthPlugin {
181197
});
182198

183199
await Promise.all(tasksToExecute);
184-
185-
await this.adminforth.resource(this.getResourceId()).update(jobId, {
186-
[this.options.statusField]: 'DONE',
187-
})
188-
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
200+
if (lastJobStatus !== 'CANCELLED' && failedTasks === 0) {
201+
await this.adminforth.resource(this.getResourceId()).update(jobId, {
202+
[this.options.statusField]: 'DONE',
203+
})
204+
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE' });
205+
} else if (failedTasks > 0) {
206+
await this.adminforth.resource(this.getResourceId()).update(jobId, {
207+
[this.options.statusField]: 'DONE_WITH_ERRORS',
208+
})
209+
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
210+
}
189211
}
190212

191213
private async runProcessingUnfinishedTasks(
@@ -268,6 +290,26 @@ export default class extends AdminForthPlugin {
268290
return { jobs: jobsToReturn };
269291
}
270292
});
293+
294+
server.endpoint({
295+
method: 'POST',
296+
path: `/plugin/${this.pluginInstanceId}/cancel-job`,
297+
handler: async ({ body }) => {
298+
const jobId = body.jobId;
299+
try {
300+
await this.adminforth.resource(this.getResourceId()).update(jobId, {
301+
[this.options.statusField]: 'CANCELLED',
302+
});
303+
this.adminforth.websocket.publish('/background-jobs', {
304+
jobId,
305+
status: 'CANCELLED',
306+
});
307+
return { ok: true };
308+
} catch (error) {
309+
return { ok: false }
310+
}
311+
}
312+
});
271313
}
272314

273315
}

0 commit comments

Comments
 (0)