|
@@ -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'
|
|
|
- };
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- return names[jobName];
|
|
|
- }
|
|
|
+ const jobDetailsArray = Object.entries(jobDetails) as [JobName, JobDetails][];
|
|
|
|
|
|
- const start = async (jobId: JobName, force: boolean) => {
|
|
|
- const label = getJobLabel(jobId);
|
|
|
+ async function runJob(jobId: JobName, jobCommand: JobCommandDto) {
|
|
|
+ const title = jobDetails[jobId]?.title;
|
|
|
|
|
|
try {
|
|
|
- await api.jobApi.sendJobCommand(jobId, { command: JobCommand.Start, force });
|
|
|
-
|
|
|
- jobs[jobId].active += 1;
|
|
|
-
|
|
|
- notificationController.show({
|
|
|
- message: `Started job: ${label}`,
|
|
|
- type: NotificationType.Info
|
|
|
- });
|
|
|
+ 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, `Unable to start job: ${label}`);
|
|
|
+ handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);
|
|
|
}
|
|
|
- };
|
|
|
+ }
|
|
|
</script>
|
|
|
|
|
|
<div class="flex flex-col gap-7">
|
|
|
- {#if jobs}
|
|
|
- <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]}
|
|
|
- >
|
|
|
- Note that some assets may not have any objects detected
|
|
|
- </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]}
|
|
|
- />
|
|
|
-
|
|
|
+ {#each jobDetailsArray as [jobName, { title, subtitle, allowForceCommand, component }]}
|
|
|
<JobTile
|
|
|
- title="Storage migration"
|
|
|
- showOptions={false}
|
|
|
- subtitle={''}
|
|
|
- on:click={(e) => start(JobName.StorageTemplateMigrationQueue, e.detail.force)}
|
|
|
- jobCounts={jobs[JobName.StorageTemplateMigrationQueue]}
|
|
|
+ {title}
|
|
|
+ {subtitle}
|
|
|
+ {allowForceCommand}
|
|
|
+ on:command={({ detail }) => runJob(jobName, detail)}
|
|
|
+ jobCounts={jobs[jobName]}
|
|
|
>
|
|
|
- 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
|
|
|
+ <svelte:component this={component} />
|
|
|
</JobTile>
|
|
|
- {/if}
|
|
|
+ {/each}
|
|
|
</div>
|