improve Edit Job UI

This commit is contained in:
sbwalker
2026-01-14 08:20:30 -05:00
parent b0d624034a
commit 0d0efaf4ca
3 changed files with 92 additions and 121 deletions

View File

@@ -11,26 +11,24 @@
<div class="container"> <div class="container">
@if (!_editable) @if (!_editable)
{ {
<div class="alert alert-warning alert-dismissible fade show mb-3 custom" role="alert"> <ModuleMessage Message="@Localizer["JobNotEditable"]" Type="MessageType.Warning" />
@Localizer["JobNotEditable"]
</div>
} }
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label> <Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="type" class="form-control" @bind="@_jobType" readonly /> <input id="type" class="form-control" @bind="@_jobType" required disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="200" required disabled="@(!_editable)" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label> <Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="enabled" class="form-select" @bind="@_isEnabled" required> <select id="enabled" class="form-select" @bind="@_isEnabled" required disabled="@(!_editable)">
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
@@ -83,17 +81,20 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="next" HelpText="Optionally modify the date and time when this job should execute next" ResourceKey="NextExecution">Next Execution: </Label> <Label Class="col-sm-3" For="next" HelpText="The date and time when this job will execute next. This value cannot be modified. Use the settings above to control the execution of the job." ResourceKey="NextExecution">Next Execution: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="next" class="form-control" @bind="@_nextDate" readonly /> <input id="next" class="form-control" @bind="@_nextDate" disabled />
</div> </div>
</div> </div>
</div> </div>
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button> @if (_editable)
@if (!_editable)
{ {
<button type="button" class="btn btn-danger ms-1" @onclick="ExecuteJob">@SharedLocalizer["Execute"]</button> <button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
}
else
{
<button type="button" class="btn btn-danger" @onclick="DisableJob">@Localizer["Disable"]</button>
} }
<NavLink class="btn btn-secondary ms-1" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary ms-1" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br /> <br />
@@ -128,6 +129,11 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{
await LoadJob();
}
protected async Task LoadJob()
{ {
try try
{ {
@@ -165,23 +171,15 @@
private async Task SaveJob() private async Task SaveJob()
{ {
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate)) try
{ {
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
return;
}
validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
var job = await JobService.GetJobAsync(_jobId); var job = await JobService.GetJobAsync(_jobId);
job.Name = _name; job.Name = _name;
job.JobType = _jobType; job.IsEnabled = bool.Parse(_isEnabled);
var enabledChanged = job.IsEnabled != Boolean.Parse(_isEnabled);
job.IsEnabled = Boolean.Parse(_isEnabled);
if (_editable)
{
job.Frequency = _frequency; job.Frequency = _frequency;
if (job.Frequency == "O") // once if (job.Frequency == "O") // once
{ {
@@ -191,6 +189,7 @@
{ {
job.Interval = int.Parse(_interval); job.Interval = int.Parse(_interval);
} }
job.StartDate = _startDate.HasValue && _startTime.HasValue job.StartDate = _startDate.HasValue && _startTime.HasValue
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay)) ? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
: null; : null;
@@ -200,80 +199,63 @@
: null; : null;
job.RetentionHistory = int.Parse(_retentionHistory); job.RetentionHistory = int.Parse(_retentionHistory);
}
if (!job.IsEnabled) if (!job.IsEnabled || Utilities.ValidateEffectiveExpiryDates(job.StartDate, job.EndDate))
{
if (!job.IsEnabled || (job.StartDate >= DateTime.UtcNow || job.StartDate == null))
{ {
job.NextExecution = null; job.NextExecution = null;
} job = await JobService.UpdateJobAsync(job);
else if (enabledChanged) await logger.LogInformation("Job Updated {Job}", job);
{
job.NextExecution = null;
if(job.StartDate != null && job.StartDate < DateTime.UtcNow)
{
var startDate = DateTime.UtcNow;
if ((job.Frequency == "d" || job.Frequency == "w" || job.Frequency == "M") && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
// set the start time
startDate = startDate.Date.Add(job.StartDate.Value.TimeOfDay);
if(startDate < DateTime.UtcNow)
{
switch (job.Frequency)
{
case "d":
startDate = startDate.AddDays(job.Interval);
break;
case "w":
startDate = startDate.AddDays(job.Interval * 7);
break;
case "M":
startDate = startDate.AddMonths(job.Interval);
break;
}
}
}
job.StartDate = startDate;
}
}
await PerformSaveJobAction(job);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{
AddModuleMessage(Localizer["Message.StartDateError"], MessageType.Warning);
}
}
else
{
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
}
}
else
{ {
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
} }
} }
catch (Exception ex)
{
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
}
}
private async Task ExecuteJob() private async Task DisableJob()
{
try
{ {
var job = await JobService.GetJobAsync(_jobId); var job = await JobService.GetJobAsync(_jobId);
if (job != null) if (job != null)
{ {
if (job.IsExecuting)
{
AddModuleMessage(Localizer["Message.ExecutingError"], MessageType.Warning);
}
else
{
job.IsEnabled = false;
job.NextExecution = null; job.NextExecution = null;
await PerformSaveJobAction(job);
if(!job.IsStarted)
{
await JobService.StartJobAsync(_jobId);
}
NavigationManager.NavigateTo(NavigateUrl());
}
}
private async Task PerformSaveJobAction(Job job)
{
try
{
job = await JobService.UpdateJobAsync(job); job = await JobService.UpdateJobAsync(job);
await logger.LogInformation("Job Updated {Job}", job); await logger.LogInformation("Job Updated {Job}", job);
await LoadJob();
StateHasChanged();
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Updating Job {Job} {Error}", job, ex.Message); await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error); AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
} }
} }

View File

@@ -139,7 +139,7 @@
<value>Error Updating Job</value> <value>Error Updating Job</value>
</data> </data>
<data name="Message.Required.JobInfo" xml:space="preserve"> <data name="Message.Required.JobInfo" xml:space="preserve">
<value>You Must Provide The Job Name, Type, Frequency, and Retention</value> <value>You Must Provide The Job Name, Frequency, and Retention</value>
</data> </data>
<data name="Name.HelpText" xml:space="preserve"> <data name="Name.HelpText" xml:space="preserve">
<value>Enter the job name</value> <value>Enter the job name</value>
@@ -154,7 +154,7 @@
<value>Select how often you want the job to run</value> <value>Select how often you want the job to run</value>
</data> </data>
<data name="Starting.HelpText" xml:space="preserve"> <data name="Starting.HelpText" xml:space="preserve">
<value>Optionally enter the date and time when this job should start executing</value> <value>Optionally enter the date and time when this job should start executing. If no date or time is specified, the job will execute immediately.</value>
</data> </data>
<data name="Ending.HelpText" xml:space="preserve"> <data name="Ending.HelpText" xml:space="preserve">
<value>Optionally enter the date and time when this job should stop executing</value> <value>Optionally enter the date and time when this job should stop executing</value>
@@ -163,7 +163,7 @@
<value>Number of log entries to retain for this job</value> <value>Number of log entries to retain for this job</value>
</data> </data>
<data name="NextExecution.HelpText" xml:space="preserve"> <data name="NextExecution.HelpText" xml:space="preserve">
<value>Optionally modify the date and time when this job should execute next</value> <value>The date and time when this job will execute next. This value cannot be modified. Use the settings above to control the execution of the job.</value>
</data> </data>
<data name="Type.Text" xml:space="preserve"> <data name="Type.Text" xml:space="preserve">
<value>Type: </value> <value>Type: </value>
@@ -193,12 +193,15 @@
<value>Execute Once</value> <value>Execute Once</value>
</data> </data>
<data name="Message.StartEndDateError" xml:space="preserve"> <data name="Message.StartEndDateError" xml:space="preserve">
<value>Start Date cannot be after End Date.</value> <value>The Start Date Cannot Be Later Than The End Date</value>
</data>
<data name="Message.StartDateError" xml:space="preserve">
<value>The Start Date Cannot Be Prior To The Current Date</value>
</data>
<data name="Message.ExecutingError" xml:space="preserve">
<value>The Job Is Currently Executing. You Must Wait Until The Job Has Completed.</value>
</data> </data>
<data name="JobNotEditable" xml:space="preserve"> <data name="JobNotEditable" xml:space="preserve">
<value>The job properties is not able to be modified because it's enabled or running in progress.</value> <value>The Job Cannot Be Modified As It Is Currently Enabled. You Must Disable The Job To Change Its Settings.</value>
</data>
<data name="Execute" xml:space="preserve">
<value>Execute</value>
</data> </data>
</root> </root>

View File

@@ -198,41 +198,27 @@ namespace Oqtane.Infrastructure
{ {
case "m": // minutes case "m": // minutes
nextExecution = nextExecution.AddMinutes(job.Interval); nextExecution = nextExecution.AddMinutes(job.Interval);
if (nextExecution < DateTime.UtcNow) nextExecution = DateTime.UtcNow;
break; break;
case "H": // hours case "H": // hours
nextExecution = nextExecution.AddHours(job.Interval); nextExecution = nextExecution.AddHours(job.Interval);
if (nextExecution < DateTime.UtcNow) nextExecution = DateTime.UtcNow;
break; break;
case "d": // days case "d": // days
nextExecution = DateTime.UtcNow.Date.Add(nextExecution.TimeOfDay); // preserve time of day
nextExecution = nextExecution.AddDays(job.Interval); nextExecution = nextExecution.AddDays(job.Interval);
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
// set the start time
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
}
break; break;
case "w": // weeks case "w": // weeks
nextExecution = DateTime.UtcNow.Date.Add(nextExecution.TimeOfDay); // preserve time of day
nextExecution = nextExecution.AddDays(job.Interval * 7); nextExecution = nextExecution.AddDays(job.Interval * 7);
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
// set the start time
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
}
break; break;
case "M": // months case "M": // months
nextExecution = DateTime.UtcNow.Date.Add(nextExecution.TimeOfDay); // preserve time of day
nextExecution = nextExecution.AddMonths(job.Interval); nextExecution = nextExecution.AddMonths(job.Interval);
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
// set the start time
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
}
break; break;
case "O": // one time case "O": // one time
break; break;
} }
if (nextExecution < DateTime.UtcNow)
{
nextExecution = DateTime.UtcNow;
}
return nextExecution; return nextExecution;
} }