feat: add stacked progress bar for parent jobs with sub-job breakdown
Parent jobs with fetched sub-jobs now show a multi-color stacked progress bar instead of the single parent-progress bar. Segments represent sub-job status distribution: completed (success), running (primary), waiting (info), failed (danger), with-errors (warning), and queued (primary for visibility in dark theme). ProgressBar gets an optional Segments parameter for stacked mode. JobRow accepts Children and computes status-weighted segments. JobTree passes cached children to JobRow.
This commit is contained in:
@@ -17,7 +17,11 @@
|
||||
<span class="text-truncate text-muted small text-end">@(Job.Started?.ToLocalTime().ToString("g")) — @(Job.Finished?.ToLocalTime().ToString("g"))</span>
|
||||
} else {
|
||||
<span class="flex-shrink-0" style="width: 90px;">
|
||||
<ProgressBar Value="@Job.Progress" Color="@StatusColor" />
|
||||
@if (_segments is not null) {
|
||||
<ProgressBar Segments="@_segments" />
|
||||
} else {
|
||||
<ProgressBar Value="@Job.Progress" Color="@StatusColor" />
|
||||
}
|
||||
</span>
|
||||
}
|
||||
<span class="flex-shrink-0 text-muted small" style="width: 60px; text-align: right;">@Elapsed</span>
|
||||
@@ -55,6 +59,46 @@
|
||||
[Parameter] public string? ChildSummaryText { get; set; }
|
||||
[Parameter] public bool IsExpanded { get; set; }
|
||||
[Parameter] public EventCallback<bool> ExpandedChanged { get; set; }
|
||||
[Parameter] public IReadOnlyList<JobStatusDto>? Children { get; set; }
|
||||
|
||||
static readonly Dictionary<EJobStatus, string> _statusColors = new() {
|
||||
[EJobStatus.Queued] = "primary",
|
||||
[EJobStatus.Running] = "primary",
|
||||
[EJobStatus.Waiting] = "info",
|
||||
[EJobStatus.Completed] = "success",
|
||||
[EJobStatus.CompletedWithErrors] = "warning",
|
||||
[EJobStatus.Failed] = "danger",
|
||||
[EJobStatus.Canceled] = "secondary",
|
||||
};
|
||||
|
||||
static readonly EJobStatus[] _segmentOrder = [
|
||||
EJobStatus.Completed,
|
||||
EJobStatus.CompletedWithErrors,
|
||||
EJobStatus.Failed,
|
||||
EJobStatus.Canceled,
|
||||
EJobStatus.Running,
|
||||
EJobStatus.Waiting,
|
||||
EJobStatus.Queued,
|
||||
];
|
||||
|
||||
List<(float Width, string Color)>? _segments;
|
||||
|
||||
protected override void OnParametersSet() {
|
||||
_segments = ComputeSegments();
|
||||
}
|
||||
|
||||
List<(float Width, string Color)>? ComputeSegments() {
|
||||
if (Children is not { Count: > 0 }) return null;
|
||||
|
||||
var total = (float)Children.Count;
|
||||
var ordered = _segmentOrder
|
||||
.Select(s => (Status: s, Children.Where(c => c.Status == s).ToList()))
|
||||
.Where(g => g.Item2.Count > 0)
|
||||
.Select(g => (g.Item2.Count / total, _statusColors[g.Status]))
|
||||
.ToList();
|
||||
|
||||
return ordered;
|
||||
}
|
||||
|
||||
string Icon => Job.JobType switch {
|
||||
EJobType.FileSystemScan => "<i class=\"bi bi-folder2-open\"></i>",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
Job="root"
|
||||
Level="@Level"
|
||||
ChildSummaryText="@ChildSummary(root, hasFetched, children)"
|
||||
Children="@(hasFetched ? children : null)"
|
||||
IsExpanded="@(_expandedParents.Contains(root.Id) && hasFetched)"
|
||||
ExpandedChanged="(bool e) => OnExpanded(root, e)" />
|
||||
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
@* Generic progress bar. Value: 0.0 to 1.0. Color: Bootstrap color name (primary, success, etc). Size: sm (default), md, lg. *@
|
||||
<div class="progress" role="progressbar" style="height: @Height">
|
||||
<div class="progress-bar @BgClass" style="width: @(Value * 100)%">
|
||||
@if (ShowPercent) { @($"{Value * 100:F0}%") }
|
||||
@* Generic progress bar. Value: 0.0 to 1.0. Color: Bootstrap color name (primary, success, etc). Size: sm (default), md, lg. Segments for stacked mode. *@
|
||||
@if (Segments is not null and { Count: > 0 }) {
|
||||
<div class="progress-stacked" style="height: @Height">
|
||||
@foreach (var seg in Segments) {
|
||||
<div class="progress" role="progressbar" style="width: @(seg.Width * 100)%">
|
||||
<div class="progress-bar @(SegmentClass(seg.Color))"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<div class="progress" role="progressbar" style="height: @Height">
|
||||
<div class="progress-bar @BgClass" style="width: @(Value * 100)%">
|
||||
@if (ShowPercent) { @($"{Value * 100:F0}%") }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public float Value { get; set; }
|
||||
[Parameter] public string? Color { get; set; }
|
||||
[Parameter] public bool ShowPercent { get; set; }
|
||||
[Parameter] public string Size { get; set; } = "sm";
|
||||
[Parameter] public IReadOnlyList<(float Width, string Color)>? Segments { get; set; }
|
||||
|
||||
string Height => Size switch {
|
||||
"sm" => "6px",
|
||||
@@ -28,4 +39,15 @@
|
||||
"dark" => "text-bg-dark",
|
||||
var c => $"bg-{c}"
|
||||
};
|
||||
|
||||
string SegmentClass(string color) => color switch {
|
||||
"primary" => "bg-primary",
|
||||
"success" => "bg-success",
|
||||
"danger" => "bg-danger",
|
||||
"warning" => "bg-warning",
|
||||
"info" => "bg-info",
|
||||
"secondary" => "bg-secondary",
|
||||
"dark" => "bg-dark",
|
||||
var c => $"bg-{c}"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user