Skip to content

Commit 602932b

Browse files
adamintAdam Ratzman
andauthored
Improve summary details view / toolbars / navbar experience on desktop for low-vision users, as well as mobile users (#4245)
* Temporarily add nuget.org feed * Refactor current layout into DesktopLayout component * wip prototype * add mobile nav menu * add rest of nav menu to mobile, increase reconnection modal size on mobile * Add common page layout, use it for Resources * Convert Console Logs toolbar * Use new layout for structured logs * fix scroll bars, place toolbar on mobile within footer * Rename page sections, make title section more generic, do trace detail page * use new layout for metrics page * Add context parameter to details views, make them full page height on mobile * wip custom browser resize logic * Get initial viewport parameters for render (do not use scoped js because it is ~20ms slower) * fix nested h1, increase mobile toolbar button size, add resize listener * clean up views * remove extra class * remove redundant code, clean up * remove redundant code, add a few comments * fix accidental logic bug * Increase mobile toolbar height, make entire page scrollable at very low resolution * Add translation, comment * move comment * remove redundant if * Move toolbar mobile button to top * remove padding bottom for page header on mobile * some requested changes * try to avoid closing dialog on mobile if a toolbar setting has been changed, remove extraneous css, add Filter to url * Persist filter into URL, as well as visible types for resources. * Remove unnecessary button on detail view, invoke listeners after filters opened * Move MobileLayout and DesktopLayout logic into MainLayout so that page is not re-initialized when layout is changed. Remove filter state added to individual pages. Add a view model to the log viewer so that logs appear quickly after switching layouts * Open aspire repo link in new tab on mobile navigation * Add divs around page content layout to fix scoped css not being applied * fix extra div not taking up total height * fix metric scrollbar, change debounced resize event to throttled * Fix scrollbar erroneously appearing on console logs * Consolidate two selects into one * Refactor mobile nav menu to its own component, rename desktop nav menu component to DesktopNavMenu, and show page as active in mobile nav menu if its url matches current uri * Show icon as active in mobile navigation menu for the current page * Close mobile filter/nav menus when changing to desktop layout * Use --accent-foreground-active for active page in mobile nav * Make detail view close button background transparent in mobile * hide duration progress circle on traces mobile * fix continuous scroll on traces/console logs/structured logs * add console log application to mobile toolbar * re-add mobile filter footer button * fix incorrect merge conflicts * Avoid invoking redundant SetStateAndNavigateAsync calls, cleanup * fix chart/table state being lost on layout change * fix toolbar with newline appearance on mobile * add file headers, fix merge * fix console logs not updating after layout change --------- Co-authored-by: Adam Ratzman <[email protected]>
1 parent 4374062 commit 602932b

File tree

87 files changed

+2285
-1172
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+2285
-1172
lines changed

src/Aspire.Dashboard/Components/Controls/Chart/ChartContainer.razor

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
@namespace Aspire.Dashboard.Components
22

3-
@using Aspire.Dashboard.Model
43
@using Aspire.Dashboard.Otlp.Model
5-
@using Aspire.Dashboard.Otlp.Model.MetricValues
64
@using Aspire.Dashboard.Resources
75
@using Metrics = Aspire.Dashboard.Components.Pages.Metrics
86
@inject IStringLocalizer<ControlsStrings> Loc

src/Aspire.Dashboard/Components/Controls/Chart/ChartContainer.razor.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,11 @@ namespace Aspire.Dashboard.Components;
1313

1414
public partial class ChartContainer : ComponentBase, IAsyncDisposable
1515
{
16-
private readonly CounterChartViewModel _viewModel = new();
17-
1816
private OtlpInstrument? _instrument;
1917
private PeriodicTimer? _tickTimer;
2018
private Task? _tickTask;
2119
private IDisposable? _themeChangedSubscription;
2220
private int _renderedDimensionsCount;
23-
private string? _previousMeterName;
24-
private string? _previousInstrumentName;
2521
private readonly InstrumentViewModel _instrumentViewModel = new InstrumentViewModel();
2622

2723
[Parameter, EditorRequired]
@@ -45,6 +41,9 @@ public partial class ChartContainer : ComponentBase, IAsyncDisposable
4541
[Inject]
4642
public required ThemeManager ThemeManager { get; init; }
4743

44+
[Inject]
45+
public required CurrentChartViewModel ChartViewModel { get; init; }
46+
4847
protected override void OnInitialized()
4948
{
5049
// Update the graph every 200ms. This displays the latest data and moves time forward.
@@ -113,7 +112,7 @@ private async Task UpdateInstrumentDataAsync(OtlpInstrument instrument)
113112

114113
private bool MatchDimension(DimensionScope dimension)
115114
{
116-
foreach (var dimensionFilter in _viewModel.DimensionFilters)
115+
foreach (var dimensionFilter in ChartViewModel.DimensionFilters)
117116
{
118117
if (!MatchFilter(dimension.Attributes, dimensionFilter))
119118
{
@@ -156,14 +155,14 @@ protected override async Task OnParametersSetAsync()
156155
return;
157156
}
158157

159-
var hasInstrumentChanged = _previousMeterName != MeterName || _previousInstrumentName != InstrumentName;
160-
_previousMeterName = MeterName;
161-
_previousInstrumentName = InstrumentName;
158+
var hasInstrumentChanged = ChartViewModel.PreviousMeterName != MeterName || ChartViewModel.PreviousInstrumentName != InstrumentName;
159+
ChartViewModel.PreviousMeterName = MeterName;
160+
ChartViewModel.PreviousInstrumentName = InstrumentName;
162161

163162
var filters = CreateUpdatedFilters(hasInstrumentChanged);
164163

165-
_viewModel.DimensionFilters.Clear();
166-
_viewModel.DimensionFilters.AddRange(filters);
164+
ChartViewModel.DimensionFilters.Clear();
165+
ChartViewModel.DimensionFilters.AddRange(filters);
167166

168167
await UpdateInstrumentDataAsync(_instrument);
169168
}
@@ -235,7 +234,7 @@ private List<DimensionFilterViewModel> CreateUpdatedFilters(bool hasInstrumentCh
235234
}
236235
else
237236
{
238-
var existing = _viewModel.DimensionFilters.SingleOrDefault(m => m.Name == item.Name);
237+
var existing = ChartViewModel.DimensionFilters.SingleOrDefault(m => m.Name == item.Name);
239238
if (existing != null)
240239
{
241240
// Select previously selected.

src/Aspire.Dashboard/Components/Controls/Chart/ChartFilters.razor

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
@inject IStringLocalizer<ControlsStrings> Loc
77

88
<div class="metrics-filters-container">
9-
@if (ViewModel.DimensionFilters.Count > 0)
9+
@if (ChartViewModel.DimensionFilters.Count > 0)
1010
{
1111
<div class="metrics-filters-section">
1212
<h5>@Loc[nameof(ControlsStrings.ChartContainerFiltersHeader)]</h5>
13-
<FluentDataGrid Items="@Queryable.AsQueryable(ViewModel.DimensionFilters)" GridTemplateColumns="200px 1fr auto" GenerateHeader="GenerateHeaderOption.None">
13+
<FluentDataGrid Items="@Queryable.AsQueryable(ChartViewModel.DimensionFilters)" GridTemplateColumns="200px 1fr auto" GenerateHeader="GenerateHeaderOption.None">
1414
<ChildContent>
1515
<PropertyColumn Tooltip="true" TooltipText="@(c => c.Name)" Property="@(c => c.Name)"/>
1616
<TemplateColumn Tooltip="true" TooltipText="@(c => c.SelectedValues.Count == 0 ? Loc[nameof(ControlsStrings.ChartContainerNoneSelected)] : string.Join(", ", c.SelectedValues.Select(v => v.Name)))">
@@ -79,7 +79,7 @@
7979
<div>
8080
<FluentSwitch Class="table-switch"
8181
Label="@Loc[nameof(ControlsStrings.ChartContainerShowCountLabel)]"
82-
@bind-Value="_showCount"
82+
@bind-Value="ChartViewModel.ShowCounts"
8383
@bind-Value:after="ShowCountChanged"/>
8484
</div>
8585
</div>
@@ -90,9 +90,6 @@
9090
[Parameter, EditorRequired]
9191
public required OtlpInstrument Instrument { get; set; }
9292

93-
[Parameter, EditorRequired]
94-
public required CounterChartViewModel ViewModel { get; set; }
95-
9693
[Parameter, EditorRequired]
9794
public required InstrumentViewModel InstrumentViewModel { get; set; }
9895
}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Aspire.Dashboard.Model;
5+
using Microsoft.AspNetCore.Components;
6+
47
namespace Aspire.Dashboard.Components;
58

69
public partial class ChartFilters
710
{
8-
private bool _showCount;
11+
[Inject]
12+
public required CurrentChartViewModel ChartViewModel { get; init; }
913

1014
protected override void OnInitialized()
1115
{
1216
InstrumentViewModel.DataUpdateSubscriptions.Add(() =>
1317
{
14-
_showCount = InstrumentViewModel.ShowCount;
18+
ChartViewModel.ShowCounts = InstrumentViewModel.ShowCount;
1519
return Task.CompletedTask;
1620
});
1721
}
1822

1923
private void ShowCountChanged()
2024
{
21-
InstrumentViewModel.ShowCount = _showCount;
25+
InstrumentViewModel.ShowCount = ChartViewModel.ShowCounts;
2226
}
2327
}

src/Aspire.Dashboard/Components/Controls/Chart/MetricTable.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103

104104
<FluentStack Orientation="Orientation.Vertical" Style="margin-bottom: 20px;">
105105
<FluentSwitch Class="table-switch"
106-
@bind-Value="@_onlyShowValueChanges"
106+
@bind-Value="@ChartViewModel.OnlyShowValueChangesInTable"
107107
@bind-Value:after="SettingsChangedAsync"
108108
Label="@Loc[nameof(ControlsStrings.MetricTableShowOnlyValueChanges)]"/>
109109
</FluentStack>

src/Aspire.Dashboard/Components/Controls/Chart/MetricTable.razor.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
using System.Diagnostics;
55
using System.Globalization;
66
using Aspire.Dashboard.Components.Controls.Chart;
7-
using Aspire.Dashboard.Components.Dialogs;
87
using Aspire.Dashboard.Model;
8+
using Aspire.Dashboard.Components.Dialogs;
99
using Aspire.Dashboard.Otlp.Model;
1010
using Aspire.Dashboard.Resources;
1111
using Aspire.Dashboard.Utils;
@@ -24,7 +24,6 @@ public partial class MetricTable : ChartBase
2424

2525
private OtlpInstrument? _instrument;
2626
private bool _showCount;
27-
private bool _onlyShowValueChanges = true;
2827
private DateTimeOffset? _lastUpdate;
2928

3029
private readonly CancellationTokenSource _waitTaskCancellationTokenSource = new();
@@ -34,6 +33,9 @@ public partial class MetricTable : ChartBase
3433
[Inject]
3534
public required IJSRuntime JS { get; init; }
3635

36+
[Inject]
37+
public required CurrentChartViewModel ChartViewModel { get; init; }
38+
3739
[Inject]
3840
public required IDialogService DialogService { get; init; }
3941

@@ -139,7 +141,7 @@ private SortedList<DateTimeOffset, MetricViewBase> UpdateMetrics(out ISet<DateTi
139141
continue;
140142
}
141143

142-
if (_onlyShowValueChanges && valueDiffs.All(diff => DoubleEquals(diff, 0)))
144+
if (ChartViewModel.OnlyShowValueChangesInTable && valueDiffs.All(diff => DoubleEquals(diff, 0)))
143145
{
144146
continue;
145147
}
@@ -174,7 +176,7 @@ MetricViewBase CreateHistogramMetricView()
174176
continue;
175177
}
176178

177-
if (_onlyShowValueChanges && DoubleEquals(valueDiff, 0d))
179+
if (ChartViewModel.OnlyShowValueChangesInTable && DoubleEquals(valueDiff, 0d))
178180
{
179181
continue;
180182
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
@using Aspire.Dashboard.Components.Resize
2+
@using Aspire.Dashboard.Resources
3+
@inject IStringLocalizer<ControlsStrings> Loc
4+
5+
<div class="details-container">
6+
<header style="height: auto;">
7+
@if (DetailsTitle is not null)
8+
{
9+
<div class="details-header-title" title="@DetailsTitle">@DetailsTitle</div>
10+
}
11+
else if (DetailsTitleTemplate is not null)
12+
{
13+
<div class="details-header-title">@DetailsTitleTemplate</div>
14+
}
15+
<div class="header-actions">
16+
@if (ViewportInformation.IsDesktop)
17+
{
18+
<FluentButton Appearance="Appearance.Stealth"
19+
IconEnd="@(Orientation == Orientation.Horizontal ? _splitHorizontalIcon : _splitVerticalIcon)"
20+
OnClick="HandleToggleOrientation"
21+
Title="@(Orientation == Orientation.Horizontal ? Loc[nameof(ControlsStrings.SummaryDetailsViewSplitHorizontal)] : Loc[nameof(ControlsStrings.SummaryDetailsViewSplitVertical)])"
22+
aria-label="@(Orientation == Orientation.Horizontal ? Loc[nameof(ControlsStrings.SummaryDetailsViewSplitHorizontal)] : Loc[nameof(ControlsStrings.SummaryDetailsViewSplitVertical)])"/>
23+
}
24+
25+
<FluentButton Appearance="Appearance.Stealth" BackgroundColor="@(ViewportInformation.IsDesktop ? null : "rgba(0, 0, 0, 0)")" IconEnd="@(new Icons.Regular.Size16.Dismiss())"
26+
OnClick="HandleDismissAsync" Title="@Loc[nameof(ControlsStrings.SummaryDetailsViewCloseView)]" aria-label="@Loc[nameof(ControlsStrings.SummaryDetailsViewCloseView)]"/>
27+
</div>
28+
</header>
29+
@Details
30+
</div>
31+
32+
@code {
33+
private readonly Icon _splitHorizontalIcon = new Icons.Regular.Size16.SplitHorizontal();
34+
private readonly Icon _splitVerticalIcon = new Icons.Regular.Size16.SplitVertical();
35+
36+
[Parameter]
37+
public string? DetailsTitle { get; set; }
38+
39+
[Parameter]
40+
public RenderFragment? Details { get; set; }
41+
42+
[Parameter]
43+
public RenderFragment? DetailsTitleTemplate { get; set; }
44+
45+
[Parameter]
46+
public required Func<Task> HandleToggleOrientation { get; set; }
47+
48+
[Parameter]
49+
public required Func<Task> HandleDismissAsync { get; set; }
50+
51+
[Parameter]
52+
public required Orientation Orientation { get; set; }
53+
54+
[CascadingParameter]
55+
public required ViewportInformation ViewportInformation { get; set; }
56+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@using Aspire.Dashboard.Model.Otlp
2+
@inject IStringLocalizer<Resources.StructuredLogs> Loc
3+
4+
<FluentSelect TOption="SelectViewModel<LogLevel?>"
5+
Items="@LogLevels"
6+
Position="SelectPosition.Below"
7+
OptionText="@(c => c.Name)"
8+
Label="@(IncludeLabel ? Loc[nameof(Resources.StructuredLogs.StructuredLogsLevels)].Value : null)"
9+
@bind-SelectedOption="@LogLevel"
10+
@bind-SelectedOption:after="@HandleSelectedLogLevelChangedInternalAsync"
11+
Width="120px"
12+
Style="min-width: auto;"
13+
AriaLabel="@Loc[nameof(Resources.StructuredLogs.StructuredLogsSelectMinimumLogLevel)]"/>
14+
15+
@code {
16+
[Parameter]
17+
public bool IncludeLabel { get; set; }
18+
19+
[Parameter, EditorRequired]
20+
public required List<SelectViewModel<LogLevel?>> LogLevels { get; set; }
21+
22+
[Parameter, EditorRequired]
23+
public required SelectViewModel<LogLevel?> LogLevel { get; set; }
24+
25+
[Parameter]
26+
public EventCallback<SelectViewModel<LogLevel?>> LogLevelChanged { get; set; }
27+
28+
[Parameter, EditorRequired]
29+
public required Func<Task> HandleSelectedLogLevelChangedAsync { get; set; }
30+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Components;
5+
6+
namespace Aspire.Dashboard.Components.Controls;
7+
8+
public partial class LogLevelSelect : ComponentBase
9+
{
10+
private async Task HandleSelectedLogLevelChangedInternalAsync()
11+
{
12+
await LogLevelChanged.InvokeAsync(LogLevel);
13+
await HandleSelectedLogLevelChangedAsync();
14+
}
15+
}
16+

src/Aspire.Dashboard/Components/Controls/LogViewer.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<div class="log-overflow continuous-scroll-overflow">
1010
<div class="log-container" id="logContainer">
11-
<Virtualize Items="_logEntries.GetEntries()" ItemSize="20" OverscanCount="100" TItem="LogEntry">
11+
<Virtualize Items="@ViewModel.LogEntries.GetEntries()" ItemSize="20" OverscanCount="100" TItem="LogEntry">
1212
<div class="line-row-container">
1313
<div class="line-row">
1414
<span class="line-area" role="log">

0 commit comments

Comments
 (0)