20-12-2022

This commit is contained in:
Jonathan Jenne 2022-12-20 15:29:29 +01:00
parent 3e2c4a9ab0
commit 1e925242bc
12 changed files with 291 additions and 179 deletions

View File

@ -25,6 +25,7 @@
<xs:element minOccurs="0" name="SuccessMessage" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="SuccessMessage" nillable="true" type="xs:string" />
<xs:element minOccurs="0" name="Successful" type="xs:boolean" /> <xs:element minOccurs="0" name="Successful" type="xs:boolean" />
<xs:element minOccurs="0" name="UpdateTime" type="xs:dateTime" /> <xs:element minOccurs="0" name="UpdateTime" type="xs:dateTime" />
<xs:element minOccurs="0" name="Waiting" type="xs:boolean" />
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:element name="StatusItem" nillable="true" type="tns:StatusItem" /> <xs:element name="StatusItem" nillable="true" type="tns:StatusItem" />

View File

@ -322,6 +322,9 @@ Namespace JobRunnerReference
<System.Runtime.Serialization.OptionalFieldAttribute()> _ <System.Runtime.Serialization.OptionalFieldAttribute()> _
Private UpdateTimeField As Date Private UpdateTimeField As Date
<System.Runtime.Serialization.OptionalFieldAttribute()> _
Private WaitingField As Boolean
<Global.System.ComponentModel.BrowsableAttribute(false)> _ <Global.System.ComponentModel.BrowsableAttribute(false)> _
Public Property ExtensionData() As System.Runtime.Serialization.ExtensionDataObject Implements System.Runtime.Serialization.IExtensibleDataObject.ExtensionData Public Property ExtensionData() As System.Runtime.Serialization.ExtensionDataObject Implements System.Runtime.Serialization.IExtensibleDataObject.ExtensionData
Get Get
@ -527,6 +530,19 @@ Namespace JobRunnerReference
End Set End Set
End Property End Property
<System.Runtime.Serialization.DataMemberAttribute()> _
Public Property Waiting() As Boolean
Get
Return Me.WaitingField
End Get
Set
If (Me.WaitingField.Equals(value) <> true) Then
Me.WaitingField = value
Me.RaisePropertyChanged("Waiting")
End If
End Set
End Property
Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Protected Sub RaisePropertyChanged(ByVal propertyName As String) Protected Sub RaisePropertyChanged(ByVal propertyName As String)

View File

@ -9,35 +9,66 @@ Public Class StatusItem
Public Const STEP_WARNING = "WARNING" Public Const STEP_WARNING = "WARNING"
Public Const STEP_ERROR = "ERROR" Public Const STEP_ERROR = "ERROR"
' Unique Job Run Id, GUID ''' <summary>
''' Is the job currently executing
''' </summary>
Public Executing As Boolean = False
''' <summary>
''' Did the job complete without an error
''' </summary>
Public Successful As Boolean = False
''' <summary>
''' Did the job do some work or is/was just waiting for some input
''' </summary>
Public Waiting As Boolean = False
''' <summary>
''' Unique Job Run Id, GUID
''' </summary>
Public Id As String Public Id As String
' Job Id, corresponds to Job Schedule in DB ''' <summary>
''' Job Id, corresponds to Job Schedule in DB
''' </summary>
Public JobId As String Public JobId As String
' Job Name, corresponds to Job Schedule Key from Quartz ''' <summary>
''' Job Name, corresponds to Job Schedule Key from Quartz
''' </summary>
Public Name As String = "Unnamed" Public Name As String = "Unnamed"
Public Steps As List(Of HistoryStep) Public Steps As List(Of HistoryStep)
' Runtime Variables ' Runtime Variables
' Progress Counter ''' <summary>
''' Progress Counter
''' </summary>
Public ProgressCurrent As Integer = 0 Public ProgressCurrent As Integer = 0
' Total Progress ''' <summary>
''' Total Progress
''' </summary>
Public ProgressTotal As Integer = 0 Public ProgressTotal As Integer = 0
' Flag to determin if the job is currently executing/working
Public Executing As Boolean = False ''' <summary>
' Creation time of job, set by Constructor ''' Creation time of job, set by Constructor
''' </summary>
Public CreationTime As Date = Date.Now Public CreationTime As Date = Date.Now
' Start time of execution, set by JobStatus.Start ''' <summary>
''' Start time of execution, set by JobStatus.Start
''' </summary>
Public StartTime As Date Public StartTime As Date
' End time of execution, set by JobStatus.Complete ''' <summary>
''' End time of execution, set by JobStatus.Complete
''' </summary>
Public CompleteTime As Date Public CompleteTime As Date
' Time of last Progress Update ''' <summary>
''' Time of last Progress Update
''' </summary>
Public UpdateTime As Date Public UpdateTime As Date
' Total execution time, calculated by JobStatus.Complete ''' <summary>
''' Total execution time, calculated by JobStatus.Complete
''' </summary>
Public ExecutionTime As TimeSpan Public ExecutionTime As TimeSpan
' Completion/Failure Messages ' Completion/Failure Messages
Public Successful As Boolean = False
Public SuccessMessage As String = "" Public SuccessMessage As String = ""
Public FailureMessage As String = "" Public FailureMessage As String = ""

