mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat: remove 'none' trigger from sync strategies, add dirty tracking / unsaved changes to sync page
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { db } from '../db.ts';
|
||||
|
||||
// Types
|
||||
export type SyncTrigger = 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule';
|
||||
export type SyncTrigger = 'manual' | 'on_pull' | 'on_change' | 'schedule';
|
||||
|
||||
export interface ProfileSelection {
|
||||
databaseId: number;
|
||||
@@ -75,7 +75,7 @@ export const arrSyncQueries = {
|
||||
profileId: row.profile_id
|
||||
})),
|
||||
config: {
|
||||
trigger: (configRow?.trigger as SyncTrigger) ?? 'none',
|
||||
trigger: (configRow?.trigger as SyncTrigger) ?? 'manual',
|
||||
cron: configRow?.cron ?? null
|
||||
}
|
||||
};
|
||||
@@ -133,7 +133,7 @@ export const arrSyncQueries = {
|
||||
profileId: row.profile_id
|
||||
})),
|
||||
config: {
|
||||
trigger: (configRow?.trigger as SyncTrigger) ?? 'none',
|
||||
trigger: (configRow?.trigger as SyncTrigger) ?? 'manual',
|
||||
cron: configRow?.cron ?? null
|
||||
}
|
||||
};
|
||||
@@ -184,7 +184,7 @@ export const arrSyncQueries = {
|
||||
namingDatabaseId: row?.naming_database_id ?? null,
|
||||
qualityDefinitionsDatabaseId: row?.quality_definitions_database_id ?? null,
|
||||
mediaSettingsDatabaseId: row?.media_settings_database_id ?? null,
|
||||
trigger: (row?.trigger as SyncTrigger) ?? 'none',
|
||||
trigger: (row?.trigger as SyncTrigger) ?? 'manual',
|
||||
cron: row?.cron ?? null
|
||||
};
|
||||
},
|
||||
|
||||
@@ -240,22 +240,19 @@ export async function syncInstance(instanceId: number): Promise<ProcessSyncsResu
|
||||
const mmConfig = arrSyncQueries.getMediaManagementSync(instanceId);
|
||||
|
||||
// Sync quality profiles if configured
|
||||
if (qpConfig.config.trigger !== 'none' && qpConfig.selections.length > 0) {
|
||||
if (qpConfig.selections.length > 0) {
|
||||
const syncer = new QualityProfileSyncer(client, instanceId, instance.name, instance.type as SyncArrType);
|
||||
result.qualityProfiles = await syncer.sync();
|
||||
}
|
||||
|
||||
// Sync delay profiles if configured
|
||||
if (dpConfig.config.trigger !== 'none' && dpConfig.selections.length > 0) {
|
||||
if (dpConfig.selections.length > 0) {
|
||||
const syncer = new DelayProfileSyncer(client, instanceId, instance.name);
|
||||
result.delayProfiles = await syncer.sync();
|
||||
}
|
||||
|
||||
// Sync media management if configured
|
||||
if (
|
||||
mmConfig.trigger !== 'none' &&
|
||||
(mmConfig.namingDatabaseId || mmConfig.qualityDefinitionsDatabaseId || mmConfig.mediaSettingsDatabaseId)
|
||||
) {
|
||||
if (mmConfig.namingDatabaseId || mmConfig.qualityDefinitionsDatabaseId || mmConfig.mediaSettingsDatabaseId) {
|
||||
const syncer = new MediaManagementSyncer(client, instanceId, instance.name, instance.type as ArrType);
|
||||
result.mediaManagement = await syncer.sync();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export const actions: Actions = {
|
||||
|
||||
try {
|
||||
const selections: ProfileSelection[] = JSON.parse(selectionsJson || '[]');
|
||||
const effectiveTrigger = trigger || 'none';
|
||||
const effectiveTrigger = trigger || 'manual';
|
||||
const effectiveCron = cron || null;
|
||||
arrSyncQueries.saveQualityProfilesSync(id, selections, {
|
||||
trigger: effectiveTrigger,
|
||||
@@ -113,7 +113,7 @@ export const actions: Actions = {
|
||||
|
||||
try {
|
||||
const selections: ProfileSelection[] = JSON.parse(selectionsJson || '[]');
|
||||
const effectiveTrigger = trigger || 'none';
|
||||
const effectiveTrigger = trigger || 'manual';
|
||||
const effectiveCron = cron || null;
|
||||
arrSyncQueries.saveDelayProfilesSync(id, selections, {
|
||||
trigger: effectiveTrigger,
|
||||
@@ -151,7 +151,7 @@ export const actions: Actions = {
|
||||
const cron = formData.get('cron') as string | null;
|
||||
|
||||
try {
|
||||
const effectiveTrigger = trigger || 'none';
|
||||
const effectiveTrigger = trigger || 'manual';
|
||||
const effectiveCron = cron || null;
|
||||
arrSyncQueries.saveMediaManagementSync(id, {
|
||||
namingDatabaseId: namingDatabaseId ? parseInt(namingDatabaseId, 10) : null,
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
import type { PageData } from './$types';
|
||||
import { Info } from 'lucide-svelte';
|
||||
import InfoModal from '$ui/modal/InfoModal.svelte';
|
||||
import DirtyModal from '$ui/modal/DirtyModal.svelte';
|
||||
import QualityProfiles from './components/QualityProfiles.svelte';
|
||||
import DelayProfiles from './components/DelayProfiles.svelte';
|
||||
import MediaManagement from './components/MediaManagement.svelte';
|
||||
import type { SyncTrigger } from '$db/queries/arrSync.ts';
|
||||
import { initEdit, initCreate } from '$lib/client/stores/dirty';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -40,6 +42,19 @@
|
||||
};
|
||||
let mediaManagementTrigger: SyncTrigger = data.syncData.mediaManagement.trigger;
|
||||
let mediaManagementCron: string = data.syncData.mediaManagement.cron || '0 * * * *';
|
||||
|
||||
// Track dirty state from each component
|
||||
let qualityProfilesDirty = false;
|
||||
let delayProfilesDirty = false;
|
||||
let mediaManagementDirty = false;
|
||||
|
||||
// Sync combined dirty state to global dirty store for DirtyModal
|
||||
$: anyDirty = qualityProfilesDirty || delayProfilesDirty || mediaManagementDirty;
|
||||
$: if (anyDirty) {
|
||||
initCreate({});
|
||||
} else {
|
||||
initEdit({});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -72,18 +87,21 @@
|
||||
bind:state={qualityProfileState}
|
||||
bind:syncTrigger={qualityProfileTrigger}
|
||||
bind:cronExpression={qualityProfileCron}
|
||||
bind:isDirty={qualityProfilesDirty}
|
||||
/>
|
||||
<DelayProfiles
|
||||
databases={data.databases}
|
||||
bind:state={delayProfileState}
|
||||
bind:syncTrigger={delayProfileTrigger}
|
||||
bind:cronExpression={delayProfileCron}
|
||||
bind:isDirty={delayProfilesDirty}
|
||||
/>
|
||||
<MediaManagement
|
||||
databases={data.databases}
|
||||
bind:state={mediaManagementState}
|
||||
bind:syncTrigger={mediaManagementTrigger}
|
||||
bind:cronExpression={mediaManagementCron}
|
||||
bind:isDirty={mediaManagementDirty}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -153,3 +171,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</InfoModal>
|
||||
|
||||
<DirtyModal />
|
||||
|
||||
@@ -13,12 +13,18 @@
|
||||
|
||||
export let databases: DatabaseWithProfiles[];
|
||||
export let state: Record<number, Record<number, boolean>> = {};
|
||||
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
|
||||
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
|
||||
export let cronExpression: string = '0 * * * *';
|
||||
|
||||
let saving = false;
|
||||
let syncing = false;
|
||||
|
||||
// Track saved state for dirty detection
|
||||
let savedState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
$: currentState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
export let isDirty = false;
|
||||
$: isDirty = currentState !== savedState;
|
||||
|
||||
// Initialize state for all databases/profiles
|
||||
$: {
|
||||
for (const db of databases) {
|
||||
@@ -60,6 +66,8 @@
|
||||
|
||||
if (response.ok) {
|
||||
alertStore.add('success', 'Delay profiles sync config saved');
|
||||
// Update saved state to current
|
||||
savedState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
} else {
|
||||
alertStore.add('error', 'Failed to save delay profiles sync config');
|
||||
}
|
||||
@@ -139,5 +147,5 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} on:save={handleSave} on:sync={handleSync} />
|
||||
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} {isDirty} on:save={handleSave} on:sync={handleSync} />
|
||||
</div>
|
||||
|
||||
@@ -43,12 +43,18 @@
|
||||
showMediaDropdown = false;
|
||||
}
|
||||
|
||||
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
|
||||
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
|
||||
export let cronExpression: string = '0 * * * *';
|
||||
|
||||
let saving = false;
|
||||
let syncing = false;
|
||||
|
||||
// Track saved state for dirty detection
|
||||
let savedState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
$: currentState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
export let isDirty = false;
|
||||
$: isDirty = currentState !== savedState;
|
||||
|
||||
async function handleSave() {
|
||||
saving = true;
|
||||
try {
|
||||
@@ -66,6 +72,8 @@
|
||||
|
||||
if (response.ok) {
|
||||
alertStore.add('success', 'Media management sync config saved');
|
||||
// Update saved state to current
|
||||
savedState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
} else {
|
||||
alertStore.add('error', 'Failed to save media management sync config');
|
||||
}
|
||||
@@ -246,5 +254,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} on:save={handleSave} on:sync={handleSync} />
|
||||
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} {isDirty} on:save={handleSave} on:sync={handleSync} />
|
||||
</div>
|
||||
|
||||
@@ -13,12 +13,18 @@
|
||||
|
||||
export let databases: DatabaseWithProfiles[];
|
||||
export let state: Record<number, Record<number, boolean>> = {};
|
||||
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
|
||||
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
|
||||
export let cronExpression: string = '0 * * * *';
|
||||
|
||||
let saving = false;
|
||||
let syncing = false;
|
||||
|
||||
// Track saved state for dirty detection
|
||||
let savedState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
$: currentState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
export let isDirty = false;
|
||||
$: isDirty = currentState !== savedState;
|
||||
|
||||
// Initialize state for all databases/profiles
|
||||
$: {
|
||||
for (const db of databases) {
|
||||
@@ -60,6 +66,8 @@
|
||||
|
||||
if (response.ok) {
|
||||
alertStore.add('success', 'Quality profiles sync config saved');
|
||||
// Update saved state to current
|
||||
savedState = JSON.stringify({ state, syncTrigger, cronExpression });
|
||||
} else {
|
||||
alertStore.add('error', 'Failed to save quality profiles sync config');
|
||||
}
|
||||
@@ -139,5 +147,5 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} on:save={handleSave} on:sync={handleSync} />
|
||||
<SyncFooter bind:syncTrigger bind:cronExpression {saving} {syncing} {isDirty} on:save={handleSave} on:sync={handleSync} />
|
||||
</div>
|
||||
|
||||
@@ -3,20 +3,24 @@
|
||||
import { Check, RefreshCw, Save, Loader2 } from 'lucide-svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let syncTrigger: 'none' | 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'none';
|
||||
export let syncTrigger: 'manual' | 'on_pull' | 'on_change' | 'schedule' = 'manual';
|
||||
export let cronExpression: string = '0 * * * *';
|
||||
export let saving: boolean = false;
|
||||
export let syncing: boolean = false;
|
||||
export let isDirty: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{ save: void; sync: void }>();
|
||||
|
||||
const triggerOptions = [
|
||||
{ value: 'none', label: 'None' },
|
||||
{ value: 'manual', label: 'Manual' },
|
||||
{ value: 'on_pull', label: 'On Pull' },
|
||||
{ value: 'on_change', label: 'On Change' },
|
||||
{ value: 'schedule', label: 'Schedule' }
|
||||
] as const;
|
||||
|
||||
// Save disabled when not dirty, Sync disabled when dirty (unsaved changes)
|
||||
$: saveDisabled = saving || !isDirty;
|
||||
$: syncDisabled = syncing || isDirty;
|
||||
</script>
|
||||
|
||||
<div class="border-t border-neutral-200 px-6 py-4 dark:border-neutral-800">
|
||||
@@ -48,8 +52,9 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={syncing}
|
||||
disabled={syncDisabled}
|
||||
on:click={() => dispatch('sync')}
|
||||
title={isDirty ? 'Save changes before syncing' : ''}
|
||||
class="flex items-center gap-1.5 rounded-lg border border-neutral-300 bg-white px-3 py-1.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-50 disabled:opacity-50 disabled:cursor-not-allowed dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
|
||||
>
|
||||
{#if syncing}
|
||||
@@ -61,7 +66,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={saving}
|
||||
disabled={saveDisabled}
|
||||
on:click={() => dispatch('save')}
|
||||
class="flex items-center gap-1.5 rounded-lg bg-accent-600 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-accent-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user