feat(ui): show CompletedWithErrors as orange and group sub-jobs by status

This commit is contained in:
2026-07-06 20:28:42 +02:00
parent a7b71153e9
commit 9dd93494c1
2 changed files with 72 additions and 24 deletions

View File

@@ -63,30 +63,33 @@
};
string RowClass => Job.Status switch {
EJobStatus.Completed => "",
EJobStatus.Failed => "border-start border-2 border-danger",
EJobStatus.Canceled => "",
_ => ""
EJobStatus.Completed => "",
EJobStatus.CompletedWithErrors => "border-start border-2 border-warning",
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"
EJobStatus.Queued => "#6c757d",
EJobStatus.Running => "#0d6efd",
EJobStatus.Waiting => "#0dcaf0",
EJobStatus.Completed => "#198754",
EJobStatus.CompletedWithErrors => "#fd7e14",
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"
EJobStatus.Queued => "secondary",
EJobStatus.Running => "primary",
EJobStatus.Waiting => "info",
EJobStatus.Completed => "success",
EJobStatus.CompletedWithErrors => "warning",
EJobStatus.Failed => "danger",
EJobStatus.Canceled => "secondary",
_ => "secondary"
};
bool CanCancel => Job.Status is EJobStatus.Queued or EJobStatus.Running or EJobStatus.Waiting;
@@ -98,9 +101,9 @@
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
EJobStatus.Completed or EJobStatus.CompletedWithErrors 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
EJobStatus.Completed or EJobStatus.CompletedWithErrors or EJobStatus.Failed or EJobStatus.Canceled when Job.Finished.HasValue
=> Job.Finished.Value.ToLocalTime() - Job.Created.ToLocalTime(),
_ => null
};

View File

@@ -15,10 +15,27 @@
ExpandedChanged="(bool e) => OnExpanded(root.Id, e)" />
@if (_expandedParents.Contains(root.Id) && hasFetched && hasChildren) {
<JobTree Jobs="children!"
Level="@(Level + 1)"
FetchChildren="@FetchChildren"
EmptyText="" />
var groups = children!.GroupBy(j => j.Status).OrderBy(g => (int)g.Key);
@foreach (var group in groups) {
var groupKey = $"{root.Id}-{(int)group.Key}";
var isGroupExpanded = _expandedGroups.Contains(groupKey);
<div class="ms-@((Level + 1) * 3)">
<div class="small fw-semibold py-1 d-flex align-items-center gap-2 user-select-none"
@onclick="() => ToggleGroup(groupKey)" role="button">
<i class="bi @(isGroupExpanded ? "bi-chevron-down" : "bi-chevron-right")"></i>
<span class="d-inline-block rounded-circle flex-shrink-0"
style="width: 8px; height: 8px; background: @(StatusColor(group.Key))"></span>
<span>@(StatusLabel(group.Key))</span>
<span class="text-muted">@group.Count()</span>
</div>
@if (isGroupExpanded) {
<JobTree Jobs="group.ToList()"
Level="@(Level + 2)"
FetchChildren="@FetchChildren"
EmptyText="" />
}
</div>
}
}
}
}
@@ -34,6 +51,7 @@
: Jobs?.OrderBy(j => j.Created).ToList() ?? [];
readonly HashSet<Guid> _expandedParents = [];
readonly HashSet<string> _expandedGroups = [];
readonly Dictionary<Guid, List<JobStatusDto>?> _childrenCache = [];
string? ChildSummary(JobStatusDto job, bool hasFetched, List<JobStatusDto>? children) {
@@ -55,6 +73,28 @@
return string.Join(" · ", parts);
}
static string StatusColor(EJobStatus status) => status switch {
EJobStatus.Queued => "#6c757d",
EJobStatus.Running => "#0d6efd",
EJobStatus.Waiting => "#0dcaf0",
EJobStatus.Completed => "#198754",
EJobStatus.CompletedWithErrors => "#fd7e14",
EJobStatus.Failed => "#dc3545",
EJobStatus.Canceled => "#6c757d",
_ => "#6c757d"
};
static string StatusLabel(EJobStatus status) => status switch {
EJobStatus.Completed => "Succeeded",
EJobStatus.CompletedWithErrors => "Completed with errors",
EJobStatus.Failed => "Failed",
EJobStatus.Canceled => "Canceled",
EJobStatus.Queued => "Queued",
EJobStatus.Running => "Running",
EJobStatus.Waiting => "Waiting",
_ => status.ToString()
};
async Task OnExpanded(Guid id, bool expanded) {
if (expanded) {
_expandedParents.Add(id);
@@ -65,4 +105,9 @@
_expandedParents.Remove(id);
}
}
void ToggleGroup(string groupKey) {
if (!_expandedGroups.Remove(groupKey))
_expandedGroups.Add(groupKey);
}
}