diff --git a/frontend/src/types.ts b/frontend/src/types.ts index f081de0..0121e0a 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -421,6 +421,8 @@ export interface SystemSettings { enableStorageGuard: boolean; pauseRecordingWhenFreeSpaceBelowMegabytes: number; resumeRecordingWhenFreeSpaceAboveMegabytes: number; + storageGreenThresholdPercent: number; + storageRedThresholdPercent: number; enableRetentionCleanup: boolean; retentionDays: number; retentionDeleteFiles: boolean; diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 3fecf98..dd01468 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -106,6 +106,8 @@ const form = reactive({ enableStorageGuard: true, pauseRecordingWhenFreeSpaceBelowMegabytes: 1024, resumeRecordingWhenFreeSpaceAboveMegabytes: 4096, + storageGreenThresholdPercent: 30, + storageRedThresholdPercent: 10, enableRetentionCleanup: false, retentionDays: 30, retentionDeleteFiles: false, @@ -1097,10 +1099,37 @@ watch( + + + + +
高于此比例时正常录制,低于时拒绝新录制
+
+
+ + + +
低于此比例时暂停所有录制和转码,仅保留上传
+
+
+
- 恢复阈值建议高于暂停阈值,避免磁盘空间在临界值附近反复抖动。MP4 转码会额外占用中间 TS 文件空间。 + 恢复阈值建议高于暂停阈值,避免磁盘空间在临界值附近反复抖动。MP4 转码会额外占用中间 TS 文件空间。
+ 绿色/红色水位线控制三级存储保护:绿色(正常录制) → 黄色(拒绝新录制,现有继续转码上传) → 红色(暂停所有录制转码,仅上传清盘)。
diff --git a/src/LiveRecorder.Application/Models/Settings/SettingsModels.cs b/src/LiveRecorder.Application/Models/Settings/SettingsModels.cs index 01cfbf0..c686a2e 100644 --- a/src/LiveRecorder.Application/Models/Settings/SettingsModels.cs +++ b/src/LiveRecorder.Application/Models/Settings/SettingsModels.cs @@ -112,6 +112,12 @@ public sealed class SystemSettingsDto public int ResumeRecordingWhenFreeSpaceAboveMegabytes { get; set; } = 4096; + /// Disk free percentage above which storage is considered healthy (Green tier). Default 30%. + public double StorageGreenThresholdPercent { get; set; } = 30; + + /// Disk free percentage below which storage is critical (Red tier). Default 10%. + public double StorageRedThresholdPercent { get; set; } = 10; + public bool EnableAutoReconnect { get; set; } = true; public int ReconnectDelayMaxSeconds { get; set; } = 5; @@ -385,6 +391,12 @@ public sealed class UpdateSystemSettingsRequest public int ResumeRecordingWhenFreeSpaceAboveMegabytes { get; set; } = 4096; + /// Disk free percentage above which storage is considered healthy (Green tier). Default 30%. + public double StorageGreenThresholdPercent { get; set; } = 30; + + /// Disk free percentage below which storage is critical (Red tier). Default 10%. + public double StorageRedThresholdPercent { get; set; } = 10; + public bool EnableAutoReconnect { get; set; } = true; public int ReconnectDelayMaxSeconds { get; set; } = 5; diff --git a/src/LiveRecorder.Application/Services/SystemSettingsService.cs b/src/LiveRecorder.Application/Services/SystemSettingsService.cs index f72a5a8..6c6da70 100644 --- a/src/LiveRecorder.Application/Services/SystemSettingsService.cs +++ b/src/LiveRecorder.Application/Services/SystemSettingsService.cs @@ -5,6 +5,7 @@ using LiveRecorder.Application.Models.Cleanup; using LiveRecorder.Application.Models.Settings; using LiveRecorder.Domain.Entities; using LiveRecorder.Domain.Enums; +using System.Globalization; using System.Text.Json; namespace LiveRecorder.Application.Services; @@ -25,6 +26,8 @@ public sealed class SystemSettingsService : ISystemSettingsService private const string EnableStorageGuardKey = "storage.guard.enabled"; private const string PauseRecordingWhenFreeSpaceBelowMegabytesKey = "storage.guard.pause_recording_below_mb"; private const string ResumeRecordingWhenFreeSpaceAboveMegabytesKey = "storage.guard.resume_recording_above_mb"; + private const string StorageGreenThresholdPercentKey = "storage.guard.green_threshold_percent"; + private const string StorageRedThresholdPercentKey = "storage.guard.red_threshold_percent"; private const string EnableReconnectKey = "recording.enable_auto_reconnect"; private const string ReconnectDelayMaxSecondsKey = "recording.reconnect_delay_max_seconds"; private const string ReadWriteTimeoutMillisecondsKey = "recording.read_write_timeout_milliseconds"; @@ -140,6 +143,8 @@ public sealed class SystemSettingsService : ISystemSettingsService EnableStorageGuard = bool.TryParse(GetValue(lookup, EnableStorageGuardKey, "true"), out var enableStorageGuard) && enableStorageGuard, PauseRecordingWhenFreeSpaceBelowMegabytes = GetIntValue(lookup, PauseRecordingWhenFreeSpaceBelowMegabytesKey, 1024, 0, 1048576), ResumeRecordingWhenFreeSpaceAboveMegabytes = GetIntValue(lookup, ResumeRecordingWhenFreeSpaceAboveMegabytesKey, 4096, 0, 1048576), + StorageGreenThresholdPercent = GetDoubleValue(lookup, StorageGreenThresholdPercentKey, 30, 5, 90), + StorageRedThresholdPercent = GetDoubleValue(lookup, StorageRedThresholdPercentKey, 10, 1, 85), EnableAutoReconnect = bool.TryParse(GetValue(lookup, EnableReconnectKey, "true"), out var enableReconnect) && enableReconnect, ReconnectDelayMaxSeconds = GetIntValue(lookup, ReconnectDelayMaxSecondsKey, 5, 1, 300), ReadWriteTimeoutMilliseconds = GetIntValue(lookup, ReadWriteTimeoutMillisecondsKey, 15000000, 1000, 60000000), @@ -307,6 +312,16 @@ public sealed class SystemSettingsService : ISystemSettingsService Math.Clamp(request.ResumeRecordingWhenFreeSpaceAboveMegabytes, 0, 1048576).ToString(), now, cancellationToken); + await UpsertAsync( + StorageGreenThresholdPercentKey, + Math.Clamp(request.StorageGreenThresholdPercent, 5, 90).ToString("F1", CultureInfo.InvariantCulture), + now, + cancellationToken); + await UpsertAsync( + StorageRedThresholdPercentKey, + Math.Clamp(request.StorageRedThresholdPercent, 1, 85).ToString("F1", CultureInfo.InvariantCulture), + now, + cancellationToken); await UpsertAsync(EnableReconnectKey, request.EnableAutoReconnect.ToString(), now, cancellationToken); await UpsertAsync(ReconnectDelayMaxSecondsKey, request.ReconnectDelayMaxSeconds.ToString(), now, cancellationToken); await UpsertAsync(ReadWriteTimeoutMillisecondsKey, request.ReadWriteTimeoutMilliseconds.ToString(), now, cancellationToken); @@ -466,6 +481,22 @@ public sealed class SystemSettingsService : ISystemSettingsService return Math.Clamp(parsedValue, minimum, maximum); } + private static double GetDoubleValue( + IReadOnlyDictionary lookup, + string key, + double fallback, + double minimum, + double maximum) + { + var raw = GetValue(lookup, key, fallback.ToString(CultureInfo.InvariantCulture)); + if (!double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedValue)) + { + return fallback; + } + + return Math.Clamp(parsedValue, minimum, maximum); + } + private static IReadOnlyList GetIntListValue(IReadOnlyDictionary lookup, string key) { if (!lookup.TryGetValue(key, out var raw) || string.IsNullOrWhiteSpace(raw)) diff --git a/src/LiveRecorder.Infrastructure/Services/StorageGuardService.cs b/src/LiveRecorder.Infrastructure/Services/StorageGuardService.cs index 5eb2247..faa85e2 100644 --- a/src/LiveRecorder.Infrastructure/Services/StorageGuardService.cs +++ b/src/LiveRecorder.Infrastructure/Services/StorageGuardService.cs @@ -7,8 +7,6 @@ namespace LiveRecorder.Infrastructure.Services; public sealed class StorageGuardService : IStorageGuardService { private const long Megabyte = 1024L * 1024L; - private const double GreenThresholdPercent = 30.0; - private const double YellowThresholdPercent = 10.0; private readonly ILogger _logger; public StorageGuardService(ILogger logger) @@ -46,13 +44,16 @@ public sealed class StorageGuardService : IStorageGuardService var usagePercent = totalBytes > 0 ? (double)usedBytes / totalBytes * 100.0 : 0; var freePercent = 100.0 - usagePercent; - // Determine tier + // Determine tier using configurable thresholds + var greenThreshold = Math.Clamp(settings.StorageGreenThresholdPercent, 5, 90); + var redThreshold = Math.Clamp(settings.StorageRedThresholdPercent, 1, greenThreshold - 1); + StorageTier tier; - if (freePercent >= GreenThresholdPercent) + if (freePercent >= greenThreshold) { tier = StorageTier.Green; } - else if (freePercent >= YellowThresholdPercent) + else if (freePercent >= redThreshold) { tier = StorageTier.Yellow; }