removed enabled per arr config and added enabled per arr instance

This commit is contained in:
Flaminel
2025-06-21 16:01:03 +03:00
parent 861c74e452
commit 5ea1361832
30 changed files with 269 additions and 421 deletions

View File

@@ -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;

View File

@@ -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<string, object>();
// 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<object>();
foreach (var instance in enabledSonarrInstances)
{
var sonarrStatus = new List<object>();
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<object>();
foreach (var instance in enabledRadarrInstances)
{
var radarrStatus = new List<object>();
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<object>();
foreach (var instance in enabledLidarrInstances)
{
var lidarrStatus = new List<object>();
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)

View File

@@ -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;

View File

@@ -7,6 +7,8 @@ namespace Cleanuparr.Application.Features.Arr.Dtos;
/// </summary>
public record CreateArrInstanceDto
{
public bool Enabled { get; init; } = true;
[Required]
public required string Name { get; init; }

View File

@@ -5,7 +5,5 @@ namespace Cleanuparr.Application.Features.Arr.Dtos;
/// </summary>
public record UpdateLidarrConfigDto
{
public bool Enabled { get; init; }
public short FailedImportMaxStrikes { get; init; } = -1;
}

View File

@@ -5,7 +5,5 @@ namespace Cleanuparr.Application.Features.Arr.Dtos;
/// </summary>
public record UpdateRadarrConfigDto
{
public bool Enabled { get; init; }
public short FailedImportMaxStrikes { get; init; } = -1;
}

View File

@@ -7,8 +7,6 @@ namespace Cleanuparr.Application.Features.Arr.Dtos;
/// </summary>
public record UpdateSonarrConfigDto
{
public bool Enabled { get; init; }
public short FailedImportMaxStrikes { get; init; } = -1;
}
@@ -22,6 +20,8 @@ public record ArrInstanceDto
/// </summary>
public Guid? Id { get; init; }
public bool Enabled { get; init; } = true;
[Required]
public required string Name { get; init; }

View File

@@ -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<string> ignoredDownloads = ContextProvider.Get<GeneralConfig>().IgnoredDownloads;

View File

@@ -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());

View File

@@ -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<string> ignoredDownloads = ContextProvider.Get<GeneralConfig>().IgnoredDownloads;

View File

@@ -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)
{

View File

@@ -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
{
/// <inheritdoc />
@@ -28,10 +28,6 @@ namespace Cleanuparr.Persistence.Migrations.Data
.HasColumnType("TEXT")
.HasColumnName("id");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("enabled");
b.Property<short>("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<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")

View File

@@ -36,7 +36,6 @@ namespace Cleanuparr.Persistence.Migrations.Data
{
id = table.Column<Guid>(type: "TEXT", nullable: false),
type = table.Column<string>(type: "TEXT", nullable: false),
enabled = table.Column<bool>(type: "INTEGER", nullable: false),
failed_import_max_strikes = table.Column<short>(type: "INTEGER", nullable: false)
},
constraints: table =>
@@ -183,6 +182,7 @@ namespace Cleanuparr.Persistence.Migrations.Data
columns: table => new
{
id = table.Column<Guid>(type: "TEXT", nullable: false),
enabled = table.Column<bool>(type: "INTEGER", nullable: false),
arr_config_id = table.Column<Guid>(type: "TEXT", nullable: false),
name = table.Column<string>(type: "TEXT", nullable: false),
url = table.Column<string>(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",

View File

@@ -25,10 +25,6 @@ namespace Cleanuparr.Persistence.Migrations.Data
.HasColumnType("TEXT")
.HasColumnName("id");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("enabled");
b.Property<short>("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<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")

View File

@@ -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<ArrInstance> Instances { get; set; } = [];

View File

@@ -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; }

View File

@@ -269,7 +269,7 @@ export class ConfigurationService {
/**
* Update Sonarr configuration (global settings only)
*/
updateSonarrConfig(config: {enabled: boolean, failedImportMaxStrikes: number}): Observable<any> {
updateSonarrConfig(config: {failedImportMaxStrikes: number}): Observable<any> {
return this.http.put<any>(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<RadarrConfig> {
return this.http.put<RadarrConfig>(this.basePathService.buildApiUrl('/configuration/radarr'), config).pipe(
updateRadarrConfig(config: {failedImportMaxStrikes: number}): Observable<any> {
return this.http.put<any>(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<LidarrConfig> {
return this.http.put<LidarrConfig>(this.basePathService.buildApiUrl('/configuration/lidarr'), config).pipe(
updateLidarrConfig(config: {failedImportMaxStrikes: number}): Observable<any> {
return this.http.put<any>(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"));

View File

@@ -49,44 +49,32 @@ export class LidarrConfigStore extends signalStore(
),
/**
* Save the Lidarr configuration (basic settings only)
* Save the Lidarr global configuration
*/
saveConfig: rxMethod<Partial<LidarrConfig>>(
(config$: Observable<Partial<LidarrConfig>>) => 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)
))
)
),

View File

@@ -32,14 +32,6 @@
</ng-template>
<form [formGroup]="globalForm" class="p-fluid">
<div class="field-row">
<label class="field-label">Enable Lidarr Integration</label>
<div class="field-input">
<p-checkbox formControlName="enabled" [binary]="true"></p-checkbox>
<small class="form-helper-text">When enabled, Lidarr API integration will be used</small>
</div>
</div>
<div class="field-row">
<label class="field-label">Failed Import Max Strikes</label>
<div>
@@ -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"
></button>
@@ -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"
></button>
@@ -128,6 +120,13 @@
<div class="instance-field">
<label>{{ instance.url }}</label>
</div>
<div class="instance-field">
<label>Status:
<span [class]="instance.enabled ? 'text-green-500' : 'text-red-500'">
{{ instance.enabled ? 'Enabled' : 'Disabled' }}
</span>
</label>
</div>
</div>
</div>
</div>
@@ -140,7 +139,7 @@
icon="pi pi-plus"
label="Add Instance"
class="p-button-outlined"
[disabled]="instanceManagementDisabled"
[disabled]="lidarrSaving()"
(click)="openAddInstanceModal()"
></button>
</div>
@@ -160,6 +159,14 @@
(onHide)="closeInstanceModal()"
>
<form [formGroup]="instanceForm" class="p-fluid instance-form">
<div class="field flex flex-row">
<label class="field-label">Enabled</label>
<div class="field-input">
<p-checkbox formControlName="enabled" [binary]="true"></p-checkbox>
<small class="form-helper-text">Enable this Lidarr instance</small>
</div>
</div>
<div class="field">
<label for="instance-name">Name *</label>
<input

View File

@@ -82,11 +82,11 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat
constructor() {
// Initialize forms
this.globalForm = this.formBuilder.group({
enabled: [false],
failedImportMaxStrikes: [{ value: -1, disabled: true }],
failedImportMaxStrikes: [-1],
});
this.instanceForm = this.formBuilder.group({
enabled: [true],
name: ['', Validators.required],
url: ['', [Validators.required, this.uriValidator.bind(this)]],
apiKey: ['', Validators.required],
@@ -95,9 +95,6 @@ export class LidarrSettingsComponent implements OnDestroy, CanComponentDeactivat
// Load Lidarr config data
this.lidarrStore.loadConfig();
// Setup form value change listeners
this.setupFormValueChangeListeners();
// Setup effect to update form when config changes
effect(() => {
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
}

View File

@@ -49,44 +49,32 @@ export class RadarrConfigStore extends signalStore(
),
/**
* Save the Radarr configuration (basic settings only)
* Save the Radarr global configuration
*/
saveConfig: rxMethod<Partial<RadarrConfig>>(
(config$: Observable<Partial<RadarrConfig>>) => 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)
))
)
),

View File

@@ -32,14 +32,6 @@
</ng-template>
<form [formGroup]="globalForm" class="p-fluid">
<div class="field-row">
<label class="field-label">Enable Radarr Integration</label>
<div class="field-input">
<p-checkbox formControlName="enabled" [binary]="true"></p-checkbox>
<small class="form-helper-text">When enabled, Radarr API integration will be used</small>
</div>
</div>
<div class="field-row">
<label class="field-label">Failed Import Max Strikes</label>
<div>
@@ -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"
></button>
@@ -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"
></button>
@@ -128,6 +120,13 @@
<div class="instance-field">
<label>{{ instance.url }}</label>
</div>
<div class="instance-field">
<label>Status:
<span [class]="instance.enabled ? 'text-green-500' : 'text-red-500'">
{{ instance.enabled ? 'Enabled' : 'Disabled' }}
</span>
</label>
</div>
</div>
</div>
</div>
@@ -140,7 +139,7 @@
icon="pi pi-plus"
label="Add Instance"
class="p-button-outlined"
[disabled]="instanceManagementDisabled"
[disabled]="radarrSaving()"
(click)="openAddInstanceModal()"
></button>
</div>
@@ -160,6 +159,14 @@
(onHide)="closeInstanceModal()"
>
<form [formGroup]="instanceForm" class="p-fluid instance-form">
<div class="field flex flex-row">
<label class="field-label">Enabled</label>
<div class="field-input">
<p-checkbox formControlName="enabled" [binary]="true"></p-checkbox>
<small class="form-helper-text">Enable this Radarr instance</small>
</div>
</div>
<div class="field">
<label for="instance-name">Name *</label>
<input

View File

@@ -82,11 +82,11 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat
constructor() {
// Initialize forms
this.globalForm = this.formBuilder.group({
enabled: [false],
failedImportMaxStrikes: [{ value: -1, disabled: true }],
failedImportMaxStrikes: [-1],
});
this.instanceForm = this.formBuilder.group({
enabled: [true],
name: ['', Validators.required],
url: ['', [Validators.required, this.uriValidator.bind(this)]],
apiKey: ['', Validators.required],
@@ -95,9 +95,6 @@ export class RadarrSettingsComponent implements OnDestroy, CanComponentDeactivat
// Load Radarr config data
this.radarrStore.loadConfig();
// Setup form value change listeners
this.setupFormValueChangeListeners();
// Setup effect to update form when config changes
effect(() => {
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
}

View File

@@ -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({

View File

@@ -32,14 +32,6 @@
</ng-template>
<form [formGroup]="globalForm" class="p-fluid">
<div class="field-row">
<label class="field-label">Enable Sonarr Integration</label>
<div class="field-input">
<p-checkbox formControlName="enabled" [binary]="true"></p-checkbox>
<small class="form-helper-text">When enabled, Sonarr API integration will be used</small>
</div>
</div>
<div class="field-row">
<label class="field-label">Failed Import Max Strikes</label>
<div>
@@ -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"
></button>
@@ -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"
></button>
@@ -128,6 +120,13 @@
<div class="instance-field">
<label>{{ instance.url }}</label>
</div>
<div class="instance-field">
<label>Status:
<span [class]="instance.enabled ? 'text-green-500' : 'text-red-500'">
{{ instance.enabled ? 'Enabled' : 'Disabled' }}
</span>
</label>
</div>
</div>
</div>
</div>
@@ -140,7 +139,7 @@
icon="pi pi-plus"
label="Add Instance"
class="p-button-outlined"
[disabled]="instanceManagementDisabled"
[disabled]="sonarrSaving()"
(click)="openAddInstanceModal()"
></button>
</div>
@@ -160,6 +159,14 @@
(onHide)="closeInstanceModal()"
>
<form [formGroup]="instanceForm" class="p-fluid instance-form">
<div class="field flex flex-row">
<label class="field-label">Enabled</label>
<div class="field-input">
<p-checkbox formControlName="enabled" [binary]="true"></p-checkbox>
<small class="form-helper-text">Enable this Sonarr instance</small>
</div>
</div>
<div class="field">
<label for="instance-name">Name *</label>
<input

View File

@@ -82,11 +82,11 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat
constructor() {
// Initialize forms
this.globalForm = this.formBuilder.group({
enabled: [false],
failedImportMaxStrikes: [{ value: -1, disabled: true }],
failedImportMaxStrikes: [-1],
});
this.instanceForm = this.formBuilder.group({
enabled: [true],
name: ['', Validators.required],
url: ['', [Validators.required, this.uriValidator.bind(this)]],
apiKey: ['', Validators.required],
@@ -95,9 +95,6 @@ export class SonarrSettingsComponent implements OnDestroy, CanComponentDeactivat
// Load Sonarr config data
this.sonarrStore.loadConfig();
// Setup form value change listeners
this.setupFormValueChangeListeners();
// Setup effect to update form when config changes
effect(() => {
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
}

View File

@@ -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;

View File

@@ -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[];
}

View File

@@ -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[];
}

View File

@@ -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[];
}