mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(web): pause and resume jobs (#2125)
* feat(web): pause and resume jobs * add bg color to status instead of using badge * styling --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
@@ -1,115 +1,87 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
notificationController,
|
||||
NotificationType
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { AllJobStatusResponseDto, api, JobCommand, JobName } from '@api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { AllJobStatusResponseDto, api, JobCommand, JobCommandDto, JobName } from '@api';
|
||||
import type { ComponentType } from 'svelte';
|
||||
import JobTile from './job-tile.svelte';
|
||||
import StorageMigrationDescription from './storage-migration-description.svelte';
|
||||
|
||||
let jobs: AllJobStatusResponseDto;
|
||||
let timer: NodeJS.Timer;
|
||||
export let jobs: AllJobStatusResponseDto;
|
||||
|
||||
const load = async () => {
|
||||
const { data } = await api.jobApi.getAllJobsStatus();
|
||||
jobs = data;
|
||||
type JobDetails = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
allowForceCommand?: boolean;
|
||||
component?: ComponentType;
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
await load();
|
||||
timer = setInterval(async () => await load(), 5_000);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(timer);
|
||||
});
|
||||
|
||||
function getJobLabel(jobName: JobName) {
|
||||
const names: Record<JobName, string> = {
|
||||
[JobName.ThumbnailGenerationQueue]: 'Generate Thumbnails',
|
||||
[JobName.MetadataExtractionQueue]: 'Extract Metadata',
|
||||
[JobName.VideoConversionQueue]: 'Transcode Videos',
|
||||
[JobName.ObjectTaggingQueue]: 'Tag Objects',
|
||||
[JobName.ClipEncodingQueue]: 'Clip Encoding',
|
||||
[JobName.BackgroundTaskQueue]: 'Background Task',
|
||||
[JobName.StorageTemplateMigrationQueue]: 'Storage Template Migration',
|
||||
[JobName.SearchQueue]: 'Search'
|
||||
};
|
||||
|
||||
return names[jobName];
|
||||
}
|
||||
|
||||
const start = async (jobId: JobName, force: boolean) => {
|
||||
const label = getJobLabel(jobId);
|
||||
|
||||
try {
|
||||
await api.jobApi.sendJobCommand(jobId, { command: JobCommand.Start, force });
|
||||
|
||||
jobs[jobId].active += 1;
|
||||
|
||||
notificationController.show({
|
||||
message: `Started job: ${label}`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, `Unable to start job: ${label}`);
|
||||
const jobDetails: { [Key in JobName]?: JobDetails } = {
|
||||
[JobName.ThumbnailGenerationQueue]: {
|
||||
title: 'Generate Thumbnails',
|
||||
subtitle: 'Regenerate JPEG and WebP thumbnails'
|
||||
},
|
||||
[JobName.MetadataExtractionQueue]: {
|
||||
title: 'Extract Metadata',
|
||||
subtitle: 'Extract metadata information i.e. GPS, resolution...etc'
|
||||
},
|
||||
[JobName.ObjectTaggingQueue]: {
|
||||
title: 'Tag Objects',
|
||||
subtitle:
|
||||
'Run machine learning to tag objects\nNote that some assets may not have any objects detected'
|
||||
},
|
||||
[JobName.ClipEncodingQueue]: {
|
||||
title: 'Encode Clip',
|
||||
subtitle: 'Run machine learning to generate clip embeddings'
|
||||
},
|
||||
[JobName.VideoConversionQueue]: {
|
||||
title: 'Transcode Videos',
|
||||
subtitle: 'Transcode videos not in the desired format'
|
||||
},
|
||||
[JobName.StorageTemplateMigrationQueue]: {
|
||||
title: 'Storage Template Migration',
|
||||
allowForceCommand: false,
|
||||
component: StorageMigrationDescription
|
||||
}
|
||||
};
|
||||
|
||||
const jobDetailsArray = Object.entries(jobDetails) as [JobName, JobDetails][];
|
||||
|
||||
async function runJob(jobId: JobName, jobCommand: JobCommandDto) {
|
||||
const title = jobDetails[jobId]?.title;
|
||||
|
||||
try {
|
||||
await api.jobApi.sendJobCommand(jobId, jobCommand);
|
||||
|
||||
// TODO: Return actual job status from server and use that.
|
||||
switch (jobCommand.command) {
|
||||
case JobCommand.Start:
|
||||
jobs[jobId].active += 1;
|
||||
break;
|
||||
case JobCommand.Resume:
|
||||
jobs[jobId].active += 1;
|
||||
jobs[jobId].paused = 0;
|
||||
break;
|
||||
case JobCommand.Pause:
|
||||
jobs[jobId].paused += 1;
|
||||
jobs[jobId].active = 0;
|
||||
jobs[jobId].waiting = 0;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-7">
|
||||
{#if jobs}
|
||||
{#each jobDetailsArray as [jobName, { title, subtitle, allowForceCommand, component }]}
|
||||
<JobTile
|
||||
title="Generate thumbnails"
|
||||
subtitle="Regenerate JPEG and WebP thumbnails"
|
||||
on:click={(e) => start(JobName.ThumbnailGenerationQueue, e.detail.force)}
|
||||
jobCounts={jobs[JobName.ThumbnailGenerationQueue]}
|
||||
/>
|
||||
|
||||
<JobTile
|
||||
title="Extract Metadata"
|
||||
subtitle="Extract metadata information i.e. GPS, resolution...etc"
|
||||
on:click={(e) => start(JobName.MetadataExtractionQueue, e.detail.force)}
|
||||
jobCounts={jobs[JobName.MetadataExtractionQueue]}
|
||||
/>
|
||||
|
||||
<JobTile
|
||||
title="Tag Objects"
|
||||
subtitle="Run machine learning to tag objects"
|
||||
on:click={(e) => start(JobName.ObjectTaggingQueue, e.detail.force)}
|
||||
jobCounts={jobs[JobName.ObjectTaggingQueue]}
|
||||
{title}
|
||||
{subtitle}
|
||||
{allowForceCommand}
|
||||
on:command={({ detail }) => runJob(jobName, detail)}
|
||||
jobCounts={jobs[jobName]}
|
||||
>
|
||||
Note that some assets may not have any objects detected
|
||||
<svelte:component this={component} />
|
||||
</JobTile>
|
||||
|
||||
<JobTile
|
||||
title="Encode Clip"
|
||||
subtitle="Run machine learning to generate clip embeddings"
|
||||
on:click={(e) => start(JobName.ClipEncodingQueue, e.detail.force)}
|
||||
jobCounts={jobs[JobName.ClipEncodingQueue]}
|
||||
/>
|
||||
|
||||
<JobTile
|
||||
title="Transcode Videos"
|
||||
subtitle="Transcode videos not in the desired format"
|
||||
on:click={(e) => start(JobName.VideoConversionQueue, e.detail.force)}
|
||||
jobCounts={jobs[JobName.VideoConversionQueue]}
|
||||
/>
|
||||
|
||||
<JobTile
|
||||
title="Storage migration"
|
||||
showOptions={false}
|
||||
subtitle={''}
|
||||
on:click={(e) => start(JobName.StorageTemplateMigrationQueue, e.detail.force)}
|
||||
jobCounts={jobs[JobName.StorageTemplateMigrationQueue]}
|
||||
>
|
||||
Apply the current
|
||||
<a
|
||||
href="/admin/system-settings?open=storage-template"
|
||||
class="text-immich-primary dark:text-immich-dark-primary">Storage template</a
|
||||
>
|
||||
to previously uploaded assets
|
||||
</JobTile>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user