Files
MilkyShots/MilkStream.Client/Components/JobRow.razor

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
}
}
}