refactor: job vs queue naming (#23902)

This commit is contained in:
Jason Rasmussen
2025-11-14 14:42:00 -05:00
committed by GitHub
parent 1200bfad13
commit d784d431d0
36 changed files with 1356 additions and 1325 deletions

View File

@@ -4,8 +4,8 @@
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { getJobName } from '$lib/utils';
import { JobName, type SystemConfigJobDto } from '@immich/sdk';
import { getQueueName } from '$lib/utils';
import { QueueName, type SystemConfigJobDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
@@ -13,18 +13,18 @@
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
const jobNames = [
JobName.ThumbnailGeneration,
JobName.MetadataExtraction,
JobName.Library,
JobName.Sidecar,
JobName.SmartSearch,
JobName.FaceDetection,
JobName.FacialRecognition,
JobName.VideoConversion,
JobName.StorageTemplateMigration,
JobName.Migration,
JobName.Ocr,
const queueNames = [
QueueName.ThumbnailGeneration,
QueueName.MetadataExtraction,
QueueName.Library,
QueueName.Sidecar,
QueueName.SmartSearch,
QueueName.FaceDetection,
QueueName.FacialRecognition,
QueueName.VideoConversion,
QueueName.StorageTemplateMigration,
QueueName.Migration,
QueueName.Ocr,
];
function isSystemConfigJobDto(jobName: string): jobName is keyof SystemConfigJobDto {
@@ -35,22 +35,22 @@
<div>
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" onsubmit={(event) => event.preventDefault()}>
{#each jobNames as jobName (jobName)}
{#each queueNames as queueName (queueName)}
<div class="ms-4 mt-4 flex flex-col gap-4">
{#if isSystemConfigJobDto(jobName)}
{#if isSystemConfigJobDto(queueName)}
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
{disabled}
label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })}
label={$t('admin.job_concurrency', { values: { job: $getQueueName(queueName) } })}
description=""
bind:value={configToEdit.job[jobName].concurrency}
bind:value={configToEdit.job[queueName].concurrency}
required={true}
isEdited={!(configToEdit.job[jobName].concurrency == config.job[jobName].concurrency)}
isEdited={!(configToEdit.job[queueName].concurrency == config.job[queueName].concurrency)}
/>
{:else}
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })}
label={$t('admin.job_concurrency', { values: { job: $getQueueName(queueName) } })}
description=""
value={1}
disabled={true}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Badge from '$lib/elements/Badge.svelte';
import { locale } from '$lib/stores/preferences.store';
import { JobCommand, type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@immich/sdk';
import { QueueCommand, type QueueCommandDto, type QueueStatisticsDto, type QueueStatusDto } from '@immich/sdk';
import { Icon, IconButton } from '@immich/ui';
import {
mdiAlertCircle,
@@ -22,21 +22,21 @@
title: string;
subtitle: string | undefined;
description: Component | undefined;
jobCounts: JobCountsDto;
statistics: QueueStatisticsDto;
queueStatus: QueueStatusDto;
icon: string;
disabled?: boolean;
allText: string | undefined;
refreshText: string | undefined;
missingText: string;
onCommand: (command: JobCommandDto) => void;
onCommand: (command: QueueCommandDto) => void;
}
let {
title,
subtitle,
description,
jobCounts,
statistics,
queueStatus,
icon,
disabled = false,
@@ -46,7 +46,7 @@
onCommand,
}: Props = $props();
let waitingCount = $derived(jobCounts.waiting + jobCounts.paused + jobCounts.delayed);
let waitingCount = $derived(statistics.waiting + statistics.paused + statistics.delayed);
let isIdle = $derived(!queueStatus.isActive && !queueStatus.isPaused);
let multipleButtons = $derived(allText || refreshText);
@@ -67,11 +67,11 @@
<span class="uppercase">{title}</span>
</span>
<div class="flex gap-2">
{#if jobCounts.failed > 0}
{#if statistics.failed > 0}
<Badge>
<div class="flex flex-row gap-1">
<span class="text-sm">
{$t('admin.jobs_failed', { values: { jobCount: jobCounts.failed.toLocaleString($locale) } })}
{$t('admin.jobs_failed', { values: { jobCount: statistics.failed.toLocaleString($locale) } })}
</span>
<IconButton
color="primary"
@@ -79,15 +79,15 @@
aria-label={$t('clear_message')}
size="tiny"
shape="round"
onclick={() => onCommand({ command: JobCommand.ClearFailed, force: false })}
onclick={() => onCommand({ command: QueueCommand.ClearFailed, force: false })}
/>
</div>
</Badge>
{/if}
{#if jobCounts.delayed > 0}
{#if statistics.delayed > 0}
<Badge>
<span class="text-sm">
{$t('admin.jobs_delayed', { values: { jobCount: jobCounts.delayed.toLocaleString($locale) } })}
{$t('admin.jobs_delayed', { values: { jobCount: statistics.delayed.toLocaleString($locale) } })}
</span>
</Badge>
{/if}
@@ -111,7 +111,7 @@
>
<p>{$t('active')}</p>
<p class="text-2xl">
{jobCounts.active.toLocaleString($locale)}
{statistics.active.toLocaleString($locale)}
</p>
</div>
@@ -131,7 +131,7 @@
<JobTileButton
disabled={true}
color="light-gray"
onClick={() => onCommand({ command: JobCommand.Start, force: false })}
onClick={() => onCommand({ command: QueueCommand.Start, force: false })}
>
<Icon icon={mdiAlertCircle} size="36" />
<span class="uppercase">{$t('disabled')}</span>
@@ -140,20 +140,20 @@
{#if !disabled && !isIdle}
{#if waitingCount > 0}
<JobTileButton color="gray" onClick={() => onCommand({ command: JobCommand.Empty, force: false })}>
<JobTileButton color="gray" onClick={() => onCommand({ command: QueueCommand.Empty, force: false })}>
<Icon icon={mdiClose} size="24" />
<span class="uppercase">{$t('clear')}</span>
</JobTileButton>
{/if}
{#if queueStatus.isPaused}
{@const size = waitingCount > 0 ? '24' : '48'}
<JobTileButton color="light-gray" onClick={() => onCommand({ command: JobCommand.Resume, force: false })}>
<JobTileButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Resume, force: false })}>
<!-- size property is not reactive, so have to use width and height -->
<Icon icon={mdiFastForward} {size} />
<span class="uppercase">{$t('resume')}</span>
</JobTileButton>
{:else}
<JobTileButton color="light-gray" onClick={() => onCommand({ command: JobCommand.Pause, force: false })}>
<JobTileButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Pause, force: false })}>
<Icon icon={mdiPause} size="24" />
<span class="uppercase">{$t('pause')}</span>
</JobTileButton>
@@ -162,25 +162,25 @@
{#if !disabled && multipleButtons && isIdle}
{#if allText}
<JobTileButton color="dark-gray" onClick={() => onCommand({ command: JobCommand.Start, force: true })}>
<JobTileButton color="dark-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: true })}>
<Icon icon={mdiAllInclusive} size="24" />
<span class="uppercase">{allText}</span>
</JobTileButton>
{/if}
{#if refreshText}
<JobTileButton color="gray" onClick={() => onCommand({ command: JobCommand.Start, force: undefined })}>
<JobTileButton color="gray" onClick={() => onCommand({ command: QueueCommand.Start, force: undefined })}>
<Icon icon={mdiImageRefreshOutline} size="24" />
<span class="uppercase">{refreshText}</span>
</JobTileButton>
{/if}
<JobTileButton color="light-gray" onClick={() => onCommand({ command: JobCommand.Start, force: false })}>
<JobTileButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: false })}>
<Icon icon={mdiSelectionSearch} size="24" />
<span class="uppercase">{missingText}</span>
</JobTileButton>
{/if}
{#if !disabled && !multipleButtons && isIdle}
<JobTileButton color="light-gray" onClick={() => onCommand({ command: JobCommand.Start, force: false })}>
<JobTileButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: false })}>
<Icon icon={mdiPlay} size="48" />
<span class="uppercase">{missingText}</span>
</JobTileButton>

View File

@@ -1,8 +1,14 @@
<script lang="ts">
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { getJobName } from '$lib/utils';
import { getQueueName } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { JobCommand, JobName, sendJobCommand, type AllJobStatusResponseDto, type JobCommandDto } from '@immich/sdk';
import {
QueueCommand,
type QueueCommandDto,
QueueName,
type QueuesResponseDto,
runQueueCommandLegacy,
} from '@immich/sdk';
import { modalManager, toastManager } from '@immich/ui';
import {
mdiContentDuplicate,
@@ -23,7 +29,7 @@
import StorageMigrationDescription from './StorageMigrationDescription.svelte';
interface Props {
jobs: AllJobStatusResponseDto;
jobs: QueuesResponseDto;
}
let { jobs = $bindable() }: Props = $props();
@@ -38,17 +44,17 @@
missingText: string;
disabled?: boolean;
icon: string;
handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>;
handleCommand?: (jobId: QueueName, jobCommand: QueueCommandDto) => Promise<void>;
};
const handleConfirmCommand = async (jobId: JobName, dto: JobCommandDto) => {
const handleConfirmCommand = async (jobId: QueueName, dto: QueueCommandDto) => {
if (dto.force) {
const isConfirmed = await modalManager.showDialog({
prompt: $t('admin.confirm_reprocess_all_faces'),
});
if (isConfirmed) {
await handleCommand(jobId, { command: JobCommand.Start, force: true });
await handleCommand(jobId, { command: QueueCommand.Start, force: true });
return;
}
@@ -58,54 +64,54 @@
await handleCommand(jobId, dto);
};
let jobDetails: Partial<Record<JobName, JobDetails>> = {
[JobName.ThumbnailGeneration]: {
let jobDetails: Partial<Record<QueueName, JobDetails>> = {
[QueueName.ThumbnailGeneration]: {
icon: mdiFileJpgBox,
title: $getJobName(JobName.ThumbnailGeneration),
title: $getQueueName(QueueName.ThumbnailGeneration),
subtitle: $t('admin.thumbnail_generation_job_description'),
allText: $t('all'),
missingText: $t('missing'),
},
[JobName.MetadataExtraction]: {
[QueueName.MetadataExtraction]: {
icon: mdiTable,
title: $getJobName(JobName.MetadataExtraction),
title: $getQueueName(QueueName.MetadataExtraction),
subtitle: $t('admin.metadata_extraction_job_description'),
allText: $t('all'),
missingText: $t('missing'),
},
[JobName.Library]: {
[QueueName.Library]: {
icon: mdiLibraryShelves,
title: $getJobName(JobName.Library),
title: $getQueueName(QueueName.Library),
subtitle: $t('admin.library_tasks_description'),
missingText: $t('rescan'),
},
[JobName.Sidecar]: {
title: $getJobName(JobName.Sidecar),
[QueueName.Sidecar]: {
title: $getQueueName(QueueName.Sidecar),
icon: mdiFileXmlBox,
subtitle: $t('admin.sidecar_job_description'),
allText: $t('sync'),
missingText: $t('discover'),
disabled: !featureFlags.sidecar,
},
[JobName.SmartSearch]: {
[QueueName.SmartSearch]: {
icon: mdiImageSearch,
title: $getJobName(JobName.SmartSearch),
title: $getQueueName(QueueName.SmartSearch),
subtitle: $t('admin.smart_search_job_description'),
allText: $t('all'),
missingText: $t('missing'),
disabled: !featureFlags.smartSearch,
},
[JobName.DuplicateDetection]: {
[QueueName.DuplicateDetection]: {
icon: mdiContentDuplicate,
title: $getJobName(JobName.DuplicateDetection),
title: $getQueueName(QueueName.DuplicateDetection),
subtitle: $t('admin.duplicate_detection_job_description'),
allText: $t('all'),
missingText: $t('missing'),
disabled: !featureFlags.duplicateDetection,
},
[JobName.FaceDetection]: {
[QueueName.FaceDetection]: {
icon: mdiFaceRecognition,
title: $getJobName(JobName.FaceDetection),
title: $getQueueName(QueueName.FaceDetection),
subtitle: $t('admin.face_detection_description'),
allText: $t('reset'),
refreshText: $t('refresh'),
@@ -113,67 +119,67 @@
handleCommand: handleConfirmCommand,
disabled: !featureFlags.facialRecognition,
},
[JobName.FacialRecognition]: {
[QueueName.FacialRecognition]: {
icon: mdiTagFaces,
title: $getJobName(JobName.FacialRecognition),
title: $getQueueName(QueueName.FacialRecognition),
subtitle: $t('admin.facial_recognition_job_description'),
allText: $t('reset'),
missingText: $t('missing'),
handleCommand: handleConfirmCommand,
disabled: !featureFlags.facialRecognition,
},
[JobName.Ocr]: {
[QueueName.Ocr]: {
icon: mdiOcr,
title: $getJobName(JobName.Ocr),
title: $getQueueName(QueueName.Ocr),
subtitle: $t('admin.ocr_job_description'),
allText: $t('all'),
missingText: $t('missing'),
disabled: !featureFlags.ocr,
},
[JobName.VideoConversion]: {
[QueueName.VideoConversion]: {
icon: mdiVideo,
title: $getJobName(JobName.VideoConversion),
title: $getQueueName(QueueName.VideoConversion),
subtitle: $t('admin.video_conversion_job_description'),
allText: $t('all'),
missingText: $t('missing'),
},
[JobName.StorageTemplateMigration]: {
[QueueName.StorageTemplateMigration]: {
icon: mdiFolderMove,
title: $getJobName(JobName.StorageTemplateMigration),
title: $getQueueName(QueueName.StorageTemplateMigration),
missingText: $t('start'),
description: StorageMigrationDescription,
},
[JobName.Migration]: {
[QueueName.Migration]: {
icon: mdiFolderMove,
title: $getJobName(JobName.Migration),
title: $getQueueName(QueueName.Migration),
subtitle: $t('admin.migration_job_description'),
missingText: $t('start'),
},
};
let jobList = Object.entries(jobDetails) as [JobName, JobDetails][];
let jobList = Object.entries(jobDetails) as [QueueName, JobDetails][];
async function handleCommand(jobId: JobName, jobCommand: JobCommandDto) {
const title = jobDetails[jobId]?.title;
async function handleCommand(name: QueueName, dto: QueueCommandDto) {
const title = jobDetails[name]?.title;
try {
jobs[jobId] = await sendJobCommand({ id: jobId, jobCommandDto: jobCommand });
jobs[name] = await runQueueCommandLegacy({ name, queueCommandDto: dto });
switch (jobCommand.command) {
case JobCommand.Empty: {
switch (dto.command) {
case QueueCommand.Empty: {
toastManager.success($t('admin.cleared_jobs', { values: { job: title } }));
break;
}
}
} catch (error) {
handleError(error, $t('admin.failed_job_command', { values: { command: jobCommand.command, job: title } }));
handleError(error, $t('admin.failed_job_command', { values: { command: dto.command, job: title } }));
}
}
</script>
<div class="flex flex-col gap-7">
{#each jobList as [jobName, { title, subtitle, description, disabled, allText, refreshText, missingText, icon, handleCommand: handleCommandOverride }] (jobName)}
{@const { jobCounts, queueStatus } = jobs[jobName]}
{@const { jobCounts: statistics, queueStatus } = jobs[jobName]}
<JobTile
{icon}
{title}
@@ -183,7 +189,7 @@
{allText}
{refreshText}
{missingText}
{jobCounts}
{statistics}
{queueStatus}
onCommand={(command) => (handleCommandOverride || handleCommand)(jobName, command)}
/>