View File

@ -5,14 +5,32 @@ namespace ECM.JobRunner.Web.Data
public class DashboardResponse public class DashboardResponse
{ {
public DateTime heartbeat = DateTime.MinValue; public DateTime heartbeat = DateTime.MinValue;
public List<HistoryItem> jobHistory = new();
public List<StatusItem> jobStatus = new(); public List<StatusItem> jobStatus = new();
public List<HistoryItem> GetHistoryForLastMinutes(int pMinutes) public class JobHistory
{ {
return jobHistory. public List<StatusItem> items;
Where(h => (DateTime.Now - h.CreatedAt) < new TimeSpan(0, pMinutes, 0)). public int total;
public int success;
public int failed;
public int waiting;
}
public JobHistory GetHistoryForLastMinutes(int pMinutes)
{
var items = jobStatus.
Where(s => (DateTime.Now - s.CompleteTime) < new TimeSpan(0, pMinutes, 0)).
Where(s => s.Executing == false).
ToList(); ToList();
return new JobHistory()
{
items = items,
total = items.Count,
success = items.Where(i => i.Successful && i.Waiting == false).Count(),
failed = items.Where(i => i.Successful == false).Count(),
waiting = items.Where(i => i.Successful && i.Waiting == true).Count()
};
} }
} }
} }

View File

