132 lines
5.5 KiB
Plaintext
132 lines
5.5 KiB
Plaintext
@using Butter.Dtos.Jobs
|
|
@using Butter.Types
|
|
@using MilkStream.Client.Services
|
|
@inject JobsService JobsService
|
|
|
|
<div class="@($"job-row {RowClass}")" style="padding-left: @(Level * 24 + 8)px">
|
|
<div class="d-flex align-items-center gap-2 py-1 px-1 job-row-main rounded" @onclick="Toggle" role="button">
|
|
<span class="flex-shrink-0">@((MarkupString)Icon)</span>
|
|
<span class="text-truncate flex-grow-1 small" title="@Job.Name">@Job.Name</span>
|
|
<span class="flex-shrink-0" title="@Job.Status.ToString()">
|
|
<span class="d-inline-block rounded-circle" style="width: 8px; height: 8px; background: @DotColor"></span>
|
|
</span>
|
|
<span class="flex-shrink-0" style="width: 90px;">
|
|
<ProgressBar Value="@Job.Progress" Color="@StatusColor" />
|
|
</span>
|
|
<span class="flex-shrink-0 text-muted small" style="width: 60px; text-align: right;">@Elapsed</span>
|
|
@if (CanCancel) {
|
|
<button class="btn btn-sm p-0 border-0 text-danger flex-shrink-0 lh-1" @onclick:stopPropagation @onclick="Cancel" title="Cancel">
|
|
<i class="bi bi-x-lg" style="font-size: 0.75rem;"></i>
|
|
</button>
|
|
}
|
|
<span class="flex-shrink-0 text-muted small @(Expanded ? "bi-chevron-up" : "bi-chevron-down")"></span>
|
|
</div>
|
|
@if (Expanded) {
|
|
<div class="job-row-details small text-muted px-2 py-1" style="border-left: 2px solid @DotColor; margin-left: 16px;">
|
|
<div class="d-flex flex-wrap gap-x-3 gap-y-0">
|
|
<span>Created: @Job.Created.ToLocalTime().ToString("g")</span>
|
|
@if (Job.Started.HasValue) {
|
|
<span>Started: @Job.Started.Value.ToLocalTime().ToString("g")</span>
|
|
}
|
|
@if (Job.Finished.HasValue) {
|
|
<span>Finished: @Job.Finished.Value.ToLocalTime().ToString("g")</span>
|
|
}
|
|
@if (Duration is not null) {
|
|
<span>Duration: @Duration.Value.ToString(@"h\:mm\:ss")</span>
|
|
}
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(Job.Message)) {
|
|
<div class="mt-1" style="white-space: pre-wrap; word-break: break-word;">@Job.Message</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter] public required JobStatusDto Job { get; set; }
|
|
[Parameter] public int Level { get; set; }
|
|
|
|
bool Expanded;
|
|
|
|
string Icon => Job.JobType switch {
|
|
EJobType.FileSystemScan => "<i class=\"bi bi-folder2-open\"></i>",
|
|
EJobType.ThumbnailGeneration => "<i class=\"bi bi-pip\"></i>",
|
|
EJobType.PreviewGeneration => "<i class=\"bi bi-file-image\"></i>",
|
|
EJobType.MetadataExtraction => "<i class=\"bi bi-file-earmark-break\"></i>",
|
|
EJobType.IntegrityCheck => "<i class=\"bi bi-file-earmark-check\"></i>",
|
|
EJobType.PHashGeneration => "<i class=\"bi bi-hash\"></i>",
|
|
_ => "<i class=\"bi bi-question-circle\"></i>"
|
|
};
|
|
|
|
string RowClass => Job.Status switch {
|
|
EJobStatus.Completed => "",
|
|
EJobStatus.Failed => "border-start border-2 border-danger",
|
|
EJobStatus.Canceled => "",
|
|
_ => ""
|
|
};
|
|
|
|
string DotColor => Job.Status switch {
|
|
EJobStatus.Queued => "#6c757d",
|
|
EJobStatus.Running => "#0d6efd",
|
|
EJobStatus.Waiting => "#0dcaf0",
|
|
EJobStatus.Completed => "#198754",
|
|
EJobStatus.Failed => "#dc3545",
|
|
EJobStatus.Canceled => "#6c757d",
|
|
_ => "#6c757d"
|
|
};
|
|
|
|
string StatusColor => Job.Status switch {
|
|
EJobStatus.Queued => "secondary",
|
|
EJobStatus.Running => "primary",
|
|
EJobStatus.Waiting => "info",
|
|
EJobStatus.Completed => "success",
|
|
EJobStatus.Failed => "danger",
|
|
EJobStatus.Canceled => "secondary",
|
|
_ => "secondary"
|
|
};
|
|
|
|
bool CanCancel => Job.Status is EJobStatus.Queued or EJobStatus.Running or EJobStatus.Waiting;
|
|
|
|
string? Elapsed {
|
|
get {
|
|
var now = DateTime.Now;
|
|
TimeSpan? ts = Job.Status switch {
|
|
EJobStatus.Queued => now - Job.Created.ToLocalTime(),
|
|
EJobStatus.Running or EJobStatus.Waiting when Job.Started.HasValue
|
|
=> now - Job.Started.Value.ToLocalTime(),
|
|
EJobStatus.Completed or EJobStatus.Failed or EJobStatus.Canceled when Job.Finished.HasValue && Job.Started.HasValue
|
|
=> Job.Finished.Value.ToLocalTime() - Job.Started.Value.ToLocalTime(),
|
|
EJobStatus.Completed or EJobStatus.Failed or EJobStatus.Canceled when Job.Finished.HasValue
|
|
=> Job.Finished.Value.ToLocalTime() - Job.Created.ToLocalTime(),
|
|
_ => null
|
|
};
|
|
return ts switch {
|
|
null => null,
|
|
{ TotalMinutes: < 1 } => $"{(int)ts.Value.TotalSeconds}s",
|
|
{ TotalHours: < 1 } => $"{(int)ts.Value.TotalMinutes}m",
|
|
_ => $"{(int)ts.Value.TotalHours}h {ts.Value.Minutes}m"
|
|
};
|
|
}
|
|
}
|
|
|
|
TimeSpan? Duration {
|
|
get {
|
|
if (Job.Started is null) return null;
|
|
var end = Job.Finished ?? DateTime.UtcNow;
|
|
return end.ToLocalTime() - Job.Started.Value.ToLocalTime();
|
|
}
|
|
}
|
|
|
|
async Task Toggle() {
|
|
Expanded = !Expanded;
|
|
}
|
|
|
|
async Task Cancel() {
|
|
try {
|
|
await JobsService.CancelJob(Job);
|
|
} catch {
|
|
// Silently handle — next poll will reflect actual state
|
|
}
|
|
}
|
|
}
|