mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-28 13:30:56 +01:00
feat(upgrades): add upgrade configuration management with CRUD operations
This commit is contained in:
@@ -12,6 +12,7 @@ import { migration as migration007 } from './migrations/007_create_notification_
|
||||
import { migration as migration008 } from './migrations/008_create_database_instances.ts';
|
||||
import { migration as migration009 } from './migrations/009_add_personal_access_token.ts';
|
||||
import { migration as migration010 } from './migrations/010_add_is_private.ts';
|
||||
import { migration as migration011 } from './migrations/011_create_upgrade_configs.ts';
|
||||
|
||||
export interface Migration {
|
||||
version: number;
|
||||
@@ -239,7 +240,8 @@ export function loadMigrations(): Migration[] {
|
||||
migration007,
|
||||
migration008,
|
||||
migration009,
|
||||
migration010
|
||||
migration010,
|
||||
migration011
|
||||
];
|
||||
|
||||
// Sort by version number
|
||||
|
||||
58
src/lib/server/db/migrations/011_create_upgrade_configs.ts
Normal file
58
src/lib/server/db/migrations/011_create_upgrade_configs.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { Migration } from '../migrations.ts';
|
||||
|
||||
/**
|
||||
* Migration 011: Create upgrade_configs table
|
||||
*
|
||||
* Creates the table for storing upgrade configuration per arr instance.
|
||||
* Each arr instance can have one upgrade config that controls automatic
|
||||
* quality upgrade searching.
|
||||
*
|
||||
* Fields:
|
||||
* - id: Auto-incrementing primary key
|
||||
* - arr_instance_id: Foreign key to arr_instances (unique - one config per instance)
|
||||
* - enabled: Whether upgrade searching is enabled
|
||||
* - schedule: Interval in minutes between upgrade runs
|
||||
* - filter_mode: How to cycle through filters ('round_robin' | 'random')
|
||||
* - filters: JSON array of FilterConfig objects
|
||||
* - current_filter_index: Tracks position for round-robin mode
|
||||
* - created_at: Timestamp of creation
|
||||
* - updated_at: Timestamp of last update
|
||||
*/
|
||||
|
||||
export const migration: Migration = {
|
||||
version: 11,
|
||||
name: 'Create upgrade_configs table',
|
||||
|
||||
up: `
|
||||
CREATE TABLE upgrade_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- Relationship
|
||||
arr_instance_id INTEGER NOT NULL UNIQUE,
|
||||
|
||||
-- Core settings
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
schedule INTEGER NOT NULL DEFAULT 360,
|
||||
filter_mode TEXT NOT NULL DEFAULT 'round_robin',
|
||||
|
||||
-- Filters (stored as JSON)
|
||||
filters TEXT NOT NULL DEFAULT '[]',
|
||||
|
||||
-- State tracking
|
||||
current_filter_index INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- Metadata
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (arr_instance_id) REFERENCES arr_instances(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_upgrade_configs_arr_instance ON upgrade_configs(arr_instance_id);
|
||||
`,
|
||||
|
||||
down: `
|
||||
DROP INDEX IF EXISTS idx_upgrade_configs_arr_instance;
|
||||
DROP TABLE IF EXISTS upgrade_configs;
|
||||
`
|
||||
};
|
||||
200
src/lib/server/db/queries/upgradeConfigs.ts
Normal file
200
src/lib/server/db/queries/upgradeConfigs.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { db } from '../db.ts';
|
||||
import type { FilterConfig, FilterMode, UpgradeConfig } from '$lib/shared/filters';
|
||||
|
||||
/**
|
||||
* Database row type for upgrade_configs table
|
||||
*/
|
||||
interface UpgradeConfigRow {
|
||||
id: number;
|
||||
arr_instance_id: number;
|
||||
enabled: number;
|
||||
schedule: number;
|
||||
filter_mode: string;
|
||||
filters: string;
|
||||
current_filter_index: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for creating/updating an upgrade config
|
||||
*/
|
||||
export interface UpgradeConfigInput {
|
||||
enabled?: boolean;
|
||||
schedule?: number;
|
||||
filterMode?: FilterMode;
|
||||
filters?: FilterConfig[];
|
||||
currentFilterIndex?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert database row to UpgradeConfig
|
||||
*/
|
||||
function rowToConfig(row: UpgradeConfigRow): UpgradeConfig {
|
||||
return {
|
||||
id: row.id,
|
||||
arrInstanceId: row.arr_instance_id,
|
||||
enabled: row.enabled === 1,
|
||||
schedule: row.schedule,
|
||||
filterMode: row.filter_mode as FilterMode,
|
||||
filters: JSON.parse(row.filters) as FilterConfig[],
|
||||
currentFilterIndex: row.current_filter_index,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* All queries for upgrade_configs table
|
||||
*/
|
||||
export const upgradeConfigsQueries = {
|
||||
/**
|
||||
* Get upgrade config by arr instance ID
|
||||
*/
|
||||
getByArrInstanceId(arrInstanceId: number): UpgradeConfig | undefined {
|
||||
const row = db.queryFirst<UpgradeConfigRow>(
|
||||
'SELECT * FROM upgrade_configs WHERE arr_instance_id = ?',
|
||||
arrInstanceId
|
||||
);
|
||||
return row ? rowToConfig(row) : undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all upgrade configs
|
||||
*/
|
||||
getAll(): UpgradeConfig[] {
|
||||
const rows = db.query<UpgradeConfigRow>('SELECT * FROM upgrade_configs');
|
||||
return rows.map(rowToConfig);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all enabled upgrade configs
|
||||
*/
|
||||
getEnabled(): UpgradeConfig[] {
|
||||
const rows = db.query<UpgradeConfigRow>(
|
||||
'SELECT * FROM upgrade_configs WHERE enabled = 1'
|
||||
);
|
||||
return rows.map(rowToConfig);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create or update an upgrade config for an arr instance
|
||||
* Uses upsert pattern since there's one config per instance
|
||||
*/
|
||||
upsert(arrInstanceId: number, input: UpgradeConfigInput): UpgradeConfig {
|
||||
const existing = this.getByArrInstanceId(arrInstanceId);
|
||||
|
||||
if (existing) {
|
||||
// Update existing
|
||||
this.update(arrInstanceId, input);
|
||||
return this.getByArrInstanceId(arrInstanceId)!;
|
||||
}
|
||||
|
||||
// Create new
|
||||
const enabled = input.enabled !== undefined ? (input.enabled ? 1 : 0) : 0;
|
||||
const schedule = input.schedule ?? 360;
|
||||
const filterMode = input.filterMode ?? 'round_robin';
|
||||
const filters = JSON.stringify(input.filters ?? []);
|
||||
const currentFilterIndex = input.currentFilterIndex ?? 0;
|
||||
|
||||
db.execute(
|
||||
`INSERT INTO upgrade_configs
|
||||
(arr_instance_id, enabled, schedule, filter_mode, filters, current_filter_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
arrInstanceId,
|
||||
enabled,
|
||||
schedule,
|
||||
filterMode,
|
||||
filters,
|
||||
currentFilterIndex
|
||||
);
|
||||
|
||||
return this.getByArrInstanceId(arrInstanceId)!;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an upgrade config
|
||||
*/
|
||||
update(arrInstanceId: number, input: UpgradeConfigInput): boolean {
|
||||
const updates: string[] = [];
|
||||
const params: (string | number)[] = [];
|
||||
|
||||
if (input.enabled !== undefined) {
|
||||
updates.push('enabled = ?');
|
||||
params.push(input.enabled ? 1 : 0);
|
||||
}
|
||||
if (input.schedule !== undefined) {
|
||||
updates.push('schedule = ?');
|
||||
params.push(input.schedule);
|
||||
}
|
||||
if (input.filterMode !== undefined) {
|
||||
updates.push('filter_mode = ?');
|
||||
params.push(input.filterMode);
|
||||
}
|
||||
if (input.filters !== undefined) {
|
||||
updates.push('filters = ?');
|
||||
params.push(JSON.stringify(input.filters));
|
||||
}
|
||||
if (input.currentFilterIndex !== undefined) {
|
||||
updates.push('current_filter_index = ?');
|
||||
params.push(input.currentFilterIndex);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
updates.push('updated_at = CURRENT_TIMESTAMP');
|
||||
params.push(arrInstanceId);
|
||||
|
||||
const affected = db.execute(
|
||||
`UPDATE upgrade_configs SET ${updates.join(', ')} WHERE arr_instance_id = ?`,
|
||||
...params
|
||||
);
|
||||
|
||||
return affected > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete an upgrade config
|
||||
*/
|
||||
delete(arrInstanceId: number): boolean {
|
||||
const affected = db.execute(
|
||||
'DELETE FROM upgrade_configs WHERE arr_instance_id = ?',
|
||||
arrInstanceId
|
||||
);
|
||||
return affected > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Increment the current filter index (for round-robin mode)
|
||||
* Wraps around to 0 when reaching the end
|
||||
*/
|
||||
incrementFilterIndex(arrInstanceId: number): number {
|
||||
const config = this.getByArrInstanceId(arrInstanceId);
|
||||
if (!config) return 0;
|
||||
|
||||
const enabledFilters = config.filters.filter((f) => f.enabled);
|
||||
if (enabledFilters.length === 0) return 0;
|
||||
|
||||
const nextIndex = (config.currentFilterIndex + 1) % enabledFilters.length;
|
||||
|
||||
db.execute(
|
||||
'UPDATE upgrade_configs SET current_filter_index = ?, updated_at = CURRENT_TIMESTAMP WHERE arr_instance_id = ?',
|
||||
nextIndex,
|
||||
arrInstanceId
|
||||
);
|
||||
|
||||
return nextIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the filter index to 0
|
||||
*/
|
||||
resetFilterIndex(arrInstanceId: number): void {
|
||||
db.execute(
|
||||
'UPDATE upgrade_configs SET current_filter_index = 0, updated_at = CURRENT_TIMESTAMP WHERE arr_instance_id = ?',
|
||||
arrInstanceId
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
-- Profilarr Database Schema
|
||||
-- This file documents the current database schema after all migrations
|
||||
-- DO NOT execute this file directly - use migrations instead
|
||||
-- Last updated: 2025-11-04
|
||||
-- Last updated: 2025-12-27
|
||||
|
||||
-- ==============================================================================
|
||||
-- TABLE: migrations
|
||||
@@ -225,6 +225,36 @@ CREATE TABLE database_instances (
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ==============================================================================
|
||||
-- TABLE: upgrade_configs
|
||||
-- Purpose: Store upgrade configuration per arr instance for automated quality upgrades
|
||||
-- Migration: 011_create_upgrade_configs.ts
|
||||
-- ==============================================================================
|
||||
|
||||
CREATE TABLE upgrade_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- Relationship (one config per arr instance)
|
||||
arr_instance_id INTEGER NOT NULL UNIQUE,
|
||||
|
||||
-- Core settings
|
||||
enabled INTEGER NOT NULL DEFAULT 0, -- Master on/off switch
|
||||
schedule INTEGER NOT NULL DEFAULT 360, -- Run interval in minutes (default 6 hours)
|
||||
filter_mode TEXT NOT NULL DEFAULT 'round_robin', -- 'round_robin' or 'random'
|
||||
|
||||
-- Filters (stored as JSON array of FilterConfig objects)
|
||||
filters TEXT NOT NULL DEFAULT '[]',
|
||||
|
||||
-- State tracking
|
||||
current_filter_index INTEGER NOT NULL DEFAULT 0, -- For round-robin mode
|
||||
|
||||
-- Metadata
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
FOREIGN KEY (arr_instance_id) REFERENCES arr_instances(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- ==============================================================================
|
||||
-- INDEXES
|
||||
-- Purpose: Improve query performance
|
||||
@@ -249,3 +279,6 @@ CREATE INDEX idx_notification_history_status ON notification_history(status);
|
||||
|
||||
-- Database instances indexes (Migration: 008_create_database_instances.ts)
|
||||
CREATE INDEX idx_database_instances_uuid ON database_instances(uuid);
|
||||
|
||||
-- Upgrade configs indexes (Migration: 011_create_upgrade_configs.ts)
|
||||
CREATE INDEX idx_upgrade_configs_arr_instance ON upgrade_configs(arr_instance_id);
|
||||
|
||||
@@ -47,6 +47,18 @@ export interface FilterConfig {
|
||||
|
||||
export type FilterMode = 'round_robin' | 'random';
|
||||
|
||||
export interface UpgradeConfig {
|
||||
id?: number;
|
||||
arrInstanceId: number;
|
||||
enabled: boolean;
|
||||
schedule: number; // minutes
|
||||
filterMode: FilterMode;
|
||||
filters: FilterConfig[];
|
||||
currentFilterIndex: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export const filterModes: { id: FilterMode; label: string; description: string }[] = [
|
||||
{
|
||||
id: 'round_robin',
|
||||
@@ -289,6 +301,20 @@ export function createEmptyFilterConfig(name: string = 'New Filter'): FilterConf
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty upgrade config for an arr instance
|
||||
*/
|
||||
export function createEmptyUpgradeConfig(arrInstanceId: number): UpgradeConfig {
|
||||
return {
|
||||
arrInstanceId,
|
||||
enabled: false,
|
||||
schedule: 360, // 6 hours
|
||||
filterMode: 'round_robin',
|
||||
filters: [],
|
||||
currentFilterIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty filter rule with defaults
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { ServerLoad } from '@sveltejs/kit';
|
||||
import { error, fail } from '@sveltejs/kit';
|
||||
import type { Actions, ServerLoad } from '@sveltejs/kit';
|
||||
import { arrInstancesQueries } from '$db/queries/arrInstances.ts';
|
||||
import { upgradeConfigsQueries } from '$db/queries/upgradeConfigs.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
import type { FilterConfig, FilterMode } from '$lib/shared/filters';
|
||||
|
||||
export const load: ServerLoad = ({ params }) => {
|
||||
const id = parseInt(params.id || '', 10);
|
||||
@@ -15,7 +18,147 @@ export const load: ServerLoad = ({ params }) => {
|
||||
error(404, `Instance not found: ${id}`);
|
||||
}
|
||||
|
||||
const config = upgradeConfigsQueries.getByArrInstanceId(id);
|
||||
|
||||
return {
|
||||
instance
|
||||
instance,
|
||||
config: config ?? null
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
save: async ({ params, request }) => {
|
||||
const id = parseInt(params.id || '', 10);
|
||||
|
||||
if (isNaN(id)) {
|
||||
return fail(400, { error: 'Invalid instance ID' });
|
||||
}
|
||||
|
||||
const instance = arrInstancesQueries.getById(id);
|
||||
if (!instance) {
|
||||
return fail(404, { error: 'Instance not found' });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
try {
|
||||
const enabled = formData.get('enabled') === 'true';
|
||||
const schedule = parseInt(formData.get('schedule') as string, 10) || 360;
|
||||
const filterMode = (formData.get('filterMode') as FilterMode) || 'round_robin';
|
||||
const filtersJson = formData.get('filters') as string;
|
||||
const filters: FilterConfig[] = filtersJson ? JSON.parse(filtersJson) : [];
|
||||
|
||||
const configData = {
|
||||
enabled,
|
||||
schedule,
|
||||
filterMode,
|
||||
filters
|
||||
};
|
||||
|
||||
upgradeConfigsQueries.upsert(id, configData);
|
||||
|
||||
await logger.info(`Upgrade config saved for instance "${instance.name}"`, {
|
||||
source: 'upgrades',
|
||||
meta: { instanceId: id, instanceName: instance.name }
|
||||
});
|
||||
|
||||
await logger.debug('Upgrade config details', {
|
||||
source: 'upgrades',
|
||||
meta: {
|
||||
instanceId: id,
|
||||
enabled,
|
||||
schedule,
|
||||
filterMode,
|
||||
filterCount: filters.length,
|
||||
filters: filters.map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
enabled: f.enabled,
|
||||
selector: f.selector,
|
||||
count: f.count,
|
||||
cutoff: f.cutoff,
|
||||
searchCooldown: f.searchCooldown
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
await logger.error('Failed to save upgrade config', {
|
||||
source: 'upgrades',
|
||||
meta: { instanceId: id, error: err }
|
||||
});
|
||||
return fail(500, { error: 'Failed to save configuration' });
|
||||
}
|
||||
},
|
||||
|
||||
update: async ({ params, request }) => {
|
||||
const id = parseInt(params.id || '', 10);
|
||||
|
||||
if (isNaN(id)) {
|
||||
return fail(400, { error: 'Invalid instance ID' });
|
||||
}
|
||||
|
||||
const instance = arrInstancesQueries.getById(id);
|
||||
if (!instance) {
|
||||
return fail(404, { error: 'Instance not found' });
|
||||
}
|
||||
|
||||
const existing = upgradeConfigsQueries.getByArrInstanceId(id);
|
||||
if (!existing) {
|
||||
return fail(404, { error: 'Configuration not found' });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
try {
|
||||
const enabled = formData.get('enabled') === 'true';
|
||||
const schedule = parseInt(formData.get('schedule') as string, 10) || 360;
|
||||
const filterMode = (formData.get('filterMode') as FilterMode) || 'round_robin';
|
||||
const filtersJson = formData.get('filters') as string;
|
||||
const filters: FilterConfig[] = filtersJson ? JSON.parse(filtersJson) : [];
|
||||
|
||||
const configData = {
|
||||
enabled,
|
||||
schedule,
|
||||
filterMode,
|
||||
filters
|
||||
};
|
||||
|
||||
upgradeConfigsQueries.update(id, configData);
|
||||
|
||||
await logger.info(`Upgrade config updated for instance "${instance.name}"`, {
|
||||
source: 'upgrades',
|
||||
meta: { instanceId: id, instanceName: instance.name }
|
||||
});
|
||||
|
||||
await logger.debug('Upgrade config details', {
|
||||
source: 'upgrades',
|
||||
meta: {
|
||||
instanceId: id,
|
||||
enabled,
|
||||
schedule,
|
||||
filterMode,
|
||||
filterCount: filters.length,
|
||||
filters: filters.map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
enabled: f.enabled,
|
||||
selector: f.selector,
|
||||
count: f.count,
|
||||
cutoff: f.cutoff,
|
||||
searchCooldown: f.searchCooldown
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
await logger.error('Failed to update upgrade config', {
|
||||
source: 'upgrades',
|
||||
meta: { instanceId: id, error: err }
|
||||
});
|
||||
return fail(500, { error: 'Failed to update configuration' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,46 +1,100 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import type { PageData, ActionData } from './$types';
|
||||
import type { FilterConfig, FilterMode } from '$lib/shared/filters';
|
||||
import { Info } from 'lucide-svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import { alertStore } from '$lib/client/alerts/store';
|
||||
import { Info, Save, Pencil } from 'lucide-svelte';
|
||||
import CoreSettings from './components/CoreSettings.svelte';
|
||||
import FilterSettings from './components/FilterSettings.svelte';
|
||||
import UpgradesInfoModal from './components/UpgradesInfoModal.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
export let form: ActionData;
|
||||
|
||||
let enabled = true;
|
||||
let schedule = '360'; // 6 hours in minutes
|
||||
let filterMode: FilterMode = 'round_robin';
|
||||
let filters: FilterConfig[] = [];
|
||||
// Initialize from existing config or defaults
|
||||
let enabled = data.config?.enabled ?? false;
|
||||
let schedule = String(data.config?.schedule ?? 360);
|
||||
let filterMode: FilterMode = data.config?.filterMode ?? 'round_robin';
|
||||
let filters: FilterConfig[] = data.config?.filters ?? [];
|
||||
|
||||
// Track if config exists (determines save vs edit)
|
||||
$: isNewConfig = !data.config;
|
||||
|
||||
let showInfoModal = false;
|
||||
let saving = false;
|
||||
|
||||
// Handle form response
|
||||
$: if (form?.success) {
|
||||
alertStore.add('success', `Configuration ${isNewConfig ? 'saved' : 'updated'} successfully`);
|
||||
}
|
||||
$: if (form?.error) {
|
||||
alertStore.add('error', form.error);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.instance.name} - Upgrades - Profilarr</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="mt-6 space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-50">Upgrade Configuration</h1>
|
||||
<p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Automatically search for better quality releases for your library items.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showInfoModal = true)}
|
||||
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 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
|
||||
>
|
||||
<Info size={14} />
|
||||
How it works
|
||||
</button>
|
||||
</div>
|
||||
<form
|
||||
method="POST"
|
||||
action={isNewConfig ? '?/save' : '?/update'}
|
||||
use:enhance={() => {
|
||||
saving = true;
|
||||
return async ({ update }) => {
|
||||
await update({ reset: false });
|
||||
saving = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="enabled" value={enabled} />
|
||||
<input type="hidden" name="schedule" value={schedule} />
|
||||
<input type="hidden" name="filterMode" value={filterMode} />
|
||||
<input type="hidden" name="filters" value={JSON.stringify(filters)} />
|
||||
|
||||
<CoreSettings bind:enabled bind:schedule bind:filterMode />
|
||||
<FilterSettings bind:filters />
|
||||
</div>
|
||||
<div class="mt-6 space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-50">
|
||||
Upgrade Configuration
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Automatically search for better quality releases for your library items.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showInfoModal = true)}
|
||||
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 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
|
||||
>
|
||||
<Info size={14} />
|
||||
How it works
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<CoreSettings bind:enabled bind:schedule bind:filterMode />
|
||||
<FilterSettings bind:filters />
|
||||
|
||||
<!-- Save/Update Button -->
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saving}
|
||||
class="flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50 {isNewConfig
|
||||
? 'bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600'
|
||||
: 'bg-green-600 text-white hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600'}"
|
||||
>
|
||||
{#if isNewConfig}
|
||||
<Save size={16} />
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
{:else}
|
||||
<Pencil size={16} />
|
||||
{saving ? 'Updating...' : 'Update'}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<UpgradesInfoModal bind:open={showInfoModal} />
|
||||
|
||||
Reference in New Issue
Block a user