Add extensible notification providers + UI/API

Introduce a provider-based notification system and wire it through the API and admin UI. Added INotificationProvider + notification service implementation and providers (apprise, discord, ntfy, pushover), plus a GET /api/admin/notifications/providers endpoint to expose provider metadata. Refactored code to use provider type strings (removed enum coupling), updated masking/encryption calls, and simplified the test notification endpoint to accept backendId or type+config and call sendToBackend directly.

UI: NotificationsTab now fetches provider metadata and renders provider cards and dynamic config forms (fields driven by provider metadata). Added config field rendering, improved backend cards, and edit/delete actions.

APIs: New providers route, updated admin notification CRUD routes to validate provider types dynamically, updated test route schema. Added download-client categories POST API to fetch categories from clients and wired postImportCategory handling in download-client routes.

Other notable changes: BookDate now fetches Claude models dynamically from Anthropic's Models API; added paginated model fetch helper. Added ALLOW_WEAK_PASSWORD flag exposure to auth providers and password change logic. Doc updates and various tests added/updated. File-organization doc clarifies EPERM fix using stream-based copy.
This commit is contained in:
kikootwo
2026-02-10 15:06:20 -05:00
parent 4a38dd3da8
commit af0eaceb98
73 changed files with 3421 additions and 866 deletions
+28
View File
@@ -27,7 +27,13 @@ import { AudibleRegion } from '@/lib/types/audible';
interface SelectedIndexer {
id: number;
name: string;
protocol: string;
priority: number;
seedingTimeMinutes?: number;
removeAfterProcessing?: boolean;
rssEnabled: boolean;
audiobookCategories: number[];
ebookCategories: number[];
}
interface SetupState {
@@ -86,6 +92,14 @@ interface SetupState {
bookdateApiKey: string;
bookdateModel: string;
bookdateConfigured: boolean;
// Cached UI state for back-navigation persistence
plexLibraries: { id: string; title: string; type: string }[];
absLibraries: { id: string; name: string; itemCount: number }[];
oidcTested: boolean;
pathsTested: boolean;
bookdateModels: { id: string; name: string }[];
validated: {
plex: boolean;
prowlarr: boolean;
@@ -152,6 +166,14 @@ export default function SetupWizard() {
bookdateApiKey: '',
bookdateModel: '',
bookdateConfigured: false,
// Cached UI state for back-navigation persistence
plexLibraries: [],
absLibraries: [],
oidcTested: false,
pathsTested: false,
bookdateModels: [],
validated: {
plex: false,
prowlarr: false,
@@ -379,6 +401,7 @@ export default function SetupWizard() {
plexToken={state.plexToken}
plexLibraryId={state.plexLibraryId}
plexTriggerScanAfterImport={state.plexTriggerScanAfterImport}
plexLibraries={state.plexLibraries}
onUpdate={updateField}
onNext={() => goToStep(currentStepNumber + 1)}
onBack={() => goToStep(currentStepNumber - 1)}
@@ -397,6 +420,7 @@ export default function SetupWizard() {
absApiToken={state.absApiToken}
absLibraryId={state.absLibraryId}
absTriggerScanAfterImport={state.absTriggerScanAfterImport}
absLibraries={state.absLibraries}
onUpdate={updateField}
onNext={() => goToStep(currentStepNumber + 1)}
onBack={() => goToStep(currentStepNumber - 1)}
@@ -435,6 +459,7 @@ export default function SetupWizard() {
oidcAdminClaimEnabled={state.oidcAdminClaimEnabled}
oidcAdminClaimName={state.oidcAdminClaimName}
oidcAdminClaimValue={state.oidcAdminClaimValue}
oidcTested={state.oidcTested}
onUpdate={updateField}
onNext={() => goToStep(currentStepNumber + 1)}
onBack={() => goToStep(currentStepNumber - 1)}
@@ -482,6 +507,7 @@ export default function SetupWizard() {
<ProwlarrStep
prowlarrUrl={state.prowlarrUrl}
prowlarrApiKey={state.prowlarrApiKey}
prowlarrIndexers={state.prowlarrIndexers}
onUpdate={updateField}
onNext={() => goToStep(currentStepNumber + 1)}
onBack={() => goToStep(currentStepNumber - 1)}
@@ -512,6 +538,7 @@ export default function SetupWizard() {
mediaDir={state.mediaDir}
metadataTaggingEnabled={state.metadataTaggingEnabled}
chapterMergingEnabled={state.chapterMergingEnabled}
pathsTested={state.pathsTested}
onUpdate={updateField}
onNext={() => goToStep(currentStepNumber + 1)}
onBack={() => goToStep(currentStepNumber - 1)}
@@ -528,6 +555,7 @@ export default function SetupWizard() {
bookdateApiKey={state.bookdateApiKey}
bookdateModel={state.bookdateModel}
bookdateConfigured={state.bookdateConfigured}
bookdateModels={state.bookdateModels}
onUpdate={updateField}
onNext={() => goToStep(currentStepNumber + 1)}
onSkip={() => goToStep(currentStepNumber + 1)}