16-12-2022

This commit is contained in:
Jonathan Jenne
2022-12-16 15:59:26 +01:00
parent 63edd9e542
commit 45f8dd2aad
32 changed files with 431 additions and 276 deletions

View File

@@ -7,5 +7,12 @@ namespace ECM.JobRunner.Web.Data
public DateTime heartbeat = DateTime.MinValue;
public List<HistoryItem> jobHistory = new();
public List<StatusItem> jobStatus = new();
public List<HistoryItem> GetHistoryForLastMinutes(int pMinutes)
{
return jobHistory.
Where(h => (DateTime.Now - h.CreatedAt) < new TimeSpan(0, pMinutes, 0)).
ToList();
}
}
}

View File

@@ -9,53 +9,39 @@ namespace ECM.JobRunner.Web.Data
private readonly Common.JobRunnerReference.IEDMIServiceChannel channel;
private Logger logger;
private System.Timers.Timer pollingTimer = new();
private readonly System.Timers.Timer pollingTimer = new();
public event EventHandler<DashboardResponse> DataUpdated;
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);
}
DataUpdated?.Invoke(this, e);
}
private async void PollingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs 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()
jobHistory = jobHistory.OrderByDescending(e => e.CreatedAt).ToList(),
jobStatus = jobStatus.OrderByDescending(e => e.StartTime).ToList()
};
}
private async Task<DateTime> GetHeartbeat()
{
return await channel.GetHeartbeatAsync();
}
private async Task<List<Common.JobRunnerReference.HistoryItem>> GetHistoryItems()
{
try

View File

@@ -0,0 +1,40 @@
using DigitalData.Modules.Logging;
using ECM.JobRunner.Common.JobRunnerReference;
namespace ECM.JobRunner.Web.Data
{
public class HelperService
{
private readonly Logger logger;
private readonly IEDMIServiceChannel channel;
public HelperService(LoggingService Logging, WcfService Wcf)
{
logger = Logging.LogConfig.GetLogger();
channel = Wcf.Channel;
}
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;
}
}
}
}

View File

@@ -0,0 +1,93 @@
using DigitalData.Modules.Logging;
using ECM.JobRunner.Common.JobRunnerReference;
namespace ECM.JobRunner.Web.Data
{
public class ImportProfileService
{
private readonly Logger logger;
private readonly IEDMIServiceChannel channel;
public ImportProfileService(LoggingService Logging, WcfService Wcf)
{
logger = Logging.LogConfig.GetLogger();
channel = Wcf.Channel;
}
public async Task<List<ImportProfile>> GetProfiles()
{
try
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
var profiles = resp.ProfileDefinitions.ImportProfiles.ToList();
return profiles;
}
catch (Exception ex)
{
logger.Error(ex);
return new();
}
}
public async Task<ImportProfile?> GetProfile(int pProfileId)
{
try
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return null;
if (resp.OK == false) return null;
var jobs = resp.JobDefinitions.ToList();
var profiles = resp.ProfileDefinitions.ImportProfiles.ToList();
return profiles.
Where(p => p.Id == pProfileId).
SingleOrDefault();
}
catch (Exception ex)
{
logger.Error(ex);
return null;
}
}
public async Task<bool> CreateProfile(ImportProfile profile) =>
await DoUpdateProfile(profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction.Create);
public async Task<bool> UpdateProfile(ImportProfile profile) =>
await DoUpdateProfile(profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction.Update);
public async Task<bool> DeleteProfile(ImportProfile profile) =>
await DoUpdateProfile(profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction.Delete);
private async Task<bool> DoUpdateProfile(ImportProfile profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction action)
{
try
{
var req = new UpdateProfileUpdateProfileRequest()
{
ImportProfile = profile,
Action = action
};
var resp = await channel.UpdateProfileAsync(req);
if (resp == null) return false;
if (resp.OK == false) return false;
return true;
}
catch (Exception ex)
{
logger.Error(ex);
return false;
}
}
}
}

