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