@ -17,7 +17,9 @@ namespace ECM.JobRunner.Web.Data
{ {
logger = Logging.LogConfig.GetLogger(); logger = Logging.LogConfig.GetLogger();
channel = Wcf.Channel; channel = Wcf.Channel;
pollingTimer.Interval = 1000;
pollingTimer.Elapsed += PollingTimer_Elapsed; pollingTimer.Elapsed += PollingTimer_Elapsed;
pollingTimer.Enabled = true;
} }
protected virtual void OnDataUpdated(DashboardResponse e) protected virtual void OnDataUpdated(DashboardResponse e)
@ -32,39 +34,14 @@ namespace ECM.JobRunner.Web.Data
public async Task<DashboardResponse> GetData() public async Task<DashboardResponse> GetData()
{ {
List<Common.JobRunnerReference.HistoryItem> jobHistory = await GetHistoryItems();
List<Common.JobRunnerReference.StatusItem> jobStatus = await GetStatusItems(); List<Common.JobRunnerReference.StatusItem> jobStatus = await GetStatusItems();
return new DashboardResponse() return new DashboardResponse()
{ {
jobHistory = jobHistory.OrderByDescending(e => e.CreatedAt).ToList(), jobStatus = jobStatus.OrderByDescending(e => e.CompleteTime).ToList()
jobStatus = jobStatus.OrderByDescending(e => e.StartTime).ToList()
}; };
} }
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() private async Task<List<Common.JobRunnerReference.StatusItem>> GetStatusItems()
{ {
try try

View File

@ -39,8 +39,4 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Components\Profile\" />
</ItemGroup>
</Project> </Project>

View File

@ -7,58 +7,114 @@
<h3>Job History</h3> <h3>Job History</h3>
@if (historyEntries == null) <div class="container-fluid">
{ <div class="row">
<ul class="list-group"> <div class="col-2">
<li class="list-group-item">Loading Job History..</li> <div class="card">
</ul> <div class="card-body">
} <form>
else if (historyEntries.Count == 0) <legend>Filter</legend>
{
<ul class="list-group"> <div class="mb-3 form-check">
<li class="list-group-item">No Job History yet.</li> <input type="checkbox" class="form-check-input" id="showWaiting" checked="@showWaiting" @oninput="CheckboxChanged">
</ul> <label class="form-check-label" for="exampleCheck1">Waiting</label>
} </div>
else </form>
{
@foreach (var entry in historyEntries)
{
<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="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" />
<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.JobName</span>
</div>
</div> </div>
</li> </div>
@foreach (var step in entry.Steps) </div>
<div class="col-10">
@if (filteredEntries == null)
{ {
<li class="list-group-item"> <ul class="list-group">
<strong>@step.Created.ToString("HH:mm")</strong> @step.Message <li class="list-group-item">Loading Job History..</li>
</li> </ul>
} }
<li class="list-group-item"> else if (filteredEntries.Count == 0)
<strong>@entry.CreatedAt.ToString("HH:mm")</strong> @entry.Message
</li>
@if (entry.Successful == false)
{ {
<li class="list-group-item text-danger">Job Failed</li> <ul class="list-group">
<li class="list-group-item">No Job History yet.</li>
</ul>
} }
else else
{ {
<li class="list-group-item text-success">Job Succeeded</li> @foreach (var entry in filteredEntries)
{
<ul class="list-group mb-3">
<li class="list-group-item list-group-item-secondary d-flex justify-content-between align-items-start">
<div class="me-auto">
<div class="fw-bold">
<span>@entry.Name</span>
</div>
</div>
<div>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar-check" viewBox="0 0 16 16">
<path d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z" />
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z" />
</svg> @entry.CompleteTime.ToString("HH:mm:ss")
</div>
</li>
@foreach (var step in entry.Steps)
{
<li class="list-group-item">
<strong>@step.Created.ToString("HH:mm:ss")</strong> @step.Message
</li>
}
@if (entry.Successful == false)
{
<li class="list-group-item list-group-item-danger d-flex justify-content-between align-items-start">
<span>@entry.FailureMessage</span>
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stopwatch" viewBox="0 0 16 16">
<path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z" />
<path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z" />
</svg> @entry.ExecutionTime.ToString("mm':'ss")
</span>
</li>
}
@if (entry.Successful == true && entry.Waiting == false)
{
<li class="list-group-item list-group-item-success d-flex justify-content-between align-items-start">
<span>@entry.SuccessMessage</span>
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stopwatch" viewBox="0 0 16 16">
<path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z" />
<path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z" />
</svg> @entry.ExecutionTime.ToString("mm':'ss")
</span>
</li>
}
@if (entry.Successful == true && entry.Waiting == true)
{
<li class="list-group-item list-group-item-info d-flex justify-content-between align-items-start">
<span>@entry.SuccessMessage</span>
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stopwatch" viewBox="0 0 16 16">
<path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z" />
<path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z" />
</svg> @entry.ExecutionTime.ToString("mm':'ss")
</span>
</li>
}
</ul>
}
} }
</ul> </div>
} </div>
} </div>
@code { @code {
private DateTime today; private DateTime today;
private List<HistoryItem>? historyEntries; private List<StatusItem>? statusEntries;
private List<StatusItem>? filteredEntries;
private bool showSuccessful = true;
private bool showFailed = true;
private bool showWaiting = true;
protected async override void OnInitialized() protected async override void OnInitialized()
{ {
@ -68,7 +124,16 @@ else
Api.DataUpdated += Api_DataUpdated; Api.DataUpdated += Api_DataUpdated;
} }
protected void Api_DataUpdated(object sender, DashboardResponse e) private void CheckboxChanged(ChangeEventArgs e)
{
showWaiting = (bool)e.Value;
UpdateEntries(statusEntries);
InvokeAsync(StateHasChanged);
}
protected void Api_DataUpdated(object? sender, DashboardResponse e)
{ {
UpdateData(e); UpdateData(e);
} }
@ -76,8 +141,27 @@ else
protected void UpdateData(DashboardResponse response) protected void UpdateData(DashboardResponse response)
{ {
today = response.heartbeat; today = response.heartbeat;
historyEntries = response.jobHistory; statusEntries = response.jobStatus;
UpdateEntries(response.jobStatus);
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
protected void UpdateEntries(List<StatusItem>? entries)
{
if (entries == null)
{
return;
}
var filtered = entries.
Where(s => s.Executing == false);
if (!showWaiting)
{
filtered = filtered.
Where(e => e.Waiting == false);
}
filteredEntries = filtered.ToList();
}
} }

View File

@ -6,8 +6,7 @@
<PageTitle>Status</PageTitle> <PageTitle>Status</PageTitle>
<section class="mb-4">
<section class="mb-5">
<div class="row row-cols-1 row-cols-md-3 g-4"> <div class="row row-cols-1 row-cols-md-3 g-4">
@if (last5MinutesItems != null) @if (last5MinutesItems != null)
{ {
@ -19,7 +18,12 @@
<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" /> <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 </svg> Last 5 Minutes
</h5> </h5>
<h6 class="card-subtitle mb-2 text-muted">@last5MinutesItems.Count jobs executed.</h6> <h6 class="card-subtitle mb-2">
<span>@last5MinutesItems.total jobs executed</span>,
<span class="text-success">@last5MinutesItems.success Successful</span>,
<span class="text-danger">@last5MinutesItems.failed Failed</span>,
<span class="text-muted">@last5MinutesItems.waiting Waiting</span>
</h6>
</div> </div>
</div> </div>
</div> </div>
@ -34,7 +38,7 @@
<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" /> <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 </svg> Last Hour
</h5> </h5>
<h6 class="card-subtitle mb-2 text-muted">@lastHourItems.Count jobs executed.</h6> <h6 class="card-subtitle mb-2">@lastHourItems.total jobs executed, @lastHourItems.success Successful, @lastHourItems.failed Failed, @lastHourItems.waiting Waiting</h6>
</div> </div>
</div> </div>
</div> </div>
@ -43,14 +47,14 @@
@if (last12HoursItems != null) @if (last12HoursItems != null)
{ {
<div class="col"> <div class="col">
<div class="card"> <div class="card text-bg-success">
<div class="card-body"> <div class="card-body">
<h5 class="card-title"> <h5 class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-hourglass-bottom" viewBox="0 0 16 16"> <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" /> <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 </svg> Last 12 Hours
</h5> </h5>
<h6 class="card-subtitle mb-2 text-muted">@last12HoursItems.Count jobs executed.</h6> <h6 class="card-subtitle mb-2">@last12HoursItems.total jobs executed, @last12HoursItems.success Successful, @last12HoursItems.failed Failed, @last12HoursItems.waiting Waiting</h6>
</div> </div>
</div> </div>
</div> </div>
@ -58,12 +62,9 @@
</div> </div>
</section> </section>
<h3 class="mb-3">Job Status</h3> <section class="mb-3">
<section class="mb-5">
@if (executingEntries == null) @if (executingEntries == null)
{ {
<h4>Executing</h4>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">Loading Job Status..</li> <li class="list-group-item">Loading Job Status..</li>
@ -71,7 +72,6 @@
} }
else if (executingEntries.Count == 0) else if (executingEntries.Count == 0)
{ {
<h4>Executing (0)</h4>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cup-hot" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cup-hot" viewBox="0 0 16 16">
@ -83,8 +83,6 @@
} }
else else
{ {
<h4>Executing (@executingEntries.Count)</h4>
<ul class="list-group"> <ul class="list-group">
@foreach (var entry in executingEntries) @foreach (var entry in executingEntries)
{ {
@ -109,64 +107,14 @@
</section> </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 (0)</h4>
<ul class="list-group">
<li class="list-group-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cup-hot" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M.5 6a.5.5 0 0 0-.488.608l1.652 7.434A2.5 2.5 0 0 0 4.104 16h5.792a2.5 2.5 0 0 0 2.44-1.958l.131-.59a3 3 0 0 0 1.3-5.854l.221-.99A.5.5 0 0 0 13.5 6H.5ZM13 12.5a2.01 2.01 0 0 1-.316-.025l.867-3.898A2.001 2.001 0 0 1 13 12.5ZM2.64 13.825 1.123 7h11.754l-1.517 6.825A1.5 1.5 0 0 1 9.896 15H4.104a1.5 1.5 0 0 1-1.464-1.175Z" />
<path d="m4.4.8-.003.004-.014.019a4.167 4.167 0 0 0-.204.31 2.327 2.327 0 0 0-.141.267c-.026.06-.034.092-.037.103v.004a.593.593 0 0 0 .091.248c.075.133.178.272.308.445l.01.012c.118.158.26.347.37.543.112.2.22.455.22.745 0 .188-.065.368-.119.494a3.31 3.31 0 0 1-.202.388 5.444 5.444 0 0 1-.253.382l-.018.025-.005.008-.002.002A.5.5 0 0 1 3.6 4.2l.003-.004.014-.019a4.149 4.149 0 0 0 .204-.31 2.06 2.06 0 0 0 .141-.267c.026-.06.034-.092.037-.103a.593.593 0 0 0-.09-.252A4.334 4.334 0 0 0 3.6 2.8l-.01-.012a5.099 5.099 0 0 1-.37-.543A1.53 1.53 0 0 1 3 1.5c0-.188.065-.368.119-.494.059-.138.134-.274.202-.388a5.446 5.446 0 0 1 .253-.382l.025-.035A.5.5 0 0 1 4.4.8Zm3 0-.003.004-.014.019a4.167 4.167 0 0 0-.204.31 2.327 2.327 0 0 0-.141.267c-.026.06-.034.092-.037.103v.004a.593.593 0 0 0 .091.248c.075.133.178.272.308.445l.01.012c.118.158.26.347.37.543.112.2.22.455.22.745 0 .188-.065.368-.119.494a3.31 3.31 0 0 1-.202.388 5.444 5.444 0 0 1-.253.382l-.018.025-.005.008-.002.002A.5.5 0 0 1 6.6 4.2l.003-.004.014-.019a4.149 4.149 0 0 0 .204-.31 2.06 2.06 0 0 0 .141-.267c.026-.06.034-.092.037-.103a.593.593 0 0 0-.09-.252A4.334 4.334 0 0 0 6.6 2.8l-.01-.012a5.099 5.099 0 0 1-.37-.543A1.53 1.53 0 0 1 6 1.5c0-.188.065-.368.119-.494.059-.138.134-.274.202-.388a5.446 5.446 0 0 1 .253-.382l.025-.035A.5.5 0 0 1 7.4.8Zm3 0-.003.004-.014.019a4.077 4.077 0 0 0-.204.31 2.337 2.337 0 0 0-.141.267c-.026.06-.034.092-.037.103v.004a.593.593 0 0 0 .091.248c.075.133.178.272.308.445l.01.012c.118.158.26.347.37.543.112.2.22.455.22.745 0 .188-.065.368-.119.494a3.198 3.198 0 0 1-.202.388 5.385 5.385 0 0 1-.252.382l-.019.025-.005.008-.002.002A.5.5 0 0 1 9.6 4.2l.003-.004.014-.019a4.149 4.149 0 0 0 .204-.31 2.06 2.06 0 0 0 .141-.267c.026-.06.034-.092.037-.103a.593.593 0 0 0-.09-.252A4.334 4.334 0 0 0 9.6 2.8l-.01-.012a5.099 5.099 0 0 1-.37-.543A1.53 1.53 0 0 1 9 1.5c0-.188.065-.368.119-.494.059-.138.134-.274.202-.388a5.446 5.446 0 0 1 .253-.382l.025-.035A.5.5 0 0 1 10.4.8Z" />
</svg> No completed jobs yet.
</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-success rounded-pill">Completed @entry.CompleteTime.ToLongTimeString()</span>
</li>
}
</ul>
}
@code { @code {
private DateTime today; private DateTime today;
private List<StatusItem>? executingEntries; private List<StatusItem>? executingEntries;
private List<StatusItem>? completedEntries; private List<StatusItem>? completedEntries;
private List<HistoryItem>? last5MinutesItems; private DashboardResponse.JobHistory? last5MinutesItems;
private List<HistoryItem>? lastHourItems; private DashboardResponse.JobHistory? lastHourItems;
private List<HistoryItem>? last12HoursItems; private DashboardResponse.JobHistory? last12HoursItems;
protected async override void OnInitialized() protected async override void OnInitialized()
{ {
@ -201,4 +149,6 @@ else
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
} }

View File

@ -10,7 +10,8 @@
</div> </div>
<main> <main>
<div class="top-row px-4"> <div class="top-row px-4 d-flex align-content-between justify-content-between">
<strong>Page Title</strong>
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div> </div>

View File

@ -47,16 +47,26 @@ Public Class JobStatus
Logger.Info("Completing Job [{0}] with Error", oStatus.Id) Logger.Info("Completing Job [{0}] with Error", oStatus.Id)
If oStatus IsNot Nothing Then If oStatus IsNot Nothing Then
oStatus.ProgressCurrent = oStatus.ProgressTotal
oStatus.ExecutionTime = pJob.JobRunTime
oStatus.Executing = False
oStatus.CompleteTime = Date.Now
oStatus.FailureMessage = pMessage oStatus.FailureMessage = pMessage
oStatus.Successful = False oStatus.Successful = False
oStatus.Steps = pSteps oStatus.Waiting = False
End If End If
Return oStatus Return DoComplete(pJob, pSteps)
End Function
Public Function CompleteWithWaiting(pJob As Quartz.IJobExecutionContext, pSteps As List(Of StatusItem.HistoryStep), pMessage As String) As StatusItem
Dim oStatus = GetJobStatus(pJob)
Logger.Info("Completing Job [{0}] with Waiting", oStatus.Id)
If oStatus IsNot Nothing Then
oStatus.SuccessMessage = pMessage
oStatus.Successful = True
oStatus.Waiting = True
End If
Return DoComplete(pJob, pSteps)
End Function End Function
Public Function CompleteWithSuccess(pJob As Quartz.IJobExecutionContext, pSteps As List(Of StatusItem.HistoryStep), pMessage As String) As StatusItem Public Function CompleteWithSuccess(pJob As Quartz.IJobExecutionContext, pSteps As List(Of StatusItem.HistoryStep), pMessage As String) As StatusItem
@ -65,23 +75,35 @@ Public Class JobStatus
Logger.Info("Completing Job [{0}] with Success", oStatus.Id) Logger.Info("Completing Job [{0}] with Success", oStatus.Id)
If oStatus IsNot Nothing Then If oStatus IsNot Nothing Then
oStatus.ProgressCurrent = oStatus.ProgressTotal
oStatus.ExecutionTime = pJob.JobRunTime
oStatus.Executing = False
oStatus.CompleteTime = Date.Now
oStatus.SuccessMessage = pMessage oStatus.SuccessMessage = pMessage
oStatus.Successful = False oStatus.Successful = True
oStatus.Steps = pSteps oStatus.Waiting = False
End If End If
Return DoComplete(pJob, pSteps)
End Function
Private Function DoComplete(pJob As Quartz.IJobExecutionContext, pSteps As List(Of StatusItem.HistoryStep))
Dim oStatus = GetJobStatus(pJob)
oStatus.ProgressCurrent = oStatus.ProgressTotal
oStatus.ExecutionTime = pJob.JobRunTime
oStatus.Executing = False
oStatus.CompleteTime = Date.Now
oStatus.Steps = pSteps
Return oStatus Return oStatus
End Function End Function
Private Function GetJobStatus(pJob As Quartz.IJobExecutionContext) As StatusItem Private Function GetJobStatus(pJob As Quartz.IJobExecutionContext) As StatusItem
Dim oJobId = GetJobId(pJob) Dim oJobId As String = GetJobId(pJob)
Logger.Debug("Getting status for job id [{0}]", oJobId)
Dim oExists = Entries.Where(Function(e) e.JobId = oJobId).Any() Dim oExists = Entries.Where(Function(e) e.JobId = oJobId).Any()
Logger.Debug("Job exists: [{0}]", oExists)
If Not oExists Then If Not oExists Then
Logger.Debug("Creating status for job id [{0}]", oJobId)
Entries.Add(New StatusItem With { Entries.Add(New StatusItem With {
.JobId = oJobId, .JobId = oJobId,
.Id = Guid.NewGuid.ToString(), .Id = Guid.NewGuid.ToString(),
@ -89,7 +111,7 @@ Public Class JobStatus
}) })
End If End If
Return Entries.Where(Function(e) e.Id = oJobId).SingleOrDefault() Return Entries.Where(Function(e) e.JobId = oJobId).Single()
End Function End Function
Private Function GetJobId(pJob As Quartz.IJobExecutionContext) As String Private Function GetJobId(pJob As Quartz.IJobExecutionContext) As String

View File

@ -29,11 +29,12 @@ Namespace Scheduler.Jobs
Database = oJobData.Item(Constants.Scheduler.JOB_CONFIG_DATABASE) Database = oJobData.Item(Constants.Scheduler.JOB_CONFIG_DATABASE)
State = oJobData.Item(Constants.Scheduler.JOB_CONFIG_STATE) State = oJobData.Item(Constants.Scheduler.JOB_CONFIG_STATE)
Windream = oJobData.Item(Constants.Scheduler.JOB_CONFIG_WINDREAM) Windream = oJobData.Item(Constants.Scheduler.JOB_CONFIG_WINDREAM)
Logger = LogConfig.GetLogger()
ExecutionId = Guid.NewGuid.ToString() ExecutionId = Guid.NewGuid.ToString()
Id = Integer.Parse(oArgs.Item("Id")) Id = Integer.Parse(oArgs.Item("Id"))
Name = oArgs.Item("Name") Name = oArgs.Item("Name")
Logger = LogConfig.GetLogger(Name)
State.JobStatus.Start(ctx) State.JobStatus.Start(ctx)
Logger.Info("Job [{0}] is starting!", Id) Logger.Info("Job [{0}] is starting!", Id)
@ -83,6 +84,11 @@ Namespace Scheduler.Jobs
Return Task.FromResult(True) Return Task.FromResult(True)
End Function End Function
Public Function CompleteJobWithWaiting(pMessage As String) As Task(Of Boolean)
ctx.Result = State.JobStatus.CompleteWithWaiting(ctx, JobSteps, pMessage)
Return Task.FromResult(True)
End Function
Public Function CompleteJobWithError(pException As Exception) As Task(Of Boolean) Public Function CompleteJobWithError(pException As Exception) As Task(Of Boolean)
ctx.Result = State.JobStatus.CompleteWithError(ctx, JobSteps, pException) ctx.Result = State.JobStatus.CompleteWithError(ctx, JobSteps, pException)

View File

@ -29,7 +29,7 @@ Namespace Scheduler.Jobs
If IO.Directory.Exists(oProfile.SourceFolder) = False Then If IO.Directory.Exists(oProfile.SourceFolder) = False Then
LogError("Source directory [{0}] does not exist!", oProfile.SourceFolder) LogError("Source directory [{0}] does not exist!", oProfile.SourceFolder)
Return Task.FromResult(False) Return CompleteJobWithError(New IO.DirectoryNotFoundException($"Source directory [{oProfile.SourceFolder}] does not exist!"))
End If End If
Dim oRecursive As Boolean = oProfile.IncludeSubfolders Dim oRecursive As Boolean = oProfile.IncludeSubfolders
@ -40,24 +40,24 @@ Namespace Scheduler.Jobs
If oFileNames.Count = 0 Then If oFileNames.Count = 0 Then
Logger.Info("No Files for Profile [{0}]", Name) Logger.Info("No Files for Profile [{0}]", Name)
Return CompleteJob("No files for profile") Return CompleteJobWithWaiting("No files for profile")
End If End If
LogInfo("{0} files found in source directory {1}", oFileNames.Count, oProfile.SourceFolder) LogInfo("{0} files found in source directory {1}", oFileNames.Count.ToString, oProfile.SourceFolder)
' - [ ] Process Rules, build list of files and indexes ' - [ ] Process Rules, build list of files and indexes
' - [x] Process Regex to filter out files ' - [x] Process Regex to filter out files
' - [x] Check time to filter out files ' - [x] Check time to filter out files
' - [ ] (Check if files can be accessed) ' - [ ] (Check if files can be accessed)
' - [ ] Check if backup is needed and backup files ' - [ ] Check if backup is needed and backup files
' - [ ] Import into windream ' - [x] Import into windream
' - [ ] Create original subfolder structure ' - [ ] Create original subfolder structure
' - [ ] Create DateTime Subfolders ' - [ ] Create DateTime Subfolders
' - [x] Check if file exists and version ' - [x] Check if file exists and version
' - [x] Do import ' - [x] Do import
' - [ ] Check for filesize 0 ' - [ ] Check for filesize 0
' - [ ] Write indexes (using data from getimportfile) ' - [x] Write indexes (using data from getimportfile)
' - [ ] Check if orig file should be deleted ' - [x] Check if orig file should be deleted
' - [ ] (delete subdirectories in source path) ' - [ ] (delete subdirectories in source path)
Dim oFiles = oFileNames. Dim oFiles = oFileNames.
@ -71,12 +71,22 @@ Namespace Scheduler.Jobs
LogDebug("{0} Files filtered out for being too new.", oDateFilteredCount) LogDebug("{0} Files filtered out for being too new.", oDateFilteredCount)
oFilteredFiles = oDateFilteredFiles oFilteredFiles = oDateFilteredFiles
If oFilteredFiles.Count = 0 Then
Logger.Info("No Files for Profile [{0}]", Name)
Return CompleteJobWithWaiting("No files for profile")
End If
' Process Regex to filter out files ' Process Regex to filter out files
Dim oRegexFilteredFiles = oFilteredFiles.Where(Function(f) Not FileMatchesRegex(f, oRegexList)) Dim oRegexFilteredFiles = oFilteredFiles.Where(Function(f) Not FileMatchesRegex(f, oRegexList))
Dim oRegexFilteredCount = oFilteredFiles.Except(oRegexFilteredFiles).Count() Dim oRegexFilteredCount = oFilteredFiles.Except(oRegexFilteredFiles).Count()
LogDebug("{0} Files filtered out for matching exclusion Regex.", oRegexFilteredCount) LogDebug("{0} Files filtered out for matching exclusion Regex.", oRegexFilteredCount)
oFilteredFiles = oDateFilteredFiles oFilteredFiles = oDateFilteredFiles
If oFilteredFiles.Count = 0 Then
Logger.Info("No Files for Profile [{0}]", Name)
Return CompleteJobWithWaiting("No files for profile")
End If
'------------------------------------------------------------------------------------------------- '-------------------------------------------------------------------------------------------------
' After this point, files are treated as processed and are being deleted before finishing the job ' After this point, files are treated as processed and are being deleted before finishing the job
'------------------------------------------------------------------------------------------------- '-------------------------------------------------------------------------------------------------