View File

@@ -1,67 +0,0 @@
using DigitalData.Modules.Logging;
using ECM.JobRunner.Common.JobRunnerReference;
namespace ECM.JobRunner.Web.Data
{
public class ImportService
{
private readonly Logger logger;
private readonly IEDMIServiceChannel channel;
public ImportService(LoggingService Logging, WcfService Wcf)
{
logger = Logging.LogConfig.GetLogger();
channel = Wcf.Channel;
}
public async Task<List<ImportProfile>> GetProfiles()
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
var profiles = resp.ProfileDefinitions.ImportProfiles.ToList();
return profiles;
}
public async Task<ImportProfile?> GetProfile(int pProfileId)
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
var jobs = resp.JobDefinitions.ToList();
var profiles = resp.ProfileDefinitions.ImportProfiles.ToList();
return profiles.
Where(p => p.Id == pProfileId).
SingleOrDefault();
}
public async Task<bool> CreateProfile(ImportProfile profile) =>
await DoUpdateProfile(profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction.Create);
public async Task<bool> UpdateProfile(ImportProfile profile) =>
await DoUpdateProfile(profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction.Update);
public async Task<bool> DeleteProfile(ImportProfile profile) =>
await DoUpdateProfile(profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction.Delete);
private async Task<bool> DoUpdateProfile(ImportProfile profile, UpdateProfileUpdateProfileRequest.UpdateProfileAction action)
{
var req = new UpdateProfileUpdateProfileRequest()
{
ImportProfile = profile,
Action = action
};
var resp = await channel.UpdateProfileAsync(req);
if (resp == null) return false;
if (resp.OK == false) return false;
return true;
}
}
}

View File

@@ -1,131 +0,0 @@
using DigitalData.Modules.Logging;
using ECM.JobRunner.Common.JobRunnerReference;
namespace ECM.JobRunner.Web.Data
{
public class JobService
{
private readonly Logger logger;
private readonly IEDMIServiceChannel channel;
public JobService(LoggingService Logging, WcfService Wcf)
{
logger = Logging.LogConfig.GetLogger();
channel = Wcf.Channel;
}
public async Task<List<JobDefinition>> GetJobs()
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
return resp.JobDefinitions.ToList();
}
public async Task<List<ObjectType>> GetObjectTypes()
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
return resp.WindreamObjectTypes.ToList();
}
public async Task<List<JobType>> GetJobTypes()
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
return resp.JobTypes.ToList();
}
public async Task<JobDefinition?> GetJob(int pJobId)
{
var resp = await channel.GetJobConfigAsync();
if (resp == null) return null;
if (resp.OK == false) return null;
return resp.JobDefinitions.
Where(j => j.Id == pJobId).
FirstOrDefault();
}
public async Task<bool> UpdateJob(JobDefinition job)
{
var resp = await channel.UpdateJobAsync(new UpdateJobUpdateJobRequest {
Job = job,
Action = UpdateJobUpdateJobRequest.UpdateJobAction.Update
});
if (resp == null) return false;
if (resp.OK == false) return false;
return true;
}
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;
}
}
}
}

View File

@@ -10,7 +10,8 @@ namespace ECM.JobRunner.Web.Data
{
LogConfig = new LogConfig(LogConfig.PathType.CustomPath, Config["Config:LogPath"], null, "Digital Data", "ECM.JobRunner.Web")
{
Debug = bool.Parse(Config["Config:LogDebug"])
Debug = bool.Parse(Config["Config:LogDebug"]),
EnableJsonLog = bool.Parse(Config["Config:LogJson"])
};
}
}

View File

