feat: Actions system — scheduled mutations on external services
Full-stack implementation of provider-scoped Actions with extensible executor architecture. First action type: Immich auto_organize (sort assets into albums by person, CLIP search, date range, favorites). Core: - ActionTypeDefinition registry + ActionExecutor ABC with execute/validate/dry-run - ImmichActionExecutor with multi-album support and client-side filtering - ImmichClient write methods: add/remove assets, create album, paginated search Server: - Action, ActionRule, ActionExecution DB models - Full CRUD API + manual execute + dry-run + execution history endpoints - APScheduler integration (interval + cron) for automated execution - Action type discovery API + provider people endpoint Frontend: - Actions page with CRUD, execute/dry-run buttons, inline rule editor - RuleEditor: person/album MultiEntitySelect pickers, criteria config - ExecutionHistory: expandable per-rule result details - MultiEntitySelect reusable component (searchable multi-pick palette) - Notification tracker album picker migrated to MultiEntitySelect - Fixed MdiIcon race condition (icons missing after cache-clearing reload)
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import IconPicker from '$lib/components/IconPicker.svelte';
|
||||
import Hint from '$lib/components/Hint.svelte';
|
||||
import EntitySelect from '$lib/components/EntitySelect.svelte';
|
||||
import MultiEntitySelect from '$lib/components/MultiEntitySelect.svelte';
|
||||
|
||||
interface Props {
|
||||
form: {
|
||||
@@ -18,15 +19,15 @@
|
||||
};
|
||||
providerItems: { value: number; label: string; icon: string; desc: string }[];
|
||||
collections: any[];
|
||||
collectionFilter: string;
|
||||
collectionFilter?: string;
|
||||
editing: number | null;
|
||||
submitting: boolean;
|
||||
linkCheckLoading: boolean;
|
||||
error: string;
|
||||
providerType: string;
|
||||
onsave: (e: SubmitEvent) => void;
|
||||
ontoggleCollection: (collectionId: string) => void;
|
||||
formatDate: (dateStr: string) => string;
|
||||
ontoggleCollection?: (collectionId: string) => void;
|
||||
formatDate?: (dateStr: string) => string;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -91,22 +92,17 @@
|
||||
</div>
|
||||
{#if !isScheduler && collections.length > 0}
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">{t('notificationTracker.albums')} ({collections.length})</label>
|
||||
<input type="text" bind:value={collectionFilter} placeholder="Filter..."
|
||||
class="w-full px-3 py-1.5 mb-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
|
||||
<div class="max-h-56 overflow-y-auto border border-[var(--color-border)] rounded-md p-2 space-y-1">
|
||||
{#each collections.filter(a => !collectionFilter || (a.albumName || a.name || '').toLowerCase().includes(collectionFilter.toLowerCase())) as col}
|
||||
<label class="flex items-center justify-between text-sm cursor-pointer hover:bg-[var(--color-muted)] px-2 py-1 rounded">
|
||||
<span class="flex items-center gap-2">
|
||||
<input type="checkbox" checked={form.collection_ids.includes(col.id)} onchange={() => ontoggleCollection(col.id)} />
|
||||
{col.albumName || col.name} <span class="text-[var(--color-muted-foreground)]">({col.assetCount ?? col.asset_count ?? 0})</span>
|
||||
</span>
|
||||
{#if col.updatedAt || col.updated_at}
|
||||
<span class="text-xs text-[var(--color-muted-foreground)] whitespace-nowrap ml-2">{formatDate(col.updatedAt || col.updated_at)}</span>
|
||||
{/if}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
<label class="block text-sm font-medium mb-1">{t('notificationTracker.albums')}</label>
|
||||
<MultiEntitySelect
|
||||
items={collections.map(col => ({
|
||||
value: col.id,
|
||||
label: col.albumName || col.name,
|
||||
icon: 'mdiImageMultiple',
|
||||
desc: `${col.assetCount ?? col.asset_count ?? 0} assets`,
|
||||
}))}
|
||||
bind:values={form.collection_ids}
|
||||
placeholder={t('notificationTracker.selectAlbums')}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user