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

View File

@ -13,6 +13,7 @@ Namespace Scheduler.Jobs
Friend State As State
Friend Id As Integer
Friend ExecutionId As String
Friend Name As String
Friend ResultItems As New List(Of HistoryItem.HistoryStep)
@ -30,6 +31,7 @@ Namespace Scheduler.Jobs
Windream = oJobData.Item(Constants.Scheduler.JOB_CONFIG_WINDREAM)
Logger = LogConfig.GetLogger()
ExecutionId = Guid.NewGuid.ToString()
Id = Integer.Parse(oArgs.Item("Id"))
Name = oArgs.Item("Name")

View File

@ -39,15 +39,26 @@ Public Class State
LoadData()
End Sub
Private Sub LoadData()
Public Sub ReloadTypes()
_JobTypes = GetJobTypes()
_ObjectTypes = GetObjectTypes()
End Sub
Public Sub ReloadJobs()
_JobDefinitions = GetJobDefinitions(_JobTypes)
End Sub
Public Sub ReloadProfiles()
_ProfileDefintions.ImportProfileSteps = GetImportProfileSteps()
_ProfileDefintions.ImportProfiles = GetImportProfiles()
End Sub
Private Sub LoadData()
ReloadTypes()
ReloadJobs()
ReloadProfiles()
End Sub
Private Function GetObjectTypes() As List(Of ObjectType)
Dim oObjectTypes As New List(Of ObjectType)
Try

View File

@ -16,6 +16,8 @@ Namespace WCF
Public Shared Database As MSSQLServer
Public Shared Scheduler As JobScheduler
Private ReadOnly Logger As Logger
''' <summary>
''' See: https://stackoverflow.com/questions/42327988/addserviceendpoint-throws-key-is-null
''' </summary>
@ -33,33 +35,41 @@ Namespace WCF
})
End Sub
Public Sub New()
Logger = LogConfig.GetLogger()
End Sub
Public Function GetHeartbeat() As Date Implements IJobRunner.GetHeartbeat
Return Now
End Function
Public Function GetJobHistory() As GetJobStatus.GetJobStatusResponse Implements IJobRunner.GetJobStatus
Logger.Info("Calling Method [GetJobHistory]")
Dim oMethod As New GetJobStatus.GetJobStatusMethod(LogConfig, Database, State, Scheduler)
Return oMethod.Run()
End Function
Public Function GetJobConfig() As GetJobConfig.GetJobConfigResponse Implements IJobRunner.GetJobConfig
Logger.Info("Calling Method [GetJobConfig]")
Dim oMethod As New GetJobConfig.GetJobConfigMethod(LogConfig, Database, State)
Return oMethod.Run()
End Function
Public Function UpdateJob(pData As UpdateJob.UpdateJobRequest) As UpdateJob.UpdateJobResponse Implements IJobRunner.UpdateJob
Logger.Info("Calling Method [UpdateJob]")
Dim oMethod As New UpdateJob.UpdateJobMethod(LogConfig, Database, State, Scheduler)
Return oMethod.Run(pData)
End Function
Public Function UpdateProfile(pData As UpdateProfile.UpdateProfileRequest) As UpdateProfile.UpdateProfileResponse Implements IJobRunner.UpdateProfile
Logger.Info("Calling Method [UpdateProfile]")
Dim oMethod As New UpdateProfile.UpdateProfileMethod(LogConfig, Database, State, Scheduler)
Return oMethod.Run(pData)
End Function
Public Function RunJob(pData As RunJob.RunJobRequest) As RunJob.RunJobResponse Implements IJobRunner.RunJob
'Dim oMethod As New RunJob.RunJobMethod(LogConfig, Database, State, Scheduler)
'Return oMethod.Run(pData)
Dim oMethod As New RunJob.RunJobMethod(LogConfig, Database, State, Scheduler)
Return oMethod.Run(pData)
End Function
End Class

View File

@ -14,10 +14,10 @@ Public Class RunJob
Scheduler = pScheduler
End Sub
Public Async Function Run(pData As RunJobRequest) As Task(Of RunJobResponse)
Public Function Run(pData As RunJobRequest) As RunJobResponse
' This is calling the async function ScheduleJob synchronous, so that we can return a value to the caller
' This means that the job might or might not be scheduled when this method returns.
Task.Run(Function() Scheduler.ScheduleJob(pData.JobId))
Scheduler.ScheduleJob(pData.JobId)
Return New RunJobResponse()
End Function