@@ -1,22 +1,77 @@
using DigitalData.Modules.Messaging.WCF;
using System.Net;
using ECM.JobRunner.Common.JobRunnerReference;
using System.Timers;
namespace ECM.JobRunner.Web.Data
{
public class WcfService
{
private readonly Channel<Common.JobRunnerReference.IEDMIServiceChannel> channelManager;
private ServerAddress address;
private readonly Channel<IEDMIServiceChannel> _channelManager;
private ServerAddress _address;
private bool _connected = false;
private System.Timers.Timer heartbeatTimer = new();
public readonly Common.JobRunnerReference.IEDMIServiceChannel Channel;
private IEDMIServiceChannel _channel;
public IEDMIServiceChannel Channel { get { return _channel; } }
public event EventHandler<bool>? ConnectedChanged;
public bool Connected { get { return _connected; } }
public WcfService(LoggingService Logging)
{
address.Host = "172.24.12.39";
address.Port = 9001;
_address.Host = "172.24.12.39";
_address.Port = 9001;
channelManager = new Channel<Common.JobRunnerReference.IEDMIServiceChannel>(Logging.LogConfig, address, "JobRunner");
Channel = channelManager.GetChannel();
_channelManager = new Channel<IEDMIServiceChannel>(Logging.LogConfig, _address, "JobRunner");
_channel = _channelManager.GetChannel();
_connected = true;
heartbeatTimer.Elapsed += HeartbeatTimer_Elapsed;
heartbeatTimer.Interval = 1000;
heartbeatTimer.Start();
}
private void CallConnectedChanged(bool pConnected)
{
if (ConnectedChanged != null && pConnected != _connected)
{
_connected = pConnected;
ConnectedChanged(this, pConnected);
}
}
private async void HeartbeatTimer_Elapsed(object? sender, ElapsedEventArgs e)
{
try
{
await _channel.GetHeartbeatAsync();
CallConnectedChanged(true);
}
catch (Exception)
{
CallConnectedChanged(false);
}
}
public async Task<List<ObjectType>> GetObjectTypes()
{
var resp = await _channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
return resp.WindreamObjectTypes.ToList();
}
public async Task<List<JobType>> GetJobTypes()
{
var resp = await _channel.GetJobConfigAsync();
if (resp == null) return new();
if (resp.OK == false) return new();
return resp.JobTypes.ToList();
}
}
}

View File

