06-12-2022
This commit is contained in:
89
ECM.JobRunner.Web/Components/Job/JobForm.razor
Normal file
89
ECM.JobRunner.Web/Components/Job/JobForm.razor
Normal file
@@ -0,0 +1,89 @@
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@inject JobService Jobs;
|
||||
|
||||
@if (job == null)
|
||||
{
|
||||
<p>Form loading..</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<EditForm Model="job" OnValidSubmit="OnValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id" class="form-label">Id</label>
|
||||
<input type="email" class="form-control" id="id" aria-describedby="nameHelp" @bind="job.Id" readonly="readonly">
|
||||
<div id="nameHelp" class="form-text">Die Datenbank Id des Jobs.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="name" aria-describedby="nameHelp" @bind="job.Name">
|
||||
<div id="nameHelp" class="form-text">Der Name des Jobs. Wird in der Übersicht angezeigt.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="cronExpression" class="form-label">Cron Expression</label>
|
||||
<input type="text" class="form-control" id="cronExpression" @bind="job.CronSchedule" aria-describedby="cronHelp">
|
||||
<div id="cronHelp" class="form-text">Die Zeitplan des Jobs. Erwartet eine <a href="https://www.quartz-scheduler.net/documentation/quartz-3.x/how-tos/crontrigger.html#examples" target="_blank">Quartz.NET Cron Expression.</a></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="jobType" class="form-label">Job Typ</label>
|
||||
<select class="form-select" aria-label="Job type selection" id="jobType" @bind="job.TypeId">
|
||||
<option selected>Auswählen..</option>
|
||||
@if (types != null)
|
||||
{
|
||||
@foreach (JobType type in types)
|
||||
{
|
||||
<option value="@type.Id">@type.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<div id="cronHelp" class="form-text">Der Typ des Jobs.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="enabled" @bind="job.Active">
|
||||
<label class="form-check-label" for="enabled">Aktiv</label>
|
||||
</div>
|
||||
|
||||
<div class="btn-group mt-3" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-secondary" href="jobs/@job.Id">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" />
|
||||
</svg> Back
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-save" viewBox="0 0 16 16">
|
||||
<path d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H9.5a1 1 0 0 0-1 1v7.293l2.646-2.647a.5.5 0 0 1 .708.708l-3.5 3.5a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L7.5 9.293V2a2 2 0 0 1 2-2H14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h2.5a.5.5 0 0 1 0 1H2z" />
|
||||
</svg> Save
|
||||
</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int JobId { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<EditContext> OnValidSubmit { get; set; }
|
||||
|
||||
private JobDefinition? job = new();
|
||||
private List<JobType>? types;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
types = await Jobs.GetJobTypes();
|
||||
|
||||
if (JobId == Constants.ENTITY_ID_NEW)
|
||||
{
|
||||
job = new JobDefinition() { Id = Constants.ENTITY_ID_NEW };
|
||||
}
|
||||
else
|
||||
{
|
||||
job = await Jobs.GetJob(JobId);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
ECM.JobRunner.Web/Components/Status/JobStatus.razor
Normal file
5
ECM.JobRunner.Web/Components/Status/JobStatus.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
<h3>JobStatus</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
29
ECM.JobRunner.Web/Components/Status/Progress.razor
Normal file
29
ECM.JobRunner.Web/Components/Status/Progress.razor
Normal file
@@ -0,0 +1,29 @@
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar"
|
||||
role="progressbar"
|
||||
style="width: @getCompletedPercent(jobStatus)%"
|
||||
aria-label="Job Status in %"
|
||||
aria-valuenow="@getCompletedPercent(jobStatus)"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public StatusItem? jobStatus { get; set; }
|
||||
|
||||
protected float getCompletedPercent(StatusItem? entry)
|
||||
{
|
||||
if (entry != null && entry.ProgressCurrent > 0 && entry.ProgressTotal > 0)
|
||||
{
|
||||
return ((float)entry.ProgressCurrent / (float)entry.ProgressTotal) * 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
ECM.JobRunner.Web/Constants.cs
Normal file
7
ECM.JobRunner.Web/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ECM.JobRunner.Web
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public const int ENTITY_ID_NEW = -1;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using DigitalData.Modules.Messaging.WCF;
|
||||
using ECM.JobRunner.Common;
|
||||
|
||||
namespace ECM.JobRunner.Web.Data
|
||||
{
|
||||
public class ApiService
|
||||
{
|
||||
private readonly Channel<Common.JobRunnerReference.IEDMIServiceChannel> channelManager;
|
||||
private readonly Common.JobRunnerReference.IEDMIServiceChannel channel;
|
||||
private ServerAddress address;
|
||||
|
||||
private System.Timers.Timer pollingTimer = new();
|
||||
|
||||
public event EventHandler<ApiStatusResponse> DataUpdated;
|
||||
|
||||
public ApiService(LoggingService Logging, WcfService Wcf)
|
||||
{
|
||||
channel = Wcf.Channel;
|
||||
|
||||
pollingTimer.Elapsed += PollingTimer_Elapsed;
|
||||
pollingTimer.Interval = 1000;
|
||||
pollingTimer.Start();
|
||||
}
|
||||
|
||||
protected virtual void OnDataUpdated(ApiStatusResponse e)
|
||||
{
|
||||
EventHandler<ApiStatusResponse> handler = DataUpdated;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void PollingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
OnDataUpdated(await GetData());
|
||||
}
|
||||
|
||||
public async Task<List<Common.JobRunnerReference.JobDefinition>?> GetJobList()
|
||||
{
|
||||
var resp = await channel.GetJobConfigAsync();
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return resp.JobDefinitions.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiStatusResponse> GetData()
|
||||
{
|
||||
DateTime heartbeat = await GetHeartbeat();
|
||||
List<Common.JobRunnerReference.HistoryItem> jobHistory = await GetHistoryEntries();
|
||||
|
||||
return new ApiStatusResponse()
|
||||
{
|
||||
heartbeat = heartbeat,
|
||||
jobHistory = jobHistory.OrderByDescending(e => e.CreatedAt).Take(10).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<DateTime> GetHeartbeat()
|
||||
{
|
||||
return await channel.GetHeartbeatAsync();
|
||||
}
|
||||
|
||||
private async Task<List<Common.JobRunnerReference.HistoryItem>> GetHistoryEntries()
|
||||
{
|
||||
try
|
||||
{
|
||||
var oResponse = await channel.GetJobHistoryAsync();
|
||||
|
||||
if (oResponse.OK)
|
||||
{
|
||||
return oResponse.Items.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
namespace ECM.JobRunner.Web.Data
|
||||
{
|
||||
public class ApiStatusResponse
|
||||
public class DashboardResponse
|
||||
{
|
||||
public DateTime heartbeat = DateTime.MinValue;
|
||||
public List<HistoryItem> jobHistory = new();
|
||||
public List<StatusItem> jobStatus = new();
|
||||
}
|
||||
}
|
||||
107
ECM.JobRunner.Web/Data/DashboardService.cs
Normal file
107
ECM.JobRunner.Web/Data/DashboardService.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using DigitalData.Modules.Logging;
|
||||
using DigitalData.Modules.Messaging.WCF;
|
||||
using ECM.JobRunner.Common;
|
||||
|
||||
namespace ECM.JobRunner.Web.Data
|
||||
{
|
||||
public class DashboardService
|
||||
{
|
||||
private readonly Common.JobRunnerReference.IEDMIServiceChannel channel;
|
||||
private Logger logger;
|
||||
|
||||
private System.Timers.Timer pollingTimer = new();
|
||||
|
||||
public event EventHandler<DashboardResponse> DataUpdated;
|
||||
|
||||
public DashboardService(LoggingService Logging, WcfService Wcf)
|
||||
{
|
||||
logger = Logging.LogConfig.GetLogger();
|
||||
channel = Wcf.Channel;
|
||||
|
||||
pollingTimer.Elapsed += PollingTimer_Elapsed;
|
||||
pollingTimer.Interval = 1000;
|
||||
pollingTimer.Start();
|
||||
}
|
||||
|
||||
protected virtual void OnDataUpdated(DashboardResponse e)
|
||||
{
|
||||
EventHandler<DashboardResponse> handler = DataUpdated;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void PollingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
OnDataUpdated(await GetData());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<DashboardResponse> GetData()
|
||||
{
|
||||
DateTime heartbeat = await GetHeartbeat();
|
||||
List<Common.JobRunnerReference.HistoryItem> jobHistory = await GetHistoryItems();
|
||||
List<Common.JobRunnerReference.StatusItem> jobStatus = await GetStatusItems();
|
||||
|
||||
return new DashboardResponse()
|
||||
{
|
||||
heartbeat = heartbeat,
|
||||
jobHistory = jobHistory.OrderByDescending(e => e.CreatedAt).Take(10).ToList(),
|
||||
jobStatus = jobStatus.OrderByDescending(e => e.StartTime).Take(10).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<DateTime> GetHeartbeat()
|
||||
{
|
||||
return await channel.GetHeartbeatAsync();
|
||||
}
|
||||
|
||||
private async Task<List<Common.JobRunnerReference.HistoryItem>> GetHistoryItems()
|
||||
{
|
||||
try
|
||||
{
|
||||
var oResponse = await channel.GetJobStatusAsync();
|
||||
|
||||
if (oResponse.OK)
|
||||
{
|
||||
return oResponse.HistoryItems.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e);
|
||||
return new();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task<List<Common.JobRunnerReference.StatusItem>> GetStatusItems()
|
||||
{
|
||||
try
|
||||
{
|
||||
var oResponse = await channel.GetJobStatusAsync();
|
||||
|
||||
if (oResponse.OK)
|
||||
{
|
||||
return oResponse.StatusItems.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e);
|
||||
return new();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
138
ECM.JobRunner.Web/Data/JobService.cs
Normal file
138
ECM.JobRunner.Web/Data/JobService.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using DigitalData.Modules.Logging;
|
||||
using ECM.JobRunner.Common.JobRunnerReference;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace ECM.JobRunner.Web.Data
|
||||
{
|
||||
public class JobService
|
||||
{
|
||||
private readonly Logger logger;
|
||||
private readonly Common.JobRunnerReference.IEDMIServiceChannel channel;
|
||||
|
||||
public JobService(LoggingService Logging, WcfService Wcf)
|
||||
{
|
||||
logger = Logging.LogConfig.GetLogger();
|
||||
channel = Wcf.Channel;
|
||||
}
|
||||
|
||||
public async Task<List<Common.JobRunnerReference.JobDefinition>> GetJobs()
|
||||
{
|
||||
var resp = await channel.GetJobConfigAsync();
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return resp.JobDefinitions.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Common.JobRunnerReference.JobType>> GetJobTypes()
|
||||
{
|
||||
var resp = await channel.GetJobConfigAsync();
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return resp.JobTypes.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Common.JobRunnerReference.JobDefinition?> GetJob(int pJobId)
|
||||
{
|
||||
var resp = await channel.GetJobConfigAsync();
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return resp.JobDefinitions.
|
||||
Where(j => j.Id == pJobId).
|
||||
FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateJob(JobDefinition job)
|
||||
{
|
||||
var resp = await channel.UpdateJobAsync(new UpdateJobUpdateJobRequest {
|
||||
Job = job,
|
||||
Action = UpdateJobUpdateJobRequest.UpdateJobAction.Update
|
||||
});
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CreateJob(JobDefinition job)
|
||||
{
|
||||
var resp = await channel.UpdateJobAsync(new UpdateJobUpdateJobRequest
|
||||
{
|
||||
Job = job,
|
||||
Action = UpdateJobUpdateJobRequest.UpdateJobAction.Create
|
||||
});
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteJob(JobDefinition job)
|
||||
{
|
||||
var resp = await channel.UpdateJobAsync(new UpdateJobUpdateJobRequest
|
||||
{
|
||||
Job = job,
|
||||
Action = UpdateJobUpdateJobRequest.UpdateJobAction.Delete
|
||||
});
|
||||
|
||||
if (resp != null && resp.OK)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime GetNextExecutionTime(string pCronExpression)
|
||||
{
|
||||
Quartz.CronExpression expression = new(pCronExpression);
|
||||
|
||||
if (expression != null)
|
||||
{
|
||||
var next = expression.GetNextValidTimeAfter(DateTimeOffset.Now);
|
||||
|
||||
if (next != null)
|
||||
{
|
||||
return ((DateTimeOffset)next).DateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.1.0" />
|
||||
<PackageReference Include="Quartz" Version="3.5.0" />
|
||||
<PackageReference Include="System.ServiceModel.Duplex" Version="4.10.*" />
|
||||
<PackageReference Include="System.ServiceModel.Federation" Version="4.10.*" />
|
||||
<PackageReference Include="System.ServiceModel.Http" Version="4.10.*" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/history"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@inject ApiService Api
|
||||
@inject DashboardService Api
|
||||
|
||||
<PageTitle>History</PageTitle>
|
||||
|
||||
@@ -41,18 +41,18 @@ else
|
||||
|
||||
protected async override void OnInitialized()
|
||||
{
|
||||
ApiStatusResponse data = await Api.GetData();
|
||||
DashboardResponse data = await Api.GetData();
|
||||
UpdateData(data);
|
||||
|
||||
Api.DataUpdated += Api_DataUpdated;
|
||||
}
|
||||
|
||||
protected void Api_DataUpdated(object sender, ApiStatusResponse e)
|
||||
protected void Api_DataUpdated(object sender, DashboardResponse e)
|
||||
{
|
||||
UpdateData(e);
|
||||
}
|
||||
|
||||
protected void UpdateData(ApiStatusResponse response)
|
||||
protected void UpdateData(DashboardResponse response)
|
||||
{
|
||||
today = response.heartbeat;
|
||||
historyEntries = response.jobHistory;
|
||||
|
||||
28
ECM.JobRunner.Web/Pages/Job/EditJob.razor
Normal file
28
ECM.JobRunner.Web/Pages/Job/EditJob.razor
Normal file
@@ -0,0 +1,28 @@
|
||||
@page "/jobs/{jobId:int}/edit"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@using ECM.JobRunner.Web.Components.Job;
|
||||
@inject NavigationManager Navigation;
|
||||
@inject JobService Jobs;
|
||||
|
||||
<PageTitle>Job bearbeiten</PageTitle>
|
||||
|
||||
<h3>Job bearbeiten</h3>
|
||||
|
||||
<JobForm JobId="JobId" OnValidSubmit="OnFormSubmit" />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int JobId { get; set; }
|
||||
|
||||
private async void OnFormSubmit(EditContext ctx)
|
||||
{
|
||||
JobDefinition job = (JobDefinition)ctx.Model;
|
||||
bool result = await Jobs.UpdateJob(job);
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
Navigation.NavigateTo($"/jobs/{job.Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
76
ECM.JobRunner.Web/Pages/Job/JobList.razor
Normal file
76
ECM.JobRunner.Web/Pages/Job/JobList.razor
Normal file
@@ -0,0 +1,76 @@
|
||||
@page "/jobs"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@inject JobService Jobs
|
||||
|
||||
<PageTitle>Jobs</PageTitle>
|
||||
|
||||
<h3>Jobs</h3>
|
||||
|
||||
@if (jobs == null)
|
||||
{
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Loading Jobs..</li>
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group">
|
||||
@foreach (var job in jobs)
|
||||
{
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-start" href="/jobs/@job.Id">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">
|
||||
<span>
|
||||
@if (job.Active)
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play-circle text-success" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445z" />
|
||||
</svg>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stop-circle text-danger" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3z" />
|
||||
</svg>
|
||||
}
|
||||
</span>
|
||||
<span>@job.Name</span>
|
||||
</div>
|
||||
@job.Type.Name
|
||||
</div>
|
||||
|
||||
@if (job.Active)
|
||||
{
|
||||
<span class="badge bg-primary rounded-pill">Running</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger rounded-pill">Stopped</span>
|
||||
}
|
||||
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="btn-group mt-3" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-primary" href="jobs/new">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-circle" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
|
||||
</svg> New
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<JobDefinition>? jobs;
|
||||
|
||||
protected async override void OnInitialized()
|
||||
{
|
||||
jobs = await Jobs.GetJobs();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
@page "/jobs/{jobId:int}"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@inject ApiService Api
|
||||
|
||||
@inject NavigationManager Navigation;
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject JobService Jobs
|
||||
|
||||
<PageTitle>Job</PageTitle>
|
||||
|
||||
@@ -49,7 +52,7 @@ else
|
||||
<path d="M2 1.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1h-11a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1-.5-.5zm2.5.5v1a3.5 3.5 0 0 0 1.989 3.158c.533.256 1.011.791 1.011 1.491v.702c0 .7-.478 1.235-1.011 1.491A3.5 3.5 0 0 0 4.5 13v1h7v-1a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351v-.702c0-.7.478-1.235 1.011-1.491A3.5 3.5 0 0 0 11.5 3V2h-7z" />
|
||||
</svg> Next Run at
|
||||
</div>
|
||||
@DateTime.Now.AddMinutes(5)
|
||||
@nextExecution
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -66,7 +69,7 @@ else
|
||||
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z" />
|
||||
</svg> Edit
|
||||
</a>
|
||||
<a class="btn btn-danger">
|
||||
<a class="btn btn-danger" @onclick="DeleteJob">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3" viewBox="0 0 16 16">
|
||||
<path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5ZM11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H2.506a.58.58 0 0 0-.01 0H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1h-.995a.59.59 0 0 0-.01 0H11Zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5h9.916Zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47ZM8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5Z" />
|
||||
</svg> Delete
|
||||
@@ -79,15 +82,31 @@ else
|
||||
public int JobId { get; set; }
|
||||
|
||||
private JobDefinition? job;
|
||||
private DateTime nextExecution;
|
||||
|
||||
protected async override void OnInitialized()
|
||||
{
|
||||
var jobs = await Api.GetJobList();
|
||||
job = await Jobs.GetJob(JobId);
|
||||
|
||||
if (jobs != null)
|
||||
{
|
||||
job = jobs.Where(j => j.Id == JobId).SingleOrDefault();
|
||||
if (job != null)
|
||||
{
|
||||
nextExecution = Jobs.GetNextExecutionTime(job.CronSchedule);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected async void DeleteJob()
|
||||
{
|
||||
if (job != null)
|
||||
{
|
||||
bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
|
||||
if (confirmed)
|
||||
{
|
||||
await Jobs.DeleteJob(job);
|
||||
Navigation.NavigateTo($"/jobs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
28
ECM.JobRunner.Web/Pages/Job/NewJob.razor
Normal file
28
ECM.JobRunner.Web/Pages/Job/NewJob.razor
Normal file
@@ -0,0 +1,28 @@
|
||||
@page "/jobs/new"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@using ECM.JobRunner.Web.Components.Job;
|
||||
@inject NavigationManager Navigation;
|
||||
@inject JobService Jobs;
|
||||
|
||||
<PageTitle>Job erstellen</PageTitle>
|
||||
|
||||
<h3>Job erstellen</h3>
|
||||
|
||||
<JobForm JobId="JobId" OnValidSubmit="OnFormSubmit" />
|
||||
|
||||
@code {
|
||||
public int JobId = -1;
|
||||
|
||||
private async void OnFormSubmit(EditContext ctx)
|
||||
{
|
||||
JobDefinition job = (JobDefinition)ctx.Model;
|
||||
|
||||
bool result = await Jobs.CreateJob(job);
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
Navigation.NavigateTo($"/jobs");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<h3>JobForm</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
@page "/jobs"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@inject ApiService Api
|
||||
|
||||
<PageTitle>Jobs</PageTitle>
|
||||
|
||||
<h3>Jobs</h3>
|
||||
|
||||
@if (jobList == null)
|
||||
{
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Loading Jobs..</li>
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group">
|
||||
@foreach (var job in jobList)
|
||||
{
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-start" href="/jobs/@job.Id">
|
||||
<div class="ms-2 me-auto">
|
||||
<div class="fw-bold">
|
||||
<span>@job.Name</span>
|
||||
</div>
|
||||
@job.Type.Name
|
||||
</div>
|
||||
<span class="badge bg-primary rounded-pill">Now</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<JobDefinition>? jobList;
|
||||
|
||||
protected async override void OnInitialized()
|
||||
{
|
||||
jobList = await Api.GetJobList();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<PageTitle>New Job</PageTitle>
|
||||
|
||||
<h3>New Job</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
135
ECM.JobRunner.Web/Pages/Status.razor
Normal file
135
ECM.JobRunner.Web/Pages/Status.razor
Normal file
@@ -0,0 +1,135 @@
|
||||
@page "/status"
|
||||
@using ECM.JobRunner.Common.JobRunnerReference;
|
||||
@using ECM.JobRunner.Web.Components.Status;
|
||||
@using ECM.JobRunner.Web.Data;
|
||||
@inject DashboardService Api
|
||||
|
||||
<PageTitle>Status</PageTitle>
|
||||
|
||||
<h3 class="mb-3">Job Status</h3>
|
||||
|
||||
<section class="mb-5">
|
||||
@if (executingEntries == null)
|
||||
{
|
||||
<h4>Executing</h4>
|
||||
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Loading Job Status..</li>
|
||||
</ul>
|
||||
}
|
||||
else if (executingEntries.Count == 0)
|
||||
{
|
||||
<h4>Executing</h4>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">No jobs currently executing</li>
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h4>Executing (@executingEntries.Count)</h4>
|
||||
|
||||
<ul class="list-group">
|
||||
@foreach (var entry in executingEntries)
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div class="ms-2 me-auto w-100">
|
||||
<div class="fw-bold">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle text-success" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" />
|
||||
</svg>
|
||||
<span>@entry.Name</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<span class="badge bg-primary rounded-pill">Started @entry.StartTime.ToLongTimeString()</span>
|
||||
</div>
|
||||
<Progress jobStatus="entry" />
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
</section>
|
||||
|
||||
@if (completedEntries == null)
|
||||
{
|
||||
<h4>Completed</h4>
|
||||
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Loading Job Status..</li>
|
||||
</ul>
|
||||
}
|
||||
else if (completedEntries.Count == 0)
|
||||
{
|
||||
<h4>Completed</h4>
|
||||
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">No jobs currently executing</li>
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h4>Completed (@completedEntries.Count)</h4>
|
||||
|
||||
<ul class="list-group">
|
||||
@foreach (var entry in completedEntries)
|
||||
{
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<div class="ms-2 me-auto w-100">
|
||||
<div class="fw-bold">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-circle text-success" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" />
|
||||
</svg>
|
||||
<span>@entry.Name</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Execution Time: @((int)entry.ExecutionTime.TotalSeconds)s</li>
|
||||
<li>Started: @entry.StartTime.ToLongTimeString()</li>
|
||||
<li>Completed: @entry.CompleteTime.ToLongTimeString()</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="badge bg-primary rounded-pill">Completed @entry.CompleteTime.ToLongTimeString()</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
private DateTime today;
|
||||
private List<StatusItem>? executingEntries;
|
||||
private List<StatusItem>? completedEntries;
|
||||
|
||||
protected async override void OnInitialized()
|
||||
{
|
||||
DashboardResponse data = await Api.GetData();
|
||||
UpdateData(data);
|
||||
|
||||
Api.DataUpdated += Api_DataUpdated;
|
||||
}
|
||||
|
||||
protected void Api_DataUpdated(object sender, DashboardResponse e)
|
||||
{
|
||||
UpdateData(e);
|
||||
}
|
||||
|
||||
protected void UpdateData(DashboardResponse response)
|
||||
{
|
||||
today = response.heartbeat;
|
||||
|
||||
executingEntries = response.jobStatus.
|
||||
Where(s => s.ProgressTotal > 0).
|
||||
Where(s => s.Executing).
|
||||
ToList();
|
||||
|
||||
completedEntries = response.jobStatus.
|
||||
Where(s => !s.Executing).
|
||||
Where(s => s.StartTime.AddMinutes(1) > DateTime.Now).
|
||||
ToList();
|
||||
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ builder.Services.AddServerSideBlazor();
|
||||
builder.Services.AddTransient<LoggingService>();
|
||||
builder.Services.AddTransient<DatabaseService>();
|
||||
builder.Services.AddSingleton<WcfService>();
|
||||
builder.Services.AddTransient<ApiService>();
|
||||
builder.Services.AddTransient<DashboardService>();
|
||||
builder.Services.AddTransient<JobService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
</svg> Jobs
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="status">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-speedometer" viewBox="0 0 16 16">
|
||||
<path d="M8 2a.5.5 0 0 1 .5.5V4a.5.5 0 0 1-1 0V2.5A.5.5 0 0 1 8 2zM3.732 3.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 8a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 8zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 7.31A.91.91 0 1 0 8.85 8.569l3.434-4.297a.389.389 0 0 0-.029-.518z" />
|
||||
<path fill-rule="evenodd" d="M6.664 15.889A8 8 0 1 1 9.336.11a8 8 0 0 1-2.672 15.78zm-4.665-4.283A11.945 11.945 0 0 1 8 10c2.186 0 4.236.585 6.001 1.606a7 7 0 1 0-12.002 0z" />
|
||||
</svg> Status
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="history">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clock-history" viewBox="0 0 16 16">
|
||||
|
||||
Reference in New Issue
Block a user