feat: Integrations splits into Library, Acquisition, Metadata sources and Email & network, each a page on the admin rail; the Overview keeps only what needs you (M503 #5607)
release / govulncheck (push) Successful in 14s
release / web (push) Failing after 16s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m54s
release / android (push) Successful in 5m5s
release / Build signed APK (releases and dev) (push) Successful in 5m18s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped

- Library: the folders, Run scan and a row per scan job, cover art, fingerprints and loudness (off the Overview and Duplicates)
- Acquisition: indexers and clients, searching, re-acquisition (off Missing files), Lidarr
- Metadata sources: AcoustID, and one provider block for cover art and tags in place of the pasted pair
- Email & network: outgoing email, the notification schedule, public address, proxies
- Lidarr, outgoing email and the provider block are rebuilt as SettingsGroup rows; failures read as sentences, not codes
- Duplicates and Missing files say what their setting is and link to where it is set

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 11:21:48 -04:00
co-authored by Claude Opus 5.5
parent 0cc4bc686a
commit 7e1cfb8d08
32 changed files with 1649 additions and 2115 deletions
@@ -0,0 +1,92 @@
<script lang="ts">
import { pageTitle } from '#lib/branding.js';
import { useQueryClient } from '@tanstack/svelte-query';
import {
createCoverProvidersQuery,
updateCoverProvider,
testCoverProvider,
createTagProvidersQuery,
updateTagProvider,
testTagProvider
} from '#lib/api/admin.js';
import { qk } from '#lib/api/queries.js';
import AcoustIDCard from '#lib/components/AcoustIDCard.svelte';
import ProviderGroup from '#lib/components/admin/ProviderGroup.svelte';
import SettingsColumns from '#lib/components/settings/SettingsColumns.svelte';
// Admin › Metadata sources (#5607): the outside sources Minstrel asks
// about your music. AcoustID names a recording from its sound; the cover
// art and tag sources share one block.
const client = useQueryClient();
const coverStore = createCoverProvidersQuery();
const tagStore = createTagProvidersQuery();
async function saveCover(id: string, patch: { enabled?: boolean; api_key?: string }) {
const result = await updateCoverProvider(id, patch);
await client.invalidateQueries({ queryKey: qk.coverProviders() });
// A change to the sources opens covers no source had to another try.
if (result.version_bumped) await client.invalidateQueries({ queryKey: qk.coverage() });
return result;
}
async function saveTag(id: string, patch: { enabled?: boolean; api_key?: string }) {
const result = await updateTagProvider(id, patch);
await client.invalidateQueries({ queryKey: qk.tagProviders() });
return result;
}
const link = 'underline hover:no-underline';
</script>
<svelte:head><title>{pageTitle('Admin · Metadata sources')}</title></svelte:head>
<h2 class="sr-only">Metadata sources</h2>
{#snippet coverNote(p: { id: string })}
{#if p.id === 'theaudiodb'}
Works with the public key;
<a class={link} href="https://www.theaudiodb.com/api_apply.php" target="_blank" rel="noopener noreferrer"
>a free key of your own</a
> gets its own quota.
{/if}
{/snippet}
{#snippet tagNote(p: { id: string })}
{#if p.id === 'musicbrainz'}
Needs no key; matches by MusicBrainz id.
{:else if p.id === 'lastfm'}
Matches by artist and title.
<a class={link} href="https://www.last.fm/api/account/create" target="_blank" rel="noopener noreferrer"
>Get a free key</a
>.
{/if}
{/snippet}
<SettingsColumns>
{#snippet main()}
<AcoustIDCard />
{/snippet}
{#snippet side()}
<ProviderGroup
title="Cover art"
hint="Asked in this order for covers and artist images; the first to have one wins."
noun="cover art sources"
query={$coverStore}
save={saveCover}
test={testCoverProvider}
keyPlaceholder="Empty uses the public key"
note={coverNote}
/>
<ProviderGroup
title="Tags"
hint="Styles and moods for each track, merged from every source switched on."
noun="tag sources"
query={$tagStore}
save={saveTag}
test={testTagProvider}
keyPlaceholder="Paste a key to use this source"
note={tagNote}
/>
{/snippet}
</SettingsColumns>