mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-26 12:52:00 +01:00
refactor: move database settings page into tabbed layout, make style more consistent with arrs
This commit is contained in:
16
src/lib/client/assets/Radarr.svg
Normal file
16
src/lib/client/assets/Radarr.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<rect width="1024" height="1024" id="artboard_1" />
|
||||
<clipPath id="clip_1">
|
||||
<use xlink:href="#artboard_1" clip-rule="evenodd" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="Simple-Logo-Light" clip-path="url(#clip_1)">
|
||||
<use xlink:href="#artboard_1" stroke="none" fill="#FFFFFF" fill-opacity="0" />
|
||||
<g id="Group-Copy" transform="translate(70 21.00012)">
|
||||
<path d="M105.302 154.943L112.824 869.492C52.651 877.014 7.52158 846.927 7.52158 786.755L0 192.55C0 4.51106 172.996 -40.6184 278.298 34.5974L812.33 342.982C887.546 395.633 902.589 493.413 864.981 561.107C857.46 508.456 834.895 478.37 789.765 448.284L188.039 109.813C142.91 79.7268 105.302 87.2484 105.302 154.943Z" id="Shape" fill="#24292E" stroke="none" />
|
||||
<path d="M0 376.079C45.1295 391.122 90.259 383.6 127.867 361.036L744.636 0C782.244 52.651 774.723 105.302 729.593 135.388L210.604 436.251C135.388 473.859 37.6079 436.251 0 376.079Z" transform="translate(60.17249 531.0214)" id="Shape" fill="#24292E" stroke="none" />
|
||||
<path d="M0 413.687L368.557 203.083L7.52157 0L0 413.687Z" transform="translate(240.6902 282.8092)" id="Shape" fill="#FFC230" stroke="none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
9
src/lib/client/assets/Sonarr.svg
Normal file
9
src/lib/client/assets/Sonarr.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg height="216.9" viewBox="0 0 216.7 216.9" width="216.7" xmlns="http://www.w3.org/2000/svg">
|
||||
<path clip-rule="evenodd" d="M216.7 108.45c0 29.833-10.533 55.4-31.6 76.7-.7.833-1.483 1.6-2.35 2.3-3.466 3.4-7.133 6.484-11 9.25-18.267 13.467-39.367 20.2-63.3 20.2-23.967 0-45.033-6.733-63.2-20.2-4.8-3.4-9.3-7.25-13.5-11.55-16.367-16.266-26.417-35.167-30.15-56.7-.733-4.2-1.217-8.467-1.45-12.8-.1-2.4-.15-4.8-.15-7.2 0-2.533.05-4.95.15-7.25 0-.233.066-.467.2-.7 1.567-26.6 12.033-49.583 31.4-68.95C53.05 10.517 78.617 0 108.45 0c29.933 0 55.484 10.517 76.65 31.55 21.067 21.433 31.6 47.067 31.6 76.9z" fill="#EEE" fill-rule="evenodd"/>
|
||||
<path clip-rule="evenodd" d="M194.65 42.5l-22.4 22.4C159.152 77.998 158 89.4 158 109.5c0 17.934 2.852 34.352 16.2 47.7 9.746 9.746 19 18.95 19 18.95-2.5 3.067-5.2 6.067-8.1 9-.7.833-1.483 1.6-2.35 2.3-2.533 2.5-5.167 4.817-7.9 6.95l-17.55-17.55c-15.598-15.6-27.996-17.1-48.6-17.1-19.77 0-33.223 1.822-47.7 16.3-8.647 8.647-18.55 18.6-18.55 18.6-3.767-2.867-7.333-6.034-10.7-9.5-2.8-2.8-5.417-5.667-7.85-8.6 0 0 9.798-9.848 19.15-19.2 13.852-13.853 16.1-29.916 16.1-47.85 0-17.5-2.874-33.823-15.6-46.55-8.835-8.836-21.05-21-21.05-21 2.833-3.6 5.917-7.067 9.25-10.4 2.934-2.867 5.934-5.55 9-8.05L61.1 43.85C74.102 56.852 90.767 60.2 108.7 60.2c18.467 0 35.077-3.577 48.6-17.1 8.32-8.32 19.3-19.25 19.3-19.25 2.9 2.367 5.733 4.933 8.5 7.7 3.467 3.533 6.65 7.183 9.55 10.95z" fill="#3A3F51" fill-rule="evenodd"/>
|
||||
<g clip-rule="evenodd">
|
||||
<path d="M78.7 114c-.2-1.167-.332-2.35-.4-3.55-.032-.667-.05-1.333-.05-2 0-.7.018-1.367.05-2 0-.067.018-.133.05-.2.435-7.367 3.334-13.733 8.7-19.1 5.9-5.833 12.984-8.75 21.25-8.75 8.3 0 15.384 2.917 21.25 8.75 5.834 5.934 8.75 13.033 8.75 21.3 0 8.267-2.916 15.35-8.75 21.25-.2.233-.416.45-.65.65-.966.933-1.982 1.783-3.05 2.55-5.065 3.733-10.916 5.6-17.55 5.6s-12.466-1.866-17.5-5.6c-1.332-.934-2.582-2-3.75-3.2-4.532-4.5-7.316-9.734-8.35-15.7z" fill="#0CF" fill-rule="evenodd"/>
|
||||
<path d="M157.8 59.75l-15 14.65M30.785 32.526L71.65 73.25m84.6 84.25l27.808 28.78m1.855-153.894L157.8 59.75m-125.45 126l27.35-27.4" fill="none" stroke="#0CF" stroke-miterlimit="1" stroke-width="2"/>
|
||||
<path d="M157.8 59.75l-16.95 17.2M58.97 60.604l17.2 17.15M59.623 158.43l16.75-17.4m61.928-1.396l18.028 17.945" fill="none" stroke="#0CF" stroke-miterlimit="1" stroke-width="7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Server, Plus, Trash2, Pencil, Check, X, Info, ExternalLink } from 'lucide-svelte';
|
||||
import { Server, Plus, Trash2, Info, ExternalLink } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { enhance } from '$app/forms';
|
||||
import EmptyState from '$ui/state/EmptyState.svelte';
|
||||
@@ -16,6 +16,14 @@
|
||||
import type { PageData } from './$types';
|
||||
import type { Column } from '$ui/table/types';
|
||||
import type { ArrInstance } from '$db/queries/arrInstances.ts';
|
||||
import radarrLogo from '$lib/client/assets/Radarr.svg';
|
||||
import sonarrLogo from '$lib/client/assets/Sonarr.svg';
|
||||
|
||||
// Logo lookup by type
|
||||
const logos: Record<string, string> = {
|
||||
radarr: radarrLogo,
|
||||
sonarr: sonarrLogo
|
||||
};
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -39,18 +47,24 @@
|
||||
let selectedInstance: ArrInstance | null = null;
|
||||
let deleteFormElement: HTMLFormElement;
|
||||
|
||||
// Track loaded images
|
||||
let loadedImages: Set<number> = new Set();
|
||||
|
||||
// Get logo path based on arr type
|
||||
function getLogoPath(type: string): string {
|
||||
return logos[type] || '';
|
||||
}
|
||||
|
||||
function handleImageLoad(id: number) {
|
||||
loadedImages.add(id);
|
||||
loadedImages = loadedImages;
|
||||
}
|
||||
|
||||
// Format type for display with proper casing
|
||||
function formatType(type: string): string {
|
||||
return type.charAt(0).toUpperCase() + type.slice(1);
|
||||
}
|
||||
|
||||
// Get badge variant for arr type
|
||||
function getTypeVariant(type: string): 'warning' | 'accent' | 'neutral' {
|
||||
if (type === 'radarr') return 'warning';
|
||||
if (type === 'sonarr') return 'accent';
|
||||
return 'neutral';
|
||||
}
|
||||
|
||||
// Handle row click
|
||||
function handleRowClick(instance: ArrInstance) {
|
||||
goto(`/arr/${instance.id}`);
|
||||
@@ -66,7 +80,6 @@
|
||||
// Define table columns
|
||||
const columns: Column<ArrInstance>[] = [
|
||||
{ key: 'name', header: 'Name', align: 'left' },
|
||||
{ key: 'type', header: 'Type', align: 'left', width: 'w-32' },
|
||||
{ key: 'url', header: 'URL', align: 'left' },
|
||||
{ key: 'enabled', header: 'Enabled', align: 'center', width: 'w-24' }
|
||||
];
|
||||
@@ -98,27 +111,34 @@
|
||||
<Table {columns} data={filteredInstances} hoverable={true} onRowClick={handleRowClick}>
|
||||
<svelte:fragment slot="cell" let:row let:column>
|
||||
{#if column.key === 'name'}
|
||||
<div class="font-medium text-neutral-900 dark:text-neutral-50">
|
||||
{row.name}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative h-8 w-8">
|
||||
{#if !loadedImages.has(row.id)}
|
||||
<div
|
||||
class="absolute inset-0 animate-pulse rounded-lg bg-neutral-200 dark:bg-neutral-700"
|
||||
></div>
|
||||
{/if}
|
||||
<img
|
||||
src={getLogoPath(row.type)}
|
||||
alt="{formatType(row.type)} logo"
|
||||
class="h-8 w-8 rounded-lg {loadedImages.has(row.id) ? 'opacity-100' : 'opacity-0'}"
|
||||
on:load={() => handleImageLoad(row.id)}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="font-medium text-neutral-900 dark:text-neutral-50">
|
||||
{row.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if column.key === 'type'}
|
||||
<Badge variant={getTypeVariant(row.type)} mono>{formatType(row.type)}</Badge>
|
||||
{:else if column.key === 'url'}
|
||||
<Badge variant="neutral" mono>{row.url}</Badge>
|
||||
{:else if column.key === 'enabled'}
|
||||
<div class="flex justify-center">
|
||||
{#if row.enabled}
|
||||
<span
|
||||
class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400"
|
||||
>
|
||||
<Check size={14} />
|
||||
</span>
|
||||
<Badge variant="success">Enabled</Badge>
|
||||
{:else}
|
||||
<span
|
||||
class="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-100 text-neutral-400 dark:bg-neutral-800 dark:text-neutral-500"
|
||||
>
|
||||
<X size={14} />
|
||||
</span>
|
||||
<Badge variant="neutral">Disabled</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -126,9 +146,6 @@
|
||||
|
||||
<svelte:fragment slot="actions" let:row>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<a href="/arr/{row.id}/settings" on:click={(e) => e.stopPropagation()}>
|
||||
<TableActionButton icon={Pencil} title="Edit instance" />
|
||||
</a>
|
||||
<a
|
||||
href={row.url}
|
||||
target="_blank"
|
||||
|
||||
@@ -10,18 +10,6 @@
|
||||
$: currentPath = $page.url.pathname;
|
||||
|
||||
$: tabs = [
|
||||
{
|
||||
label: 'Settings',
|
||||
href: `/arr/${instanceId}/settings`,
|
||||
active: currentPath.includes('/settings'),
|
||||
icon: Settings
|
||||
},
|
||||
{
|
||||
label: 'Library',
|
||||
href: `/arr/${instanceId}/library`,
|
||||
active: currentPath.includes('/library'),
|
||||
icon: Library
|
||||
},
|
||||
{
|
||||
label: 'Sync',
|
||||
href: `/arr/${instanceId}/sync`,
|
||||
@@ -40,11 +28,23 @@
|
||||
active: currentPath.includes('/rename'),
|
||||
icon: FileEdit
|
||||
},
|
||||
{
|
||||
label: 'Library',
|
||||
href: `/arr/${instanceId}/library`,
|
||||
active: currentPath.includes('/library'),
|
||||
icon: Library
|
||||
},
|
||||
{
|
||||
label: 'Logs',
|
||||
href: `/arr/${instanceId}/logs`,
|
||||
active: currentPath.includes('/logs'),
|
||||
icon: ScrollText
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
href: `/arr/${instanceId}/settings`,
|
||||
active: currentPath.includes('/settings'),
|
||||
icon: Settings
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ import { redirect } from '@sveltejs/kit';
|
||||
import type { ServerLoad } from '@sveltejs/kit';
|
||||
|
||||
export const load: ServerLoad = ({ params }) => {
|
||||
// Redirect to the settings tab by default
|
||||
redirect(302, `/arr/${params.id}/settings`);
|
||||
// Redirect to the sync tab by default
|
||||
redirect(302, `/arr/${params.id}/sync`);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
Lock,
|
||||
Code,
|
||||
Trash2,
|
||||
Pencil,
|
||||
ExternalLink,
|
||||
ChevronRight,
|
||||
Info
|
||||
@@ -180,9 +179,6 @@
|
||||
|
||||
<svelte:fragment slot="actions" let:row>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<a href="/databases/{row.id}/edit" on:click={(e) => e.stopPropagation()}>
|
||||
<TableActionButton icon={Pencil} title="Edit database" />
|
||||
</a>
|
||||
<a
|
||||
href={row.repository_url}
|
||||
target="_blank"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Tabs from '$ui/navigation/tabs/Tabs.svelte';
|
||||
import { GitBranch, History, Wrench, Settings } from 'lucide-svelte';
|
||||
import { GitBranch, History, Wrench, Settings, FileCog } from 'lucide-svelte';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
$: database = $page.data.database;
|
||||
@@ -31,21 +31,30 @@
|
||||
{
|
||||
label: 'Config',
|
||||
href: `/databases/${database.id}/config`,
|
||||
icon: Settings,
|
||||
icon: FileCog,
|
||||
active: currentPath.includes('/config')
|
||||
}
|
||||
]
|
||||
: [])
|
||||
: []),
|
||||
{
|
||||
label: 'Settings',
|
||||
href: `/databases/${database.id}/settings`,
|
||||
icon: Settings,
|
||||
active: currentPath.includes('/settings')
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
$: backButton = {
|
||||
label: 'Back',
|
||||
href: '/databases'
|
||||
$: breadcrumb = {
|
||||
parent: {
|
||||
label: 'Databases',
|
||||
href: '/databases'
|
||||
},
|
||||
current: database?.name ?? ''
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="p-8">
|
||||
<Tabs {tabs} {backButton} />
|
||||
<Tabs {tabs} {breadcrumb} />
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<script lang="ts">
|
||||
// Reset to root layout - edit page doesn't need the tabs
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@@ -1,29 +1,9 @@
|
||||
import { error, redirect, fail } from '@sveltejs/kit';
|
||||
import type { ServerLoad, Actions } from '@sveltejs/kit';
|
||||
import { redirect, fail } from '@sveltejs/kit';
|
||||
import type { Actions } from '@sveltejs/kit';
|
||||
import { databaseInstancesQueries } from '$db/queries/databaseInstances.ts';
|
||||
import { pcdManager } from '$pcd/pcd.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
|
||||
export const load: ServerLoad = ({ params }) => {
|
||||
const id = parseInt(params.id || '', 10);
|
||||
|
||||
// Validate ID
|
||||
if (isNaN(id)) {
|
||||
error(404, `Invalid database ID: ${params.id}`);
|
||||
}
|
||||
|
||||
// Fetch the specific instance
|
||||
const instance = databaseInstancesQueries.getById(id);
|
||||
|
||||
if (!instance) {
|
||||
error(404, `Database not found: ${id}`);
|
||||
}
|
||||
|
||||
return {
|
||||
instance
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
update: async ({ params, request }) => {
|
||||
const id = parseInt(params.id || '', 10);
|
||||
@@ -31,7 +11,7 @@ export const actions: Actions = {
|
||||
// Validate ID
|
||||
if (isNaN(id)) {
|
||||
await logger.warn('Update failed: Invalid database ID', {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id: params.id }
|
||||
});
|
||||
return fail(400, { error: 'Invalid database ID' });
|
||||
@@ -42,7 +22,7 @@ export const actions: Actions = {
|
||||
|
||||
if (!instance) {
|
||||
await logger.warn('Update failed: Database not found', {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id }
|
||||
});
|
||||
return fail(404, { error: 'Database not found' });
|
||||
@@ -59,7 +39,7 @@ export const actions: Actions = {
|
||||
// Validation
|
||||
if (!name) {
|
||||
await logger.warn('Attempted to update database with missing required fields', {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id, name }
|
||||
});
|
||||
|
||||
@@ -70,13 +50,9 @@ export const actions: Actions = {
|
||||
}
|
||||
|
||||
// Check if name already exists (excluding current instance)
|
||||
const existingWithName = databaseInstancesQueries
|
||||
.getAll()
|
||||
.find((db) => db.name === name && db.id !== id);
|
||||
|
||||
if (existingWithName) {
|
||||
if (databaseInstancesQueries.nameExists(name, id)) {
|
||||
await logger.warn('Attempted to update database with duplicate name', {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id, name }
|
||||
});
|
||||
|
||||
@@ -100,21 +76,15 @@ export const actions: Actions = {
|
||||
}
|
||||
|
||||
await logger.info(`Updated database: ${name}`, {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id, name }
|
||||
});
|
||||
|
||||
// Redirect to database detail page
|
||||
redirect(303, `/databases/${id}`);
|
||||
} catch (error) {
|
||||
// Re-throw redirect errors (they're not actual errors)
|
||||
if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
await logger.error('Failed to update database', {
|
||||
source: 'databases/[id]/edit',
|
||||
meta: { error: error instanceof Error ? error.message : String(error) }
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { error: err instanceof Error ? err.message : String(err) }
|
||||
});
|
||||
|
||||
return fail(500, {
|
||||
@@ -130,7 +100,7 @@ export const actions: Actions = {
|
||||
// Validate ID
|
||||
if (isNaN(id)) {
|
||||
await logger.warn('Delete failed: Invalid database ID', {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id: params.id }
|
||||
});
|
||||
return fail(400, { error: 'Invalid database ID' });
|
||||
@@ -141,7 +111,7 @@ export const actions: Actions = {
|
||||
|
||||
if (!instance) {
|
||||
await logger.warn('Delete failed: Database not found', {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id }
|
||||
});
|
||||
return fail(404, { error: 'Database not found' });
|
||||
@@ -152,21 +122,21 @@ export const actions: Actions = {
|
||||
await pcdManager.unlink(id);
|
||||
|
||||
await logger.info(`Unlinked database: ${instance.name}`, {
|
||||
source: 'databases/[id]/edit',
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { id, name: instance.name, repositoryUrl: instance.repository_url }
|
||||
});
|
||||
|
||||
// Redirect to databases list
|
||||
redirect(303, '/databases');
|
||||
} catch (error) {
|
||||
} catch (err) {
|
||||
// Re-throw redirect errors (they're not actual errors)
|
||||
if (error && typeof error === 'object' && 'status' in error && 'location' in error) {
|
||||
throw error;
|
||||
if (err && typeof err === 'object' && 'status' in err && 'location' in err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
await logger.error('Failed to unlink database', {
|
||||
source: 'databases/[id]/edit',
|
||||
meta: { error: error instanceof Error ? error.message : String(error) }
|
||||
source: 'databases/[id]/settings',
|
||||
meta: { error: err instanceof Error ? err.message : String(err) }
|
||||
});
|
||||
|
||||
return fail(500, { error: 'Failed to unlink database' });
|
||||
@@ -6,4 +6,8 @@
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<InstanceForm mode="edit" {form} instance={data.instance} />
|
||||
<svelte:head>
|
||||
<title>{data.database.name} - Settings - Profilarr</title>
|
||||
</svelte:head>
|
||||
|
||||
<InstanceForm mode="edit" {form} instance={data.database} />
|
||||
@@ -1,9 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { Save, Trash2, Loader2 } from 'lucide-svelte';
|
||||
import Modal from '$ui/modal/Modal.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import { Save, Trash2 } from 'lucide-svelte';
|
||||
import { alertStore } from '$alerts/store';
|
||||
import { isDirty, initEdit, initCreate, update, current, clear } from '$lib/client/stores/dirty';
|
||||
import type { DatabaseInstance } from '$db/queries/databaseInstances.ts';
|
||||
import FormInput from '$ui/form/FormInput.svelte';
|
||||
import DropdownSelect from '$ui/dropdown/DropdownSelect.svelte';
|
||||
import Modal from '$ui/modal/Modal.svelte';
|
||||
import DirtyModal from '$ui/modal/DirtyModal.svelte';
|
||||
import Button from '$ui/button/Button.svelte';
|
||||
|
||||
// Props
|
||||
export let mode: 'create' | 'edit';
|
||||
@@ -13,329 +19,280 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let data: any = undefined;
|
||||
|
||||
// Loading state
|
||||
let isLoading = false;
|
||||
// Initialize dirty tracking on mount
|
||||
onMount(() => {
|
||||
if (mode === 'edit' && instance) {
|
||||
initEdit({
|
||||
name: instance.name,
|
||||
repositoryUrl: instance.repository_url,
|
||||
personalAccessToken: '', // Never pre-populate for security
|
||||
syncStrategy: String(instance.sync_strategy),
|
||||
autoPull: instance.auto_pull ? 'true' : 'false'
|
||||
});
|
||||
} else {
|
||||
initCreate({
|
||||
name: data?.formData?.name ?? '',
|
||||
repositoryUrl: '',
|
||||
branch: data?.formData?.branch ?? '',
|
||||
personalAccessToken: data?.formData?.personalAccessToken ?? '',
|
||||
syncStrategy: data?.formData?.syncStrategy ? String(data.formData.syncStrategy) : '60',
|
||||
autoPull: data?.formData?.autoPull === '0' ? 'false' : 'true'
|
||||
});
|
||||
}
|
||||
return () => clear();
|
||||
});
|
||||
|
||||
// Form values
|
||||
|
||||
let name =
|
||||
(form as any)?.values?.name ?? (mode === 'edit' ? instance?.name : data?.formData?.name) ?? '';
|
||||
|
||||
let repositoryUrl =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(form as any)?.values?.repository_url ??
|
||||
(mode === 'edit' ? instance?.repository_url : '') ??
|
||||
'';
|
||||
let branch =
|
||||
(form as any)?.values?.branch ?? (mode === 'create' ? data?.formData?.branch : '') ?? '';
|
||||
let personalAccessToken =
|
||||
(form as any)?.values?.personal_access_token ??
|
||||
(mode === 'edit' ? instance?.personal_access_token : data?.formData?.personalAccessToken) ??
|
||||
'';
|
||||
|
||||
let syncStrategy =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(form as any)?.values?.sync_strategy ??
|
||||
(mode === 'edit'
|
||||
? instance?.sync_strategy
|
||||
: data?.formData?.syncStrategy
|
||||
? parseInt(data.formData.syncStrategy)
|
||||
: 60) ??
|
||||
60;
|
||||
|
||||
let autoPull =
|
||||
(form as any)?.values?.auto_pull ??
|
||||
(mode === 'edit'
|
||||
? instance?.auto_pull
|
||||
: data?.formData?.autoPull === '1'
|
||||
? 1
|
||||
: data?.formData?.autoPull === '0'
|
||||
? 0
|
||||
: 1) ??
|
||||
1;
|
||||
// Read current values from dirty store
|
||||
$: name = ($current.name ?? '') as string;
|
||||
$: repositoryUrl = ($current.repositoryUrl ?? '') as string;
|
||||
$: branch = ($current.branch ?? '') as string;
|
||||
$: personalAccessToken = ($current.personalAccessToken ?? '') as string;
|
||||
$: syncStrategy = ($current.syncStrategy ?? '60') as string;
|
||||
$: autoPull = ($current.autoPull ?? 'true') as string;
|
||||
|
||||
// Delete modal state
|
||||
// UI state
|
||||
let saving = false;
|
||||
let deleting = false;
|
||||
let showDeleteModal = false;
|
||||
let deleteFormElement: HTMLFormElement;
|
||||
|
||||
// Options for dropdowns
|
||||
const syncStrategyOptions = [
|
||||
{ value: '0', label: 'Manual (no auto-sync)' },
|
||||
{ value: '5', label: 'Every 5 minutes' },
|
||||
{ value: '15', label: 'Every 15 minutes' },
|
||||
{ value: '30', label: 'Every 30 minutes' },
|
||||
{ value: '60', label: 'Every hour' },
|
||||
{ value: '360', label: 'Every 6 hours' },
|
||||
{ value: '720', label: 'Every 12 hours' },
|
||||
{ value: '1440', label: 'Every 24 hours' }
|
||||
];
|
||||
|
||||
const autoPullOptions = [
|
||||
{ value: 'true', label: 'Enabled' },
|
||||
{ value: 'false', label: 'Disabled' }
|
||||
];
|
||||
|
||||
// Submit handler
|
||||
function handleSave() {
|
||||
if (!name) {
|
||||
alertStore.add('error', 'Name is required');
|
||||
return;
|
||||
}
|
||||
if (mode === 'create' && !repositoryUrl) {
|
||||
alertStore.add('error', 'Repository URL is required');
|
||||
return;
|
||||
}
|
||||
|
||||
saving = true;
|
||||
const saveForm = document.getElementById('save-form');
|
||||
if (saveForm instanceof HTMLFormElement) {
|
||||
saveForm.requestSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
$: canSubmit = $isDirty && !!name && (mode === 'edit' || !!repositoryUrl);
|
||||
|
||||
// Handle form response
|
||||
let lastFormId: unknown = null;
|
||||
$: if (form && form !== lastFormId) {
|
||||
lastFormId = form;
|
||||
if (form.success) {
|
||||
alertStore.add('success', 'Settings saved successfully');
|
||||
// Reset dirty state with new values (keep personalAccessToken empty)
|
||||
initEdit({
|
||||
name,
|
||||
repositoryUrl,
|
||||
personalAccessToken: '',
|
||||
syncStrategy,
|
||||
autoPull
|
||||
});
|
||||
}
|
||||
if (form.error) {
|
||||
alertStore.add('error', form.error);
|
||||
}
|
||||
}
|
||||
|
||||
// Display text based on mode
|
||||
$: title = mode === 'create' ? 'Link Database' : 'Edit Database';
|
||||
$: title = mode === 'create' ? 'Link Database' : 'Settings';
|
||||
$: description =
|
||||
mode === 'create'
|
||||
? 'Link a Profilarr Compliant Database from a Git repository'
|
||||
: `Update the configuration for ${instance?.name || 'this database'}`;
|
||||
$: submitButtonText = mode === 'create' ? 'Link Database' : 'Save Changes';
|
||||
$: successMessage =
|
||||
mode === 'create' ? 'Database linked successfully!' : 'Database updated successfully!';
|
||||
$: errorMessage = mode === 'create' ? 'Failed to link database' : 'Failed to update database';
|
||||
? 'Link a Profilarr Compliant Database from a Git repository.'
|
||||
: `Configure settings for ${instance?.name || 'this database'}.`;
|
||||
</script>
|
||||
|
||||
<div class="space-y-8 p-8">
|
||||
<div class="space-y-3">
|
||||
<h1 class="text-3xl font-bold text-neutral-900 dark:text-neutral-50">{title}</h1>
|
||||
<p class="text-lg text-neutral-600 dark:text-neutral-400">
|
||||
{description}
|
||||
</p>
|
||||
<div class="space-y-6" class:mt-6={mode === 'edit'}>
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-50">{title}</h1>
|
||||
<p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">{description}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if mode === 'edit'}
|
||||
<Button
|
||||
text="Unlink"
|
||||
icon={Trash2}
|
||||
iconColor="text-red-600 dark:text-red-400"
|
||||
disabled={saving || deleting}
|
||||
on:click={() => (showDeleteModal = true)}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
text={saving ? 'Saving...' : 'Save'}
|
||||
icon={Save}
|
||||
iconColor="text-blue-600 dark:text-blue-400"
|
||||
disabled={saving || !canSubmit}
|
||||
on:click={handleSave}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-4 rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-neutral-900"
|
||||
>
|
||||
<!-- Name Row -->
|
||||
<FormInput
|
||||
label="Name"
|
||||
name="name"
|
||||
value={name}
|
||||
placeholder="e.g., Main Database, 4K Profiles"
|
||||
description="A friendly name to identify this database"
|
||||
required
|
||||
on:input={(e) => update('name', e.detail)}
|
||||
/>
|
||||
|
||||
<!-- Repository URL Row -->
|
||||
<FormInput
|
||||
label="Repository URL"
|
||||
name="repository_url"
|
||||
type="url"
|
||||
value={repositoryUrl}
|
||||
placeholder="https://github.com/username/database"
|
||||
description={mode === 'edit'
|
||||
? 'Repository URL cannot be changed after linking'
|
||||
: 'Git repository URL containing the PCD manifest'}
|
||||
required
|
||||
readonly={mode === 'edit'}
|
||||
on:input={(e) => update('repositoryUrl', e.detail)}
|
||||
/>
|
||||
|
||||
<!-- Branch Row (create mode only) -->
|
||||
{#if mode === 'create'}
|
||||
<FormInput
|
||||
label="Branch"
|
||||
name="branch"
|
||||
value={branch}
|
||||
placeholder="main"
|
||||
description="Branch to checkout on link. Leave empty for the default branch."
|
||||
on:input={(e) => update('branch', e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Personal Access Token Row -->
|
||||
<FormInput
|
||||
label="Personal Access Token"
|
||||
name="personal_access_token"
|
||||
value={personalAccessToken}
|
||||
placeholder="ghp_..."
|
||||
description={mode === 'edit'
|
||||
? 'Re-enter to update. Required for private repos and to push changes.'
|
||||
: 'Required for private repositories and to push changes back to GitHub.'}
|
||||
private_
|
||||
on:input={(e) => update('personalAccessToken', e.detail)}
|
||||
/>
|
||||
|
||||
<!-- Sync Strategy Row -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||
Sync Strategy
|
||||
</label>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
How often to check for updates from the remote repository
|
||||
</p>
|
||||
<DropdownSelect
|
||||
value={syncStrategy}
|
||||
options={syncStrategyOptions}
|
||||
fullWidth
|
||||
on:change={(e) => update('syncStrategy', e.detail)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Auto Pull Row -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-neutral-900 dark:text-neutral-100">
|
||||
Auto Pull
|
||||
</label>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Automatically pull updates when available, or just receive notifications
|
||||
</p>
|
||||
<DropdownSelect
|
||||
value={autoPull}
|
||||
options={autoPullOptions}
|
||||
on:change={(e) => update('autoPull', e.detail)}
|
||||
/>
|
||||
</div>
|
||||
{#if autoPull === 'false'}
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400">
|
||||
You will receive notifications when updates are available but they won't be applied
|
||||
automatically
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden save form -->
|
||||
<form
|
||||
id="save-form"
|
||||
method="POST"
|
||||
action={mode === 'edit' ? '?/update' : undefined}
|
||||
class="hidden"
|
||||
use:enhance={() => {
|
||||
saving = true;
|
||||
return async ({ result, update: formUpdate }) => {
|
||||
if (result.type === 'redirect') {
|
||||
// For create mode, clear dirty state before redirect
|
||||
clear();
|
||||
alertStore.add('success', 'Database linked successfully');
|
||||
}
|
||||
await formUpdate({ reset: false });
|
||||
saving = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="name" value={name} />
|
||||
<input type="hidden" name="repository_url" value={repositoryUrl} />
|
||||
{#if mode === 'create'}
|
||||
<input type="hidden" name="branch" value={branch} />
|
||||
{/if}
|
||||
<input type="hidden" name="personal_access_token" value={personalAccessToken} />
|
||||
<input type="hidden" name="sync_strategy" value={syncStrategy} />
|
||||
<input type="hidden" name="auto_pull" value={autoPull === 'true' ? '1' : '0'} />
|
||||
</form>
|
||||
|
||||
<!-- Hidden delete form (edit mode only) -->
|
||||
{#if mode === 'edit'}
|
||||
<form
|
||||
id="delete-form"
|
||||
method="POST"
|
||||
action={mode === 'edit' ? '?/update' : undefined}
|
||||
class="space-y-6"
|
||||
action="?/delete"
|
||||
class="hidden"
|
||||
use:enhance={() => {
|
||||
isLoading = true;
|
||||
deleting = true;
|
||||
return async ({ result, update }) => {
|
||||
if (result.type === 'failure' && result.data) {
|
||||
alertStore.add('error', (result.data as { error?: string }).error || errorMessage);
|
||||
alertStore.add(
|
||||
'error',
|
||||
(result.data as { error?: string }).error || 'Failed to unlink database'
|
||||
);
|
||||
} else if (result.type === 'redirect') {
|
||||
// Don't show success message if redirecting to bruh page
|
||||
if (result.location && !result.location.includes('/databases/bruh')) {
|
||||
alertStore.add('success', successMessage);
|
||||
}
|
||||
alertStore.add('success', 'Database unlinked successfully');
|
||||
}
|
||||
await update();
|
||||
isLoading = false;
|
||||
deleting = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<!-- Database Details -->
|
||||
<div
|
||||
class="rounded-lg border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900"
|
||||
>
|
||||
<h2 class="mb-4 text-lg font-semibold text-neutral-900 dark:text-neutral-50">
|
||||
Database Details
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
Name <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder="e.g., Main Database, 4K Profiles"
|
||||
class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
A friendly name to identify this database
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Repository URL -->
|
||||
<div>
|
||||
<label
|
||||
for="repository_url"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
Repository URL <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="repository_url"
|
||||
name="repository_url"
|
||||
bind:value={repositoryUrl}
|
||||
required
|
||||
disabled={mode === 'edit'}
|
||||
placeholder="https://github.com/username/database"
|
||||
class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 focus:border-neutral-400 focus:outline-none disabled:cursor-not-allowed disabled:bg-neutral-100 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500 dark:disabled:bg-neutral-900"
|
||||
/>
|
||||
{#if mode === 'edit'}
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Repository URL cannot be changed after linking
|
||||
</p>
|
||||
{:else}
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Git repository URL containing the PCD manifest
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Branch -->
|
||||
{#if mode === 'create'}
|
||||
<div>
|
||||
<label
|
||||
for="branch"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
Branch
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="branch"
|
||||
name="branch"
|
||||
bind:value={branch}
|
||||
placeholder="main"
|
||||
class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Branch to checkout on link. Leave empty to use the default branch. You can change this
|
||||
later.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Personal Access Token -->
|
||||
<div>
|
||||
<label
|
||||
for="personal_access_token"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
Personal Access Token (Optional)
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="personal_access_token"
|
||||
name="personal_access_token"
|
||||
bind:value={personalAccessToken}
|
||||
placeholder="ghp_..."
|
||||
class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder-neutral-500 dark:focus:border-neutral-500"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Required for private repositories to clone and for developers to push back to GitHub.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync Settings -->
|
||||
<div
|
||||
class="rounded-lg border border-neutral-200 bg-white p-6 dark:border-neutral-800 dark:bg-neutral-900"
|
||||
>
|
||||
<h2 class="mb-4 text-lg font-semibold text-neutral-900 dark:text-neutral-50">
|
||||
Sync Settings
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Sync Strategy -->
|
||||
<div>
|
||||
<label
|
||||
for="sync_strategy"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
Sync Strategy
|
||||
</label>
|
||||
<select
|
||||
id="sync_strategy"
|
||||
name="sync_strategy"
|
||||
bind:value={syncStrategy}
|
||||
class="mt-1 block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 dark:focus:border-neutral-500"
|
||||
>
|
||||
<option value={0}>Manual (no auto-sync)</option>
|
||||
<option value={5}>Every 5 minutes</option>
|
||||
<option value={15}>Every 15 minutes</option>
|
||||
<option value={30}>Every 30 minutes</option>
|
||||
<option value={60}>Every hour</option>
|
||||
<option value={360}>Every 6 hours</option>
|
||||
<option value={720}>Every 12 hours</option>
|
||||
<option value={1440}>Every 24 hours</option>
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
How often to check for updates from the remote repository
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Auto Pull -->
|
||||
<div class="flex items-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="auto_pull"
|
||||
name="auto_pull"
|
||||
bind:checked={autoPull}
|
||||
value="1"
|
||||
class="mt-0.5 h-4 w-4 rounded border-neutral-300 text-neutral-600 focus:ring-0 dark:border-neutral-700 dark:bg-neutral-800"
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
for="auto_pull"
|
||||
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
Automatically pull updates
|
||||
</label>
|
||||
<p class="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
If enabled, updates will be pulled automatically. If disabled, you'll only receive
|
||||
notifications when updates are available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex flex-wrap items-center justify-end gap-3">
|
||||
{#if mode === 'edit'}
|
||||
<a
|
||||
href="/databases/{instance?.id}"
|
||||
class="flex items-center gap-2 rounded-lg border border-neutral-300 bg-white px-4 py-2 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300 dark:hover:bg-neutral-800"
|
||||
>
|
||||
Cancel
|
||||
</a>
|
||||
{/if}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||
>
|
||||
{#if isLoading}
|
||||
<Loader2 size={14} class="animate-spin" />
|
||||
{mode === 'create' ? 'Linking...' : 'Saving...'}
|
||||
{:else}
|
||||
<Save size={14} />
|
||||
{submitButtonText}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Delete Section (Edit Mode Only) -->
|
||||
{#if mode === 'edit'}
|
||||
<div
|
||||
class="rounded-lg border border-red-200 bg-red-50 p-6 dark:border-red-800 dark:bg-red-950/40"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-red-700 dark:text-red-300">Danger Zone</h2>
|
||||
<p class="mt-2 text-sm text-red-600 dark:text-red-400">
|
||||
Once you unlink this database, there is no going back. All local data will be removed.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showDeleteModal = true)}
|
||||
class="mt-4 flex items-center gap-2 rounded-lg border border-red-300 bg-white px-4 py-2 text-sm font-medium text-red-700 transition-colors hover:bg-red-50 dark:border-red-700 dark:bg-neutral-900 dark:text-red-300 dark:hover:bg-red-900"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
Unlink Database
|
||||
</button>
|
||||
|
||||
<form
|
||||
bind:this={deleteFormElement}
|
||||
method="POST"
|
||||
action="?/delete"
|
||||
class="hidden"
|
||||
use:enhance={() => {
|
||||
return async ({ result, update }) => {
|
||||
if (result.type === 'failure' && result.data) {
|
||||
alertStore.add(
|
||||
'error',
|
||||
(result.data as { error?: string }).error || 'Failed to unlink database'
|
||||
);
|
||||
} else if (result.type === 'redirect') {
|
||||
alertStore.add('success', 'Database unlinked successfully');
|
||||
}
|
||||
await update();
|
||||
};
|
||||
}}
|
||||
>
|
||||
<!-- Empty form, just for submission -->
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
></form>
|
||||
{/if}
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{#if mode === 'edit'}
|
||||
@@ -348,8 +305,13 @@
|
||||
confirmDanger={true}
|
||||
on:confirm={() => {
|
||||
showDeleteModal = false;
|
||||
deleteFormElement?.requestSubmit();
|
||||
const deleteForm = document.getElementById('delete-form');
|
||||
if (deleteForm instanceof HTMLFormElement) {
|
||||
deleteForm.requestSubmit();
|
||||
}
|
||||
}}
|
||||
on:cancel={() => (showDeleteModal = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<DirtyModal />
|
||||
|
||||
@@ -6,4 +6,10 @@
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<InstanceForm mode="create" {form} {data} />
|
||||
<svelte:head>
|
||||
<title>Link Database - Profilarr</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="p-8">
|
||||
<InstanceForm mode="create" {form} {data} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user