diff --git a/code/backend/Cleanuparr.Api/Controllers/ConfigurationController.cs b/code/backend/Cleanuparr.Api/Controllers/ConfigurationController.cs index 8fd4aaf5..df8cce0e 100644 --- a/code/backend/Cleanuparr.Api/Controllers/ConfigurationController.cs +++ b/code/backend/Cleanuparr.Api/Controllers/ConfigurationController.cs @@ -588,7 +588,6 @@ public class ConfigurationController : ControllerBase var config = await _dataContext.ArrConfigs .FirstAsync(x => x.Type == InstanceType.Sonarr); - config.Enabled = newConfigDto.Enabled; config.FailedImportMaxStrikes = newConfigDto.FailedImportMaxStrikes; // Validate the configuration @@ -620,7 +619,6 @@ public class ConfigurationController : ControllerBase var config = await _dataContext.ArrConfigs .FirstAsync(x => x.Type == InstanceType.Radarr); - config.Enabled = newConfigDto.Enabled; config.FailedImportMaxStrikes = newConfigDto.FailedImportMaxStrikes; // Validate the configuration @@ -652,7 +650,6 @@ public class ConfigurationController : ControllerBase var config = await _dataContext.ArrConfigs .FirstAsync(x => x.Type == InstanceType.Lidarr); - config.Enabled = newConfigDto.Enabled; config.FailedImportMaxStrikes = newConfigDto.FailedImportMaxStrikes; // Validate the configuration @@ -719,6 +716,7 @@ public class ConfigurationController : ControllerBase // Create the new instance var instance = new ArrInstance { + Enabled = newInstance.Enabled, Name = newInstance.Name, Url = new Uri(newInstance.Url), ApiKey = newInstance.ApiKey, @@ -762,6 +760,7 @@ public class ConfigurationController : ControllerBase } // Update the instance properties + instance.Enabled = updatedInstance.Enabled; instance.Name = updatedInstance.Name; instance.Url = new Uri(updatedInstance.Url); instance.ApiKey = updatedInstance.ApiKey; @@ -828,6 +827,7 @@ public class ConfigurationController : ControllerBase // Create the new instance var instance = new ArrInstance { + Enabled = newInstance.Enabled, Name = newInstance.Name, Url = new Uri(newInstance.Url), ApiKey = newInstance.ApiKey, @@ -870,6 +870,7 @@ public class ConfigurationController : ControllerBase } // Update the instance properties + instance.Enabled = updatedInstance.Enabled; instance.Name = updatedInstance.Name; instance.Url = new Uri(updatedInstance.Url); instance.ApiKey = updatedInstance.ApiKey; @@ -936,6 +937,7 @@ public class ConfigurationController : ControllerBase // Create the new instance var instance = new ArrInstance { + Enabled = newInstance.Enabled, Name = newInstance.Name, Url = new Uri(newInstance.Url), ApiKey = newInstance.ApiKey, @@ -979,6 +981,7 @@ public class ConfigurationController : ControllerBase } // Update the instance properties + instance.Enabled = updatedInstance.Enabled; instance.Name = updatedInstance.Name; instance.Url = new Uri(updatedInstance.Url); instance.ApiKey = updatedInstance.ApiKey; diff --git a/code/backend/Cleanuparr.Api/Controllers/StatusController.cs b/code/backend/Cleanuparr.Api/Controllers/StatusController.cs index 9661d424..5f93ef9b 100644 --- a/code/backend/Cleanuparr.Api/Controllers/StatusController.cs +++ b/code/backend/Cleanuparr.Api/Controllers/StatusController.cs @@ -71,17 +71,14 @@ public class StatusController : ControllerBase { Sonarr = new { - IsEnabled = sonarrConfig.Enabled, InstanceCount = sonarrConfig.Instances.Count }, Radarr = new { - IsEnabled = radarrConfig.Enabled, InstanceCount = radarrConfig.Instances.Count }, Lidarr = new { - IsEnabled = lidarrConfig.Enabled, InstanceCount = lidarrConfig.Instances.Count } } @@ -143,124 +140,125 @@ public class StatusController : ControllerBase var status = new Dictionary(); // Get configurations - var sonarrConfig = await _dataContext.ArrConfigs + var enabledSonarrInstances = await _dataContext.ArrConfigs .Include(x => x.Instances) + .Where(x => x.Type == InstanceType.Sonarr) + .SelectMany(x => x.Instances) + .Where(x => x.Enabled) .AsNoTracking() - .FirstAsync(x => x.Type == InstanceType.Sonarr); - var radarrConfig = await _dataContext.ArrConfigs + .ToListAsync(); + var enabledRadarrInstances = await _dataContext.ArrConfigs .Include(x => x.Instances) + .Where(x => x.Type == InstanceType.Radarr) + .SelectMany(x => x.Instances) + .Where(x => x.Enabled) .AsNoTracking() - .FirstAsync(x => x.Type == InstanceType.Radarr); - var lidarrConfig = await _dataContext.ArrConfigs + .ToListAsync(); + var enabledLidarrInstances = await _dataContext.ArrConfigs .Include(x => x.Instances) + .Where(x => x.Type == InstanceType.Lidarr) + .SelectMany(x => x.Instances) + .Where(x => x.Enabled) .AsNoTracking() - .FirstAsync(x => x.Type == InstanceType.Lidarr); + .ToListAsync();; + // Check Sonarr instances - if (sonarrConfig is { Enabled: true, Instances.Count: > 0 }) + var sonarrStatus = new List(); + + foreach (var instance in enabledSonarrInstances) { - var sonarrStatus = new List(); - - foreach (var instance in sonarrConfig.Instances) + try { - try + var sonarrClient = _arrClientFactory.GetClient(InstanceType.Sonarr); + await sonarrClient.TestConnectionAsync(instance); + + sonarrStatus.Add(new { - var sonarrClient = _arrClientFactory.GetClient(InstanceType.Sonarr); - await sonarrClient.TestConnectionAsync(instance); - - sonarrStatus.Add(new - { - instance.Name, - instance.Url, - IsConnected = true, - Message = "Successfully connected" - }); - } - catch (Exception ex) - { - sonarrStatus.Add(new - { - instance.Name, - instance.Url, - IsConnected = false, - Message = $"Connection failed: {ex.Message}" - }); - } + instance.Name, + instance.Url, + IsConnected = true, + Message = "Successfully connected" + }); + } + catch (Exception ex) + { + sonarrStatus.Add(new + { + instance.Name, + instance.Url, + IsConnected = false, + Message = $"Connection failed: {ex.Message}" + }); } - - status["Sonarr"] = sonarrStatus; } + status["Sonarr"] = sonarrStatus; + // Check Radarr instances - if (radarrConfig is { Enabled: true, Instances.Count: > 0 }) + var radarrStatus = new List(); + + foreach (var instance in enabledRadarrInstances) { - var radarrStatus = new List(); - - foreach (var instance in radarrConfig.Instances) + try { - try + var radarrClient = _arrClientFactory.GetClient(InstanceType.Radarr); + await radarrClient.TestConnectionAsync(instance); + + radarrStatus.Add(new { - var radarrClient = _arrClientFactory.GetClient(InstanceType.Radarr); - await radarrClient.TestConnectionAsync(instance); - - radarrStatus.Add(new - { - instance.Name, - instance.Url, - IsConnected = true, - Message = "Successfully connected" - }); - } - catch (Exception ex) - { - radarrStatus.Add(new - { - instance.Name, - instance.Url, - IsConnected = false, - Message = $"Connection failed: {ex.Message}" - }); - } + instance.Name, + instance.Url, + IsConnected = true, + Message = "Successfully connected" + }); + } + catch (Exception ex) + { + radarrStatus.Add(new + { + instance.Name, + instance.Url, + IsConnected = false, + Message = $"Connection failed: {ex.Message}" + }); } - - status["Radarr"] = radarrStatus; } + status["Radarr"] = radarrStatus; + // Check Lidarr instances - if (lidarrConfig is { Enabled: true, Instances.Count: > 0 }) + var lidarrStatus = new List(); + + foreach (var instance in enabledLidarrInstances) { - var lidarrStatus = new List(); - - foreach (var instance in lidarrConfig.Instances) + try { - try + var lidarrClient = _arrClientFactory.GetClient(InstanceType.Lidarr); + await lidarrClient.TestConnectionAsync(instance); + + lidarrStatus.Add(new { - var lidarrClient = _arrClientFactory.GetClient(InstanceType.Lidarr); - await lidarrClient.TestConnectionAsync(instance); - - lidarrStatus.Add(new - { - instance.Name, - instance.Url, - IsConnected = true, - Message = "Successfully connected" - }); - } - catch (Exception ex) - { - lidarrStatus.Add(new - { - instance.Name, - instance.Url, - IsConnected = false, - Message = $"Connection failed: {ex.Message}" - }); - } + instance.Name, + instance.Url, + IsConnected = true, + Message = "Successfully connected" + }); + } + catch (Exception ex) + { + lidarrStatus.Add(new + { + instance.Name, + instance.Url, + IsConnected = false, + Message = $"Connection failed: {ex.Message}" + }); } - - status["Lidarr"] = lidarrStatus; } + status["Lidarr"] = lidarrStatus; + return Ok(status); } catch (Exception ex) diff --git a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/ArrConfigDto.cs b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/ArrConfigDto.cs index fe792dcb..aa847bb2 100644 --- a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/ArrConfigDto.cs +++ b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/ArrConfigDto.cs @@ -7,8 +7,6 @@ public class ArrConfigDto public Guid Id { get; set; } public required InstanceType Type { get; set; } - - public bool Enabled { get; set; } public short FailedImportMaxStrikes { get; set; } = -1; diff --git a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/CreateArrInstanceDto.cs b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/CreateArrInstanceDto.cs index c0ca0e02..4c1b8b4f 100644 --- a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/CreateArrInstanceDto.cs +++ b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/CreateArrInstanceDto.cs @@ -7,6 +7,8 @@ namespace Cleanuparr.Application.Features.Arr.Dtos; /// public record CreateArrInstanceDto { + public bool Enabled { get; init; } = true; + [Required] public required string Name { get; init; } diff --git a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateLidarrConfigDto.cs b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateLidarrConfigDto.cs index 2d3006ba..af9b59c5 100644 --- a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateLidarrConfigDto.cs +++ b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateLidarrConfigDto.cs @@ -5,7 +5,5 @@ namespace Cleanuparr.Application.Features.Arr.Dtos; /// public record UpdateLidarrConfigDto { - public bool Enabled { get; init; } - public short FailedImportMaxStrikes { get; init; } = -1; } \ No newline at end of file diff --git a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateRadarrConfigDto.cs b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateRadarrConfigDto.cs index e12d09cd..c9be046f 100644 --- a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateRadarrConfigDto.cs +++ b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateRadarrConfigDto.cs @@ -5,7 +5,5 @@ namespace Cleanuparr.Application.Features.Arr.Dtos; /// public record UpdateRadarrConfigDto { - public bool Enabled { get; init; } - public short FailedImportMaxStrikes { get; init; } = -1; } \ No newline at end of file diff --git a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateSonarrConfigDto.cs b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateSonarrConfigDto.cs index 770d455b..cea94357 100644 --- a/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateSonarrConfigDto.cs +++ b/code/backend/Cleanuparr.Application/Features/Arr/Dtos/UpdateSonarrConfigDto.cs @@ -7,8 +7,6 @@ namespace Cleanuparr.Application.Features.Arr.Dtos; /// public record UpdateSonarrConfigDto { - public bool Enabled { get; init; } - public short FailedImportMaxStrikes { get; init; } = -1; } @@ -22,6 +20,8 @@ public record ArrInstanceDto /// public Guid? Id { get; init; } + public bool Enabled { get; init; } = true; + [Required] public required string Name { get; init; } diff --git a/code/backend/Cleanuparr.Application/Features/ContentBlocker/ContentBlocker.cs b/code/backend/Cleanuparr.Application/Features/ContentBlocker/ContentBlocker.cs index 7322dc54..e82edcfe 100644 --- a/code/backend/Cleanuparr.Application/Features/ContentBlocker/ContentBlocker.cs +++ b/code/backend/Cleanuparr.Application/Features/ContentBlocker/ContentBlocker.cs @@ -80,7 +80,7 @@ public sealed class ContentBlocker : GenericHandler } } - protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig arrConfig) + protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType) { IReadOnlyList ignoredDownloads = ContextProvider.Get().IgnoredDownloads; diff --git a/code/backend/Cleanuparr.Application/Features/DownloadCleaner/DownloadCleaner.cs b/code/backend/Cleanuparr.Application/Features/DownloadCleaner/DownloadCleaner.cs index 1a50dd4c..857f429a 100644 --- a/code/backend/Cleanuparr.Application/Features/DownloadCleaner/DownloadCleaner.cs +++ b/code/backend/Cleanuparr.Application/Features/DownloadCleaner/DownloadCleaner.cs @@ -200,7 +200,7 @@ public sealed class DownloadCleaner : GenericHandler } } - protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig arrConfig) + protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType) { using var _ = LogContext.PushProperty(LogProperties.Category, instanceType.ToString()); diff --git a/code/backend/Cleanuparr.Application/Features/QueueCleaner/QueueCleaner.cs b/code/backend/Cleanuparr.Application/Features/QueueCleaner/QueueCleaner.cs index 4657ad5a..88ed5a35 100644 --- a/code/backend/Cleanuparr.Application/Features/QueueCleaner/QueueCleaner.cs +++ b/code/backend/Cleanuparr.Application/Features/QueueCleaner/QueueCleaner.cs @@ -54,7 +54,7 @@ public sealed class QueueCleaner : GenericHandler await ProcessArrConfigAsync(lidarrConfig, InstanceType.Lidarr); } - protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig arrConfig) + protected override async Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType) { IReadOnlyList ignoredDownloads = ContextProvider.Get().IgnoredDownloads; diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cs b/code/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cs index 8d139eb2..acd954e7 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cs @@ -147,12 +147,17 @@ public abstract class GenericHandler : IHandler protected abstract Task ExecuteInternalAsync(); - protected abstract Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType, ArrConfig arrConfig); + protected abstract Task ProcessInstanceAsync(ArrInstance instance, InstanceType instanceType); protected async Task ProcessArrConfigAsync(ArrConfig config, InstanceType instanceType, bool throwOnFailure = false) { - if (!config.Enabled) + var enabledInstances = config.Instances + .Where(x => x.Enabled) + .ToList(); + + if (enabledInstances.Count is 0) { + _logger.LogDebug($"Skip processing {instanceType}. No enabled instances found"); return; } @@ -160,7 +165,7 @@ public abstract class GenericHandler : IHandler { try { - await ProcessInstanceAsync(arrInstance, instanceType, config); + await ProcessInstanceAsync(arrInstance, instanceType); } catch (Exception exception) { diff --git a/code/backend/Cleanuparr.Persistence/Migrations/Data/20250620212344_InitialData.Designer.cs b/code/backend/Cleanuparr.Persistence/Migrations/Data/20250621123139_InitialData.Designer.cs similarity index 99% rename from code/backend/Cleanuparr.Persistence/Migrations/Data/20250620212344_InitialData.Designer.cs rename to code/backend/Cleanuparr.Persistence/Migrations/Data/20250621123139_InitialData.Designer.cs index e1b8480b..540797da 100644 --- a/code/backend/Cleanuparr.Persistence/Migrations/Data/20250620212344_InitialData.Designer.cs +++ b/code/backend/Cleanuparr.Persistence/Migrations/Data/20250621123139_InitialData.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Cleanuparr.Persistence.Migrations.Data { [DbContext(typeof(DataContext))] - [Migration("20250620212344_InitialData")] + [Migration("20250621123139_InitialData")] partial class InitialData { /// @@ -28,10 +28,6 @@ namespace Cleanuparr.Persistence.Migrations.Data .HasColumnType("TEXT") .HasColumnName("id"); - b.Property("Enabled") - .HasColumnType("INTEGER") - .HasColumnName("enabled"); - b.Property("FailedImportMaxStrikes") .HasColumnType("INTEGER") .HasColumnName("failed_import_max_strikes"); @@ -63,6 +59,10 @@ namespace Cleanuparr.Persistence.Migrations.Data .HasColumnType("TEXT") .HasColumnName("arr_config_id"); + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT") diff --git a/code/backend/Cleanuparr.Persistence/Migrations/Data/20250620212344_InitialData.cs b/code/backend/Cleanuparr.Persistence/Migrations/Data/20250621123139_InitialData.cs similarity index 97% rename from code/backend/Cleanuparr.Persistence/Migrations/Data/20250620212344_InitialData.cs rename to code/backend/Cleanuparr.Persistence/Migrations/Data/20250621123139_InitialData.cs index 500e62d9..74022112 100644 --- a/code/backend/Cleanuparr.Persistence/Migrations/Data/20250620212344_InitialData.cs +++ b/code/backend/Cleanuparr.Persistence/Migrations/Data/20250621123139_InitialData.cs @@ -36,7 +36,6 @@ namespace Cleanuparr.Persistence.Migrations.Data { id = table.Column(type: "TEXT", nullable: false), type = table.Column(type: "TEXT", nullable: false), - enabled = table.Column(type: "INTEGER", nullable: false), failed_import_max_strikes = table.Column(type: "INTEGER", nullable: false) }, constraints: table => @@ -183,6 +182,7 @@ namespace Cleanuparr.Persistence.Migrations.Data columns: table => new { id = table.Column(type: "TEXT", nullable: false), + enabled = table.Column(type: "INTEGER", nullable: false), arr_config_id = table.Column(type: "TEXT", nullable: false), name = table.Column(type: "TEXT", nullable: false), url = table.Column(type: "TEXT", nullable: false), @@ -276,18 +276,18 @@ namespace Cleanuparr.Persistence.Migrations.Data migrationBuilder.InsertData( table: "arr_configs", - columns: new[] { "id", "enabled", "failed_import_max_strikes", "type" }, - values: new object[] { new Guid("6096303a-399c-42b8-be8f-60a02cec5a51"), false, (short)-1, "radarr" }); + columns: new[] { "id", "failed_import_max_strikes", "type" }, + values: new object[] { new Guid("6096303a-399c-42b8-be8f-60a02cec5a51"), (short)-1, "radarr" }); migrationBuilder.InsertData( table: "arr_configs", - columns: new[] { "id", "enabled", "failed_import_max_strikes", "type" }, - values: new object[] { new Guid("4fd2b82b-cffd-4b41-bcc0-204058b1e459"), false, (short)-1, "lidarr" }); + columns: new[] { "id", "failed_import_max_strikes", "type" }, + values: new object[] { new Guid("4fd2b82b-cffd-4b41-bcc0-204058b1e459"), (short)-1, "lidarr" }); migrationBuilder.InsertData( table: "arr_configs", - columns: new[] { "id", "enabled", "failed_import_max_strikes", "type" }, - values: new object[] { new Guid("0b38a68f-3d7b-4d98-ae96-115da62d9af2"), false, (short)-1, "sonarr" }); + columns: new[] { "id", "failed_import_max_strikes", "type" }, + values: new object[] { new Guid("0b38a68f-3d7b-4d98-ae96-115da62d9af2"), (short)-1, "sonarr" }); migrationBuilder.CreateIndex( name: "ix_arr_instances_arr_config_id", diff --git a/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs b/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs index b8f6b40e..39383248 100644 --- a/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs +++ b/code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs @@ -25,10 +25,6 @@ namespace Cleanuparr.Persistence.Migrations.Data .HasColumnType("TEXT") .HasColumnName("id"); - b.Property("Enabled") - .HasColumnType("INTEGER") - .HasColumnName("enabled"); - b.Property("FailedImportMaxStrikes") .HasColumnType("INTEGER") .HasColumnName("failed_import_max_strikes"); @@ -60,6 +56,10 @@ namespace Cleanuparr.Persistence.Migrations.Data .HasColumnType("TEXT") .HasColumnName("arr_config_id"); + b.Property("Enabled") + .HasColumnType("INTEGER") + .HasColumnName("enabled"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT") diff --git a/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrConfig.cs b/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrConfig.cs index 3dc0b3ea..6b169bae 100644 --- a/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrConfig.cs +++ b/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrConfig.cs @@ -12,8 +12,6 @@ public class ArrConfig : IConfig public required InstanceType Type { get; set; } - public bool Enabled { get; set; } - public short FailedImportMaxStrikes { get; set; } = -1; public List Instances { get; set; } = []; diff --git a/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrInstance.cs b/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrInstance.cs index 387d4a9b..da782eb8 100644 --- a/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrInstance.cs +++ b/code/backend/Cleanuparr.Persistence/Models/Configuration/Arr/ArrInstance.cs @@ -10,6 +10,8 @@ public sealed class ArrInstance [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } = Guid.NewGuid(); + public bool Enabled { get; set; } + public Guid ArrConfigId { get; set; } public ArrConfig? ArrConfig { get; set; } diff --git a/code/frontend/src/app/core/services/configuration.service.ts b/code/frontend/src/app/core/services/configuration.service.ts index be1ea50d..405cc827 100644 --- a/code/frontend/src/app/core/services/configuration.service.ts +++ b/code/frontend/src/app/core/services/configuration.service.ts @@ -269,7 +269,7 @@ export class ConfigurationService { /** * Update Sonarr configuration (global settings only) */ - updateSonarrConfig(config: {enabled: boolean, failedImportMaxStrikes: number}): Observable { + updateSonarrConfig(config: {failedImportMaxStrikes: number}): Observable { return this.http.put(this.basePathService.buildApiUrl('/configuration/sonarr'), config).pipe( catchError((error) => { console.error("Error updating Sonarr config:", error); @@ -292,8 +292,8 @@ export class ConfigurationService { /** * Update Radarr configuration */ - updateRadarrConfig(config: RadarrConfig): Observable { - return this.http.put(this.basePathService.buildApiUrl('/configuration/radarr'), config).pipe( + updateRadarrConfig(config: {failedImportMaxStrikes: number}): Observable { + return this.http.put(this.basePathService.buildApiUrl('/configuration/radarr'), config).pipe( catchError((error) => { console.error("Error updating Radarr config:", error); return throwError(() => new Error(error.error?.error || "Failed to update Radarr configuration")); @@ -315,8 +315,8 @@ export class ConfigurationService { /** * Update Lidarr configuration */ - updateLidarrConfig(config: LidarrConfig): Observable { - return this.http.put(this.basePathService.buildApiUrl('/configuration/lidarr'), config).pipe( + updateLidarrConfig(config: {failedImportMaxStrikes: number}): Observable { + return this.http.put(this.basePathService.buildApiUrl('/configuration/lidarr'), config).pipe( catchError((error) => { console.error("Error updating Lidarr config:", error); return throwError(() => new Error(error.error?.error || "Failed to update Lidarr configuration")); diff --git a/code/frontend/src/app/settings/lidarr/lidarr-config.store.ts b/code/frontend/src/app/settings/lidarr/lidarr-config.store.ts index 8182003e..8ee8f9a3 100644 --- a/code/frontend/src/app/settings/lidarr/lidarr-config.store.ts +++ b/code/frontend/src/app/settings/lidarr/lidarr-config.store.ts @@ -49,44 +49,32 @@ export class LidarrConfigStore extends signalStore( ), /** - * Save the Lidarr configuration (basic settings only) + * Save the Lidarr global configuration */ - saveConfig: rxMethod>( - (config$: Observable>) => config$.pipe( + saveConfig: rxMethod<{failedImportMaxStrikes: number}>( + (globalConfig$: Observable<{failedImportMaxStrikes: number}>) => globalConfig$.pipe( tap(() => patchState(store, { saving: true, error: null })), - switchMap(configUpdate => { - const currentConfig = store.config(); - if (!currentConfig) { - patchState(store, { - saving: false, - error: 'No current configuration available' - }); - return EMPTY; - } - - const updatedConfig: LidarrConfig = { - ...currentConfig, - ...configUpdate - }; - - return configService.updateLidarrConfig(updatedConfig).pipe( - tap({ - next: () => { + switchMap(globalConfig => configService.updateLidarrConfig(globalConfig).pipe( + tap({ + next: () => { + const currentConfig = store.config(); + if (currentConfig) { + // Update the local config with the new global settings patchState(store, { - config: updatedConfig, + config: { ...currentConfig, ...globalConfig }, saving: false }); - }, - error: (error) => { - patchState(store, { - saving: false, - error: error.message || 'Failed to save Lidarr configuration' - }); } - }), - catchError(() => EMPTY) - ); - }) + }, + error: (error) => { + patchState(store, { + saving: false, + error: error.message || 'Failed to save Lidarr configuration' + }); + } + }), + catchError(() => EMPTY) + )) ) ), diff --git a/code/frontend/src/app/settings/lidarr/lidarr-settings.component.html b/code/frontend/src/app/settings/lidarr/lidarr-settings.component.html index 05b3f976..d05f2e36 100644 --- a/code/frontend/src/app/settings/lidarr/lidarr-settings.component.html +++ b/code/frontend/src/app/settings/lidarr/lidarr-settings.component.html @@ -32,14 +32,6 @@
-
- -
- - When enabled, Lidarr API integration will be used -
-
-
@@ -108,7 +100,7 @@ type="button" icon="pi pi-pencil" class="p-button-text p-button-sm" - [disabled]="instanceManagementDisabled" + [disabled]="lidarrSaving()" (click)="openEditInstanceModal(instance)" pTooltip="Edit instance" > @@ -117,7 +109,7 @@ type="button" icon="pi pi-trash" class="p-button-text p-button-sm p-button-danger" - [disabled]="instanceManagementDisabled" + [disabled]="lidarrSaving()" (click)="deleteInstance(instance)" pTooltip="Delete instance" > @@ -128,6 +120,13 @@
+
+ +
@@ -140,7 +139,7 @@ icon="pi pi-plus" label="Add Instance" class="p-button-outlined" - [disabled]="instanceManagementDisabled" + [disabled]="lidarrSaving()" (click)="openAddInstanceModal()" > @@ -160,6 +159,14 @@ (onHide)="closeInstanceModal()" > +
+ +
+ + Enable this Lidarr instance +
+
+
{ const config = this.lidarrConfig(); @@ -127,56 +124,13 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat */ private updateGlobalFormFromConfig(config: LidarrConfig): void { this.globalForm.patchValue({ - enabled: config.enabled, failedImportMaxStrikes: config.failedImportMaxStrikes, }); - // Update form control disabled states - this.updateFormControlDisabledStates(config); - // Store original values for dirty checking this.storeOriginalGlobalValues(); } - /** - * Set up listeners for form control value changes to manage dependent control states - */ - private setupFormValueChangeListeners(): void { - // Listen for changes to the 'enabled' control - const enabledControl = this.globalForm.get('enabled'); - if (enabledControl) { - enabledControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(enabled => { - this.updateMainControlsState(enabled); - }); - } - } - - /** - * Update form control disabled states based on the configuration - */ - private updateFormControlDisabledStates(config: LidarrConfig): void { - const enabled = config.enabled; - this.updateMainControlsState(enabled); - } - - /** - * Update the state of main controls based on the 'enabled' control value - */ - private updateMainControlsState(enabled: boolean): void { - const failedImportMaxStrikesControl = this.globalForm.get('failedImportMaxStrikes'); - - // Disable emitting events during state changes to prevent infinite loops - const options = { emitEvent: false }; - - if (enabled) { - failedImportMaxStrikesControl?.enable(options); - } else { - failedImportMaxStrikesControl?.disable(options); - } - } - /** * Store original global form values for dirty checking */ @@ -280,11 +234,7 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat return; } - const currentConfig = this.lidarrConfig(); - if (!currentConfig) return; - const updatedConfig = { - enabled: this.globalForm.get('enabled')?.value, failedImportMaxStrikes: this.globalForm.get('failedImportMaxStrikes')?.value }; @@ -330,20 +280,18 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat return this.lidarrConfig()?.instances || []; } - /** - * Check if instance management should be disabled - */ - get instanceManagementDisabled(): boolean { - return !this.globalForm.get('enabled')?.value; - } - /** * Open modal to add new instance */ openAddInstanceModal(): void { this.modalMode = 'add'; this.editingInstance = null; - this.instanceForm.reset(); + this.instanceForm.reset({ + enabled: true, + name: '', + url: '', + apiKey: '' + }); this.showInstanceModal = true; } @@ -354,6 +302,7 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat this.modalMode = 'edit'; this.editingInstance = instance; this.instanceForm.patchValue({ + enabled: instance.enabled, name: instance.name, url: instance.url, apiKey: instance.apiKey, @@ -382,6 +331,7 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat } const instanceData: CreateArrInstanceDto = { + enabled: this.instanceForm.get('enabled')?.value, name: this.instanceForm.get('name')?.value, url: this.instanceForm.get('url')?.value, apiKey: this.instanceForm.get('apiKey')?.value, @@ -456,12 +406,12 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat }); } + + /** * Get modal title based on mode */ get modalTitle(): string { return this.modalMode === 'add' ? 'Add Lidarr Instance' : 'Edit Lidarr Instance'; } - - // Add any other necessary methods here } diff --git a/code/frontend/src/app/settings/radarr/radarr-config.store.ts b/code/frontend/src/app/settings/radarr/radarr-config.store.ts index ff1cd390..f240ab56 100644 --- a/code/frontend/src/app/settings/radarr/radarr-config.store.ts +++ b/code/frontend/src/app/settings/radarr/radarr-config.store.ts @@ -49,44 +49,32 @@ export class RadarrConfigStore extends signalStore( ), /** - * Save the Radarr configuration (basic settings only) + * Save the Radarr global configuration */ - saveConfig: rxMethod>( - (config$: Observable>) => config$.pipe( + saveConfig: rxMethod<{failedImportMaxStrikes: number}>( + (globalConfig$: Observable<{failedImportMaxStrikes: number}>) => globalConfig$.pipe( tap(() => patchState(store, { saving: true, error: null })), - switchMap(configUpdate => { - const currentConfig = store.config(); - if (!currentConfig) { - patchState(store, { - saving: false, - error: 'No current configuration available' - }); - return EMPTY; - } - - const updatedConfig: RadarrConfig = { - ...currentConfig, - ...configUpdate - }; - - return configService.updateRadarrConfig(updatedConfig).pipe( - tap({ - next: () => { + switchMap(globalConfig => configService.updateRadarrConfig(globalConfig).pipe( + tap({ + next: () => { + const currentConfig = store.config(); + if (currentConfig) { + // Update the local config with the new global settings patchState(store, { - config: updatedConfig, + config: { ...currentConfig, ...globalConfig }, saving: false }); - }, - error: (error) => { - patchState(store, { - saving: false, - error: error.message || 'Failed to save Radarr configuration' - }); } - }), - catchError(() => EMPTY) - ); - }) + }, + error: (error) => { + patchState(store, { + saving: false, + error: error.message || 'Failed to save Radarr configuration' + }); + } + }), + catchError(() => EMPTY) + )) ) ), diff --git a/code/frontend/src/app/settings/radarr/radarr-settings.component.html b/code/frontend/src/app/settings/radarr/radarr-settings.component.html index faa9997a..1cf90d1e 100644 --- a/code/frontend/src/app/settings/radarr/radarr-settings.component.html +++ b/code/frontend/src/app/settings/radarr/radarr-settings.component.html @@ -32,14 +32,6 @@ -
- -
- - When enabled, Radarr API integration will be used -
-
-
@@ -108,7 +100,7 @@ type="button" icon="pi pi-pencil" class="p-button-text p-button-sm" - [disabled]="instanceManagementDisabled" + [disabled]="radarrSaving()" (click)="openEditInstanceModal(instance)" pTooltip="Edit instance" > @@ -117,7 +109,7 @@ type="button" icon="pi pi-trash" class="p-button-text p-button-sm p-button-danger" - [disabled]="instanceManagementDisabled" + [disabled]="radarrSaving()" (click)="deleteInstance(instance)" pTooltip="Delete instance" > @@ -128,6 +120,13 @@
+
+ +
@@ -140,7 +139,7 @@ icon="pi pi-plus" label="Add Instance" class="p-button-outlined" - [disabled]="instanceManagementDisabled" + [disabled]="radarrSaving()" (click)="openAddInstanceModal()" > @@ -160,6 +159,14 @@ (onHide)="closeInstanceModal()" > +
+ +
+ + Enable this Radarr instance +
+
+
{ const config = this.radarrConfig(); @@ -127,56 +124,13 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat */ private updateGlobalFormFromConfig(config: RadarrConfig): void { this.globalForm.patchValue({ - enabled: config.enabled, failedImportMaxStrikes: config.failedImportMaxStrikes, }); - // Update form control disabled states - this.updateFormControlDisabledStates(config); - // Store original values for dirty checking this.storeOriginalGlobalValues(); } - /** - * Set up listeners for form control value changes to manage dependent control states - */ - private setupFormValueChangeListeners(): void { - // Listen for changes to the 'enabled' control - const enabledControl = this.globalForm.get('enabled'); - if (enabledControl) { - enabledControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(enabled => { - this.updateMainControlsState(enabled); - }); - } - } - - /** - * Update form control disabled states based on the configuration - */ - private updateFormControlDisabledStates(config: RadarrConfig): void { - const enabled = config.enabled; - this.updateMainControlsState(enabled); - } - - /** - * Update the state of main controls based on the 'enabled' control value - */ - private updateMainControlsState(enabled: boolean): void { - const failedImportMaxStrikesControl = this.globalForm.get('failedImportMaxStrikes'); - - // Disable emitting events during state changes to prevent infinite loops - const options = { emitEvent: false }; - - if (enabled) { - failedImportMaxStrikesControl?.enable(options); - } else { - failedImportMaxStrikesControl?.disable(options); - } - } - /** * Store original global form values for dirty checking */ @@ -280,11 +234,7 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat return; } - const currentConfig = this.radarrConfig(); - if (!currentConfig) return; - const updatedConfig = { - enabled: this.globalForm.get('enabled')?.value, failedImportMaxStrikes: this.globalForm.get('failedImportMaxStrikes')?.value }; @@ -330,20 +280,18 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat return this.radarrConfig()?.instances || []; } - /** - * Check if instance management should be disabled - */ - get instanceManagementDisabled(): boolean { - return !this.globalForm.get('enabled')?.value; - } - /** * Open modal to add new instance */ openAddInstanceModal(): void { this.modalMode = 'add'; this.editingInstance = null; - this.instanceForm.reset(); + this.instanceForm.reset({ + enabled: true, + name: '', + url: '', + apiKey: '' + }); this.showInstanceModal = true; } @@ -354,6 +302,7 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat this.modalMode = 'edit'; this.editingInstance = instance; this.instanceForm.patchValue({ + enabled: instance.enabled, name: instance.name, url: instance.url, apiKey: instance.apiKey, @@ -382,6 +331,7 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat } const instanceData: CreateArrInstanceDto = { + enabled: this.instanceForm.get('enabled')?.value, name: this.instanceForm.get('name')?.value, url: this.instanceForm.get('url')?.value, apiKey: this.instanceForm.get('apiKey')?.value, @@ -456,12 +406,12 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat }); } + + /** * Get modal title based on mode */ get modalTitle(): string { return this.modalMode === 'add' ? 'Add Radarr Instance' : 'Edit Radarr Instance'; } - - // Add any other necessary methods here } diff --git a/code/frontend/src/app/settings/sonarr/sonarr-config.store.ts b/code/frontend/src/app/settings/sonarr/sonarr-config.store.ts index e8b11941..7afddba0 100644 --- a/code/frontend/src/app/settings/sonarr/sonarr-config.store.ts +++ b/code/frontend/src/app/settings/sonarr/sonarr-config.store.ts @@ -51,8 +51,8 @@ export class SonarrConfigStore extends signalStore( /** * Save the Sonarr global configuration */ - saveConfig: rxMethod<{enabled: boolean, failedImportMaxStrikes: number}>( - (globalConfig$: Observable<{enabled: boolean, failedImportMaxStrikes: number}>) => globalConfig$.pipe( + saveConfig: rxMethod<{failedImportMaxStrikes: number}>( + (globalConfig$: Observable<{failedImportMaxStrikes: number}>) => globalConfig$.pipe( tap(() => patchState(store, { saving: true, error: null })), switchMap(globalConfig => configService.updateSonarrConfig(globalConfig).pipe( tap({ diff --git a/code/frontend/src/app/settings/sonarr/sonarr-settings.component.html b/code/frontend/src/app/settings/sonarr/sonarr-settings.component.html index fa93fc81..ff12e9b4 100644 --- a/code/frontend/src/app/settings/sonarr/sonarr-settings.component.html +++ b/code/frontend/src/app/settings/sonarr/sonarr-settings.component.html @@ -32,14 +32,6 @@ -
- -
- - When enabled, Sonarr API integration will be used -
-
-
@@ -108,7 +100,7 @@ type="button" icon="pi pi-pencil" class="p-button-text p-button-sm" - [disabled]="instanceManagementDisabled" + [disabled]="sonarrSaving()" (click)="openEditInstanceModal(instance)" pTooltip="Edit instance" > @@ -117,7 +109,7 @@ type="button" icon="pi pi-trash" class="p-button-text p-button-sm p-button-danger" - [disabled]="instanceManagementDisabled" + [disabled]="sonarrSaving()" (click)="deleteInstance(instance)" pTooltip="Delete instance" > @@ -128,6 +120,13 @@
+
+ +
@@ -140,7 +139,7 @@ icon="pi pi-plus" label="Add Instance" class="p-button-outlined" - [disabled]="instanceManagementDisabled" + [disabled]="sonarrSaving()" (click)="openAddInstanceModal()" > @@ -160,6 +159,14 @@ (onHide)="closeInstanceModal()" > +
+ +
+ + Enable this Sonarr instance +
+
+
{ const config = this.sonarrConfig(); @@ -127,56 +124,13 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat */ private updateGlobalFormFromConfig(config: SonarrConfig): void { this.globalForm.patchValue({ - enabled: config.enabled, failedImportMaxStrikes: config.failedImportMaxStrikes, }); - // Update form control disabled states - this.updateFormControlDisabledStates(config); - // Store original values for dirty checking this.storeOriginalGlobalValues(); } - /** - * Set up listeners for form control value changes to manage dependent control states - */ - private setupFormValueChangeListeners(): void { - // Listen for changes to the 'enabled' control - const enabledControl = this.globalForm.get('enabled'); - if (enabledControl) { - enabledControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(enabled => { - this.updateMainControlsState(enabled); - }); - } - } - - /** - * Update form control disabled states based on the configuration - */ - private updateFormControlDisabledStates(config: SonarrConfig): void { - const enabled = config.enabled; - this.updateMainControlsState(enabled); - } - - /** - * Update the state of main controls based on the 'enabled' control value - */ - private updateMainControlsState(enabled: boolean): void { - const failedImportMaxStrikesControl = this.globalForm.get('failedImportMaxStrikes'); - - // Disable emitting events during state changes to prevent infinite loops - const options = { emitEvent: false }; - - if (enabled) { - failedImportMaxStrikesControl?.enable(options); - } else { - failedImportMaxStrikesControl?.disable(options); - } - } - /** * Store original global form values for dirty checking */ @@ -280,11 +234,7 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat return; } - const currentConfig = this.sonarrConfig(); - if (!currentConfig) return; - const updatedConfig = { - enabled: this.globalForm.get('enabled')?.value, failedImportMaxStrikes: this.globalForm.get('failedImportMaxStrikes')?.value }; @@ -330,20 +280,18 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat return this.sonarrConfig()?.instances || []; } - /** - * Check if instance management should be disabled - */ - get instanceManagementDisabled(): boolean { - return !this.globalForm.get('enabled')?.value; - } - /** * Open modal to add new instance */ openAddInstanceModal(): void { this.modalMode = 'add'; this.editingInstance = null; - this.instanceForm.reset(); + this.instanceForm.reset({ + enabled: true, + name: '', + url: '', + apiKey: '' + }); this.showInstanceModal = true; } @@ -354,6 +302,7 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat this.modalMode = 'edit'; this.editingInstance = instance; this.instanceForm.patchValue({ + enabled: instance.enabled, name: instance.name, url: instance.url, apiKey: instance.apiKey, @@ -382,6 +331,7 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat } const instanceData: CreateArrInstanceDto = { + enabled: this.instanceForm.get('enabled')?.value, name: this.instanceForm.get('name')?.value, url: this.instanceForm.get('url')?.value, apiKey: this.instanceForm.get('apiKey')?.value, @@ -456,12 +406,12 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat }); } + + /** * Get modal title based on mode */ get modalTitle(): string { return this.modalMode === 'add' ? 'Add Sonarr Instance' : 'Edit Sonarr Instance'; } - - // Add any other necessary methods here } diff --git a/code/frontend/src/app/shared/models/arr-config.model.ts b/code/frontend/src/app/shared/models/arr-config.model.ts index 46af7675..33c5dde1 100644 --- a/code/frontend/src/app/shared/models/arr-config.model.ts +++ b/code/frontend/src/app/shared/models/arr-config.model.ts @@ -3,6 +3,7 @@ */ export interface ArrInstance { id?: string; + enabled: boolean; name: string; url: string; apiKey: string; @@ -12,6 +13,7 @@ export interface ArrInstance { * DTO for creating new Arr instances without requiring an ID */ export interface CreateArrInstanceDto { + enabled: boolean; name: string; url: string; apiKey: string; diff --git a/code/frontend/src/app/shared/models/lidarr-config.model.ts b/code/frontend/src/app/shared/models/lidarr-config.model.ts index 29d32fb9..796de924 100644 --- a/code/frontend/src/app/shared/models/lidarr-config.model.ts +++ b/code/frontend/src/app/shared/models/lidarr-config.model.ts @@ -9,7 +9,6 @@ import { ArrInstance } from "./arr-config.model"; * Main LidarrConfig model representing the configuration for Lidarr integration */ export interface LidarrConfig { - enabled: boolean; failedImportMaxStrikes: number; instances: ArrInstance[]; } diff --git a/code/frontend/src/app/shared/models/radarr-config.model.ts b/code/frontend/src/app/shared/models/radarr-config.model.ts index 9e2e1d52..8b91ea9c 100644 --- a/code/frontend/src/app/shared/models/radarr-config.model.ts +++ b/code/frontend/src/app/shared/models/radarr-config.model.ts @@ -9,7 +9,6 @@ import { ArrInstance } from "./arr-config.model"; * Main RadarrConfig model representing the configuration for Radarr integration */ export interface RadarrConfig { - enabled: boolean; failedImportMaxStrikes: number; instances: ArrInstance[]; } diff --git a/code/frontend/src/app/shared/models/sonarr-config.model.ts b/code/frontend/src/app/shared/models/sonarr-config.model.ts index cfc647ba..6d1895f7 100644 --- a/code/frontend/src/app/shared/models/sonarr-config.model.ts +++ b/code/frontend/src/app/shared/models/sonarr-config.model.ts @@ -9,7 +9,6 @@ import { ArrInstance } from "./arr-config.model"; * Main SonarrConfig model representing the configuration for Sonarr integration */ export interface SonarrConfig { - enabled: boolean; failedImportMaxStrikes: number; instances: ArrInstance[]; }