diff --git a/src/Marathon.UI/Pages/Settings.razor b/src/Marathon.UI/Pages/Settings.razor index 61f7da3..0dba50d 100644 --- a/src/Marathon.UI/Pages/Settings.razor +++ b/src/Marathon.UI/Pages/Settings.razor @@ -49,7 +49,7 @@ - + @@ -242,6 +242,20 @@ } } + if (payload is ScrapingSettingsForm scraping + && !(Uri.TryCreate(scraping.BaseUrl, UriKind.Absolute, out var baseUri) + && (baseUri.Scheme == Uri.UriSchemeHttp || baseUri.Scheme == Uri.UriSchemeHttps))) + { + Snackbar.Add(L["Settings.Scraping.BaseUrl.Invalid"], Severity.Error); + return; + } + + if (payload is WorkerOptions workers && !IsPlausibleCron(workers.UpcomingScheduleCron)) + { + Snackbar.Add(L["Settings.Workers.Cron.Invalid"], Severity.Error); + return; + } + var confirmed = await ConfirmAsync(); if (!confirmed) { @@ -260,6 +274,15 @@ } } + // Lightweight 5- or 6-field cron sanity check — avoids a Cronos dependency in the + // UI layer; the worker still does the authoritative parse at startup. + private static bool IsPlausibleCron(string? expression) + { + if (string.IsNullOrWhiteSpace(expression)) return false; + var fields = expression.Split(' ', StringSplitOptions.RemoveEmptyEntries); + return fields.Length is 5 or 6; + } + private async Task ResetSectionAsync(string section) { var confirmed = await ConfirmAsync();