@@ -25,7 +25,7 @@ else
{
<ul class="list-group mb-3">
<li class="list-group-item bg-light bg-gradient d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="me-auto">
<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" />

View File

@@ -3,7 +3,7 @@
@using ECM.JobRunner.Web.Data;
@using ECM.JobRunner.Web.Pages.ImportStep;
@inject NavigationManager Navigation;
@inject ImportService Profile;
@inject ImportProfileService Profile;
<PageTitle>Job bearbeiten</PageTitle>

View File

@@ -2,7 +2,7 @@
@using ECM.JobRunner.Common.JobRunnerReference;
@using ECM.JobRunner.Web.Data;
@inject NavigationManager Navigation;
@inject ImportService Import;
@inject ImportProfileService Import;
<PageTitle>Job erstellen</PageTitle>

View File

@@ -1,7 +1,8 @@
@using ECM.JobRunner.Common.JobRunnerReference;
@using ECM.JobRunner.Web.Data;
@inject ImportService Import;
@inject JobService Jobs;
@inject ImportProfileService Import;
@inject HelperService Jobs;
@inject WcfService Service;
@if (profile == null)
{
@@ -196,8 +197,8 @@ else
protected override async Task OnInitializedAsync()
{
jobTypes = await Jobs.GetJobTypes();
objectTypes = await Jobs.GetObjectTypes();
jobTypes = await Service.GetJobTypes();
objectTypes = await Service.GetObjectTypes();
if (ProfileId == Constants.ENTITY_ID_NEW)
{

View File

@@ -1,8 +1,8 @@
@page "/profiles/import"
@using ECM.JobRunner.Common.JobRunnerReference;
@using ECM.JobRunner.Web.Data;
@inject JobService Jobs
@inject ImportService Import;
@inject HelperService Jobs
@inject ImportProfileService Import;
<PageTitle>Import Profiles</PageTitle>
@@ -34,7 +34,7 @@ else
@foreach (var profile in filteredProfiles)
{
<a href="/profiles/import/@profile.Id" class="list-group-item list-group-item-action d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="me-auto">
<div class="fw-bold">
<span>
@if (profile.Active)

View File

@@ -4,8 +4,8 @@
@inject NavigationManager Navigation;
@inject IJSRuntime JsRuntime;
@inject JobService Jobs;
@inject ImportService Import;
@inject HelperService Helper;
@inject ImportProfileService Import;
<PageTitle>Profile</PageTitle>
@@ -39,7 +39,7 @@ else
<ul class="list-group mb-3">
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="me-auto">
<div class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z" />
@@ -50,7 +50,7 @@ else
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="me-auto">
<div class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-in-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6 3.5a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-2a.5.5 0 0 0-1 0v2A1.5 1.5 0 0 0 6.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-8A1.5 1.5 0 0 0 5 3.5v2a.5.5 0 0 0 1 0v-2z" />
@@ -61,7 +61,7 @@ else
</div>
</li>
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="me-auto">
<div class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hourglass" viewBox="0 0 16 16">
<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" />
@@ -111,7 +111,7 @@ else
if (profile != null)
{
nextExecution = Jobs.GetNextExecutionTime(profile.Job.CronSchedule);
nextExecution = Helper.GetNextExecutionTime(profile.Job.CronSchedule);
StateHasChanged();
}
}

View File

@@ -2,7 +2,7 @@
@using ECM.JobRunner.Common.JobRunnerReference;
@using ECM.JobRunner.Web.Data;
@inject NavigationManager Navigation;
@inject ImportService Profile;
@inject ImportProfileService Profile;
<PageTitle>Schritt bearbeiten</PageTitle>
@@ -21,9 +21,12 @@
private async void OnFormSubmit(EditContext ctx)
{
ImportProfile profile = await Profile.GetProfile(ProfileId);
ImportProfile? profile = await Profile.GetProfile(ProfileId);
ImportProfileStep step = (ImportProfileStep)ctx.Model;
if (profile == null)
return;
// TODO: This is ugly and manual and needs to be abstracted.
var index = profile.Steps.ToList().FindIndex(s => s.Id == StepId);
profile.Steps[index] = step;

View File

@@ -2,11 +2,11 @@
@using ECM.JobRunner.Common.JobRunnerReference;
@using ECM.JobRunner.Web.Data;
@inject NavigationManager Navigation;
@inject ImportService Profile;
@inject ImportProfileService Profile;
<PageTitle>Schritt bearbeiten</PageTitle>
<PageTitle>Neuen Schritt erstellen</PageTitle>
<h3>Schritt bearbeiten</h3>
<h3>Neuen Schritt erstellen</h3>
<StepForm ProfileId="ProfileId" StepId="-1" OnValidSubmit="OnFormSubmit" />
@@ -21,9 +21,12 @@
private async void OnFormSubmit(EditContext ctx)
{
ImportProfile profile = await Profile.GetProfile(ProfileId);
ImportProfile? profile = await Profile.GetProfile(ProfileId);
ImportProfileStep step = (ImportProfileStep)ctx.Model;
if (profile == null)
return;
// TODO: This is ugly and manual and needs to be abstracted.
var steps = profile.Steps.ToList();
steps.Add(step);

View File

@@ -4,8 +4,8 @@
@inject NavigationManager Navigation;
@inject IJSRuntime JsRuntime;
@inject JobService Jobs;
@inject ImportService Import;
@inject HelperService Jobs;
@inject ImportProfileService Import;
<PageTitle>Profilschritt</PageTitle>
@@ -143,8 +143,6 @@ else
private ImportProfile? profile;
private ImportProfileStep? step;
private DateTime nextExecution;
protected async override void OnInitialized()
{
profile = await Import.GetProfile(ProfileId);

View File

@@ -1,7 +1,7 @@
@using ECM.JobRunner.Common.JobRunnerReference;
@using ECM.JobRunner.Web.Data;
@inject ImportService Import;
@inject JobService Jobs;
@inject ImportProfileService Import;
@inject HelperService Jobs;
@if (step == null || profile == null)
{
@@ -41,7 +41,7 @@ else
}
</select>
<div id="stepIndexHelp" class="form-text">Der Windream Index, in den das Ergebnis des Schritte geschrieben wird</div>
</div>
</div>
<div class="mb-3">
<label for="stepScope" class="form-label">
@@ -166,7 +166,12 @@ else
else
{
step = profile.Steps.Where(s => s.Id == StepId).SingleOrDefault();
SetMethodArgs(step.Method);
if (step != null)
{
SetMethodArgs(step.Method);
}
}
StateHasChanged();
}
@@ -217,8 +222,8 @@ else
class StepArgument
{
public string name;
public string helpText;
public string name = "";
public string helpText = "";
}
}

View File

@@ -4,7 +4,7 @@
@using ECM.JobRunner.Web.Data
@using ECM.JobRunner.Web.Pages.ImportStep
@inject ImportService Import
@inject ImportProfileService Import
<h3>Profilschritte</h3>

View File

@@ -4,4 +4,3 @@
<h3>ECM Job Runner</h3>
<p>Welcome to your new app!</p>

View File

@@ -6,7 +6,7 @@
<div class="list-group">
<a href="profiles/import" class="list-group-item list-group-item-action d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<div class="me-auto">
<div class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z" />
@@ -16,7 +16,7 @@
</div>
</a>
<a href="profiles" class="list-group-item list-group-item-action d-flex justify-content-between align-items-start text-muted">
<div class="ms-2 me-auto">
<div class="me-auto">
<div class="fw-bold">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z" />

View File

@@ -6,6 +6,58 @@
<PageTitle>Status</PageTitle>
<section class="mb-5">
<div class="row row-cols-1 row-cols-md-3 g-4">
@if (last5MinutesItems != null)
{
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-hourglass-top" viewBox="0 0 16 16">
<path d="M2 14.5a.5.5 0 0 0 .5.5h11a.5.5 0 1 0 0-1h-1v-1a4.5 4.5 0 0 0-2.557-4.06c-.29-.139-.443-.377-.443-.59v-.7c0-.213.154-.451.443-.59A4.5 4.5 0 0 0 12.5 3V2h1a.5.5 0 0 0 0-1h-11a.5.5 0 0 0 0 1h1v1a4.5 4.5 0 0 0 2.557 4.06c.29.139.443.377.443.59v.7c0 .213-.154.451-.443.59A4.5 4.5 0 0 0 3.5 13v1h-1a.5.5 0 0 0-.5.5zm2.5-.5v-1a3.5 3.5 0 0 1 1.989-3.158c.533-.256 1.011-.79 1.011-1.491v-.702s.18.101.5.101.5-.1.5-.1v.7c0 .701.478 1.236 1.011 1.492A3.5 3.5 0 0 1 11.5 13v1h-7z" />
</svg> Last 5 Minutes
</h5>
<h6 class="card-subtitle mb-2 text-muted">@last5MinutesItems.Count jobs executed.</h6>
</div>
</div>
</div>
}
@if (lastHourItems != null)
{
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-hourglass-split" viewBox="0 0 16 16">
<path d="M2.5 15a.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 0-1h11a.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-11zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2h-7zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48V8.35zm1 0v3.17c2.134.181 3 1.48 3 1.48a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351z" />
</svg> Last Hour
</h5>
<h6 class="card-subtitle mb-2 text-muted">@lastHourItems.Count jobs executed.</h6>
</div>
</div>
</div>
}
@if (last12HoursItems != null)
{
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-hourglass-bottom" viewBox="0 0 16 16">
<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.702s.18.149.5.149.5-.15.5-.15v-.7c0-.701.478-1.236 1.011-1.492A3.5 3.5 0 0 0 11.5 3V2h-7z" />
</svg> Last 12 Hours
</h5>
<h6 class="card-subtitle mb-2 text-muted">@last12HoursItems.Count jobs executed.</h6>
</div>
</div>
</div>
}
</div>
</section>
<h3 class="mb-3">Job Status</h3>
<section class="mb-5">
@@ -112,6 +164,10 @@ else
private List<StatusItem>? executingEntries;
private List<StatusItem>? completedEntries;
private List<HistoryItem>? last5MinutesItems;
private List<HistoryItem>? lastHourItems;
private List<HistoryItem>? last12HoursItems;
protected async override void OnInitialized()
{
DashboardResponse data = await Dashboard.GetData();
@@ -139,6 +195,10 @@ else
Where(s => s.StartTime.AddMinutes(10) > DateTime.Now).
ToList();
last5MinutesItems = response.GetHistoryForLastMinutes(5);
lastHourItems = response.GetHistoryForLastMinutes(60);
last12HoursItems = response.GetHistoryForLastMinutes(60 * 12);
InvokeAsync(StateHasChanged);
}
}

View File

@@ -28,5 +28,7 @@
</div>
<script src="_framework/blazor.server.js"></script>
<script src="~/js/echarts.min.js"></script>
<script src="~/js/bootstrap.min.js"></script>
</body>
</html>

View File

@@ -12,8 +12,8 @@ builder.Services.AddTransient<LoggingService>();
builder.Services.AddTransient<DatabaseService>();
builder.Services.AddSingleton<WcfService>();
builder.Services.AddTransient<DashboardService>();
builder.Services.AddTransient<JobService>();
builder.Services.AddTransient<ImportService>();
builder.Services.AddTransient<HelperService>();
builder.Services.AddTransient<ImportProfileService>();
var app = builder.Build();
@@ -23,7 +23,6 @@ if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();

View File

@@ -1,10 +1,12 @@
@inherits LayoutComponentBase
@using ECM.JobRunner.Web.Data;
@inherits LayoutComponentBase
@inject WcfService Wcf
<PageTitle>ECM.JobRunner.Web</PageTitle>
<div class="page">
<div class=@(connected ? "page connected" : "page disconnected")>
<div class="sidebar">
<NavMenu />
<NavMenu connected="connected" />
</div>
<main>
@@ -17,3 +19,19 @@
</article>
</main>
</div>
@code {
private bool connected = true;
protected override Task OnInitializedAsync()
{
Wcf.ConnectedChanged += Wcf_ConnectedChanged;
return Task.FromResult(true);
}
public void Wcf_ConnectedChanged(object? sender, bool status)
{
connected = status;
InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,6 +1,17 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">ECM Job Runner</a>
<a class="navbar-brand" href="">
ECM Job Runner
@if (!connected)
{
<span title="Offline!">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-emoji-dizzy 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="M9.146 5.146a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708.708l-.647.646.647.646a.5.5 0 0 1-.708.708l-.646-.647-.646.647a.5.5 0 1 1-.708-.708l.647-.646-.647-.646a.5.5 0 0 1 0-.708zm-5 0a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 1 1 .708.708l-.647.646.647.646a.5.5 0 1 1-.708.708L5.5 7.207l-.646.647a.5.5 0 1 1-.708-.708l.647-.646-.647-.646a.5.5 0 0 1 0-.708zM10 11a2 2 0 1 1-4 0 2 2 0 0 1 4 0z" />
</svg>
</span>
}
</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
@@ -46,6 +57,9 @@
</div>
@code {
[Parameter]
public bool connected { get; set; }
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

View File

@@ -9,6 +9,7 @@
"Config": {
"ConnectionString": "",
"LogPath": "E:\\ECM\\JobRunner",
"LogDebug": true
"LogDebug": true,
"LogJson": true
}
}

File diff suppressed because one or more lines are too long