Skip to content

Commit 9f00227

Browse files
authored
Add context menu to resource graph, improve console logs actions (#8691)
1 parent 5e87815 commit 9f00227

28 files changed

+398
-196
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
@namespace Aspire.Dashboard.Components
2+
@using System.Collections.Immutable
3+
@using Aspire.Dashboard.Model
4+
@inherits FluentComponentBase
5+
6+
<FluentMenu @ref="_menu" Anchor="@Anchor" Anchored="@Anchored" Open="@Open" OpenChanged="OnOpenChanged" VerticalThreshold="200">
7+
@foreach (var item in Items)
8+
{
9+
@if (item.IsDivider)
10+
{
11+
<FluentDivider />
12+
}
13+
else
14+
{
15+
var additionalMenuItemAttributes = new Dictionary<string, object>(item.AdditionalAttributes ?? ImmutableDictionary<string, object>.Empty)
16+
{
17+
{ "title", item.Tooltip ?? item.Text ?? string.Empty }
18+
};
19+
20+
<FluentMenuItem Id="@item.Id" Class="@item.Class" OnClick="() => HandleItemClicked(item)" Disabled="@item.IsDisabled" AdditionalAttributes="@additionalMenuItemAttributes">
21+
@item.Text
22+
@if (item.Icon != null)
23+
{
24+
<span slot="start">
25+
<FluentIcon Value="@item.Icon" Style="vertical-align: text-bottom;" Width="16px" />
26+
</span>
27+
}
28+
</FluentMenuItem>
29+
}
30+
}
31+
</FluentMenu>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 Aspire.Dashboard.Model;
5+
using Microsoft.AspNetCore.Components;
6+
using Microsoft.FluentUI.AspNetCore.Components;
7+
8+
namespace Aspire.Dashboard.Components;
9+
10+
public partial class AspireMenu : FluentComponentBase
11+
{
12+
private FluentMenu? _menu;
13+
14+
[Parameter]
15+
public string? Anchor { get; set; }
16+
17+
[Parameter]
18+
public bool Open { get; set; }
19+
20+
[Parameter]
21+
public bool Anchored { get; set; } = true;
22+
23+
/// <summary>
24+
/// Raised when the <see cref="Open"/> property changed.
25+
/// </summary>
26+
[Parameter]
27+
public EventCallback<bool> OpenChanged { get; set; }
28+
29+
[Parameter]
30+
public required IList<MenuButtonItem> Items { get; set; }
31+
32+
public async Task CloseAsync()
33+
{
34+
if (_menu is { } menu)
35+
{
36+
await menu.CloseAsync();
37+
}
38+
}
39+
40+
public async Task OpenAsync(int clientX, int clientY)
41+
{
42+
if (_menu is { } menu)
43+
{
44+
await menu.OpenAsync(clientX, clientY);
45+
}
46+
}
47+
48+
private async Task HandleItemClicked(MenuButtonItem item)
49+
{
50+
if (item.OnClick is {} onClick)
51+
{
52+
await onClick();
53+
}
54+
Open = false;
55+
}
56+
57+
private Task OnOpenChanged(bool open)
58+
{
59+
Open = open;
60+
61+
return OpenChanged.HasDelegate
62+
? OpenChanged.InvokeAsync(open)
63+
: Task.CompletedTask;
64+
}
65+
}
Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@namespace Aspire.Dashboard.Components
22
@using System.Collections.Immutable
33
@using Aspire.Dashboard.Model
4-
@using Microsoft.FluentUI.AspNetCore.Components.DesignTokens
54
@inherits FluentComponentBase
65

76
@{
@@ -29,29 +28,4 @@
2928
}
3029
</FluentButton>
3130

32-
<FluentMenu Anchor="@MenuButtonId" aria-labelledby="button" @bind-Open="@_visible" VerticalThreshold="200">
33-
@foreach (var item in Items)
34-
{
35-
@if (item.IsDivider)
36-
{
37-
<FluentDivider />
38-
}
39-
else
40-
{
41-
var additionalMenuItemAttributes = new Dictionary<string, object>(item.AdditionalAttributes ?? ImmutableDictionary<string, object>.Empty)
42-
{
43-
{ "title", item.Tooltip ?? item.Text ?? string.Empty }
44-
};
45-
46-
<FluentMenuItem Id="@item.Id" Class="@item.Class" OnClick="() => HandleItemClicked(item)" Disabled="@item.IsDisabled" AdditionalAttributes="@additionalMenuItemAttributes">
47-
@item.Text
48-
@if (item.Icon != null)
49-
{
50-
<span slot="start">
51-
<FluentIcon Value="@item.Icon" Style="vertical-align: text-bottom;" Width="16px" />
52-
</span>
53-
}
54-
</FluentMenuItem>
55-
}
56-
}
57-
</FluentMenu>
31+
<AspireMenu Anchor="@MenuButtonId" @bind-Open="@_visible" Items="Items" />

src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,6 @@ private void ToggleMenu()
5656
_visible = !_visible;
5757
}
5858

59-
private async Task HandleItemClicked(MenuButtonItem item)
60-
{
61-
if (item.OnClick is {} onClick)
62-
{
63-
await onClick();
64-
}
65-
_visible = false;
66-
}
67-
6859
private void OnKeyDown(KeyboardEventArgs args)
6960
{
7061
if (args is not null && args.Key == "Escape")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
{
1313
var highlightedCommand = _highlightedCommands[i];
1414

15-
<FluentButton Appearance="Appearance.Lightweight" Title="@(!string.IsNullOrEmpty(highlightedCommand.DisplayDescription) ? highlightedCommand.DisplayDescription : highlightedCommand.DisplayName)" OnClick="@(() => CommandSelected.InvokeAsync(highlightedCommand))" Disabled="@(highlightedCommand.State == CommandViewModelState.Disabled || IsCommandExecuting(Resource, highlightedCommand))">
15+
<FluentButton Appearance="Appearance.Lightweight" Title="@(!string.IsNullOrEmpty(highlightedCommand.DisplayDescription) ? highlightedCommand.DisplayDescription : highlightedCommand.DisplayName)" OnClick="@(() => CommandSelected(highlightedCommand))" Disabled="@(highlightedCommand.State == CommandViewModelState.Disabled || IsCommandExecuting(Resource, highlightedCommand))">
1616
@if (!string.IsNullOrEmpty(highlightedCommand.IconName) && IconResolver.ResolveIconName(highlightedCommand.IconName, IconSize.Size16, highlightedCommand.IconVariant) is { } icon)
1717
{
1818
<FluentIcon Value="@icon" Width="16px" />

src/Aspire.Dashboard/Components/Controls/ResourceActions.razor.cs

Lines changed: 16 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using Aspire.Dashboard.Model;
55
using Aspire.Dashboard.Otlp.Storage;
6-
using Aspire.Dashboard.Utils;
76
using Microsoft.AspNetCore.Components;
87
using Microsoft.Extensions.Localization;
98
using Microsoft.FluentUI.AspNetCore.Components;
@@ -13,11 +12,7 @@ namespace Aspire.Dashboard.Components;
1312

1413
public partial class ResourceActions : ComponentBase
1514
{
16-
private static readonly Icon s_viewDetailsIcon = new Icons.Regular.Size16.Info();
1715
private static readonly Icon s_consoleLogsIcon = new Icons.Regular.Size16.SlideText();
18-
private static readonly Icon s_structuredLogsIcon = new Icons.Regular.Size16.SlideTextSparkle();
19-
private static readonly Icon s_tracesIcon = new Icons.Regular.Size16.GanttChart();
20-
private static readonly Icon s_metricsIcon = new Icons.Regular.Size16.ChartMultiple();
2116

2217
private AspireMenuButton? _menuButton;
2318

@@ -34,16 +29,13 @@ public partial class ResourceActions : ComponentBase
3429
public required TelemetryRepository TelemetryRepository { get; set; }
3530

3631
[Parameter]
37-
public required IList<CommandViewModel> Commands { get; set; }
38-
39-
[Parameter]
40-
public required EventCallback<CommandViewModel> CommandSelected { get; set; }
32+
public required Func<CommandViewModel, Task> CommandSelected { get; set; }
4133

4234
[Parameter]
4335
public required Func<ResourceViewModel, CommandViewModel, bool> IsCommandExecuting { get; set; }
4436

4537
[Parameter]
46-
public required EventCallback<string> OnViewDetails { get; set; }
38+
public required Func<string?, Task> OnViewDetails { get; set; }
4739

4840
[Parameter]
4941
public required ResourceViewModel Resource { get; set; }
@@ -65,88 +57,24 @@ protected override void OnParametersSet()
6557
_menuItems.Clear();
6658
_highlightedCommands.Clear();
6759

68-
_menuItems.Add(new MenuButtonItem
69-
{
70-
Text = ControlLoc[nameof(Resources.ControlsStrings.ActionViewDetailsText)],
71-
Icon = s_viewDetailsIcon,
72-
OnClick = () => OnViewDetails.InvokeAsync(_menuButton?.MenuButtonId)
73-
});
74-
75-
_menuItems.Add(new MenuButtonItem
76-
{
77-
Text = Loc[nameof(Resources.Resources.ResourceActionConsoleLogsText)],
78-
Icon = s_consoleLogsIcon,
79-
OnClick = () =>
80-
{
81-
NavigationManager.NavigateTo(DashboardUrls.ConsoleLogsUrl(resource: Resource.Name));
82-
return Task.CompletedTask;
83-
}
84-
});
85-
86-
// Show telemetry menu items if there is telemetry for the resource.
87-
var hasTelemetryApplication = TelemetryRepository.GetApplicationByCompositeName(Resource.Name) != null;
88-
if (hasTelemetryApplication)
89-
{
90-
_menuItems.Add(new MenuButtonItem { IsDivider = true });
91-
_menuItems.Add(new MenuButtonItem
92-
{
93-
Text = Loc[nameof(Resources.Resources.ResourceActionStructuredLogsText)],
94-
Tooltip = Loc[nameof(Resources.Resources.ResourceActionStructuredLogsText)],
95-
Icon = s_structuredLogsIcon,
96-
OnClick = () =>
97-
{
98-
NavigationManager.NavigateTo(DashboardUrls.StructuredLogsUrl(resource: GetResourceName(Resource)));
99-
return Task.CompletedTask;
100-
}
101-
});
102-
_menuItems.Add(new MenuButtonItem
103-
{
104-
Text = Loc[nameof(Resources.Resources.ResourceActionTracesText)],
105-
Tooltip = Loc[nameof(Resources.Resources.ResourceActionTracesText)],
106-
Icon = s_tracesIcon,
107-
OnClick = () =>
108-
{
109-
NavigationManager.NavigateTo(DashboardUrls.TracesUrl(resource: GetResourceName(Resource)));
110-
return Task.CompletedTask;
111-
}
112-
});
113-
_menuItems.Add(new MenuButtonItem
114-
{
115-
Text = Loc[nameof(Resources.Resources.ResourceActionMetricsText)],
116-
Tooltip = Loc[nameof(Resources.Resources.ResourceActionMetricsText)],
117-
Icon = s_metricsIcon,
118-
OnClick = () =>
119-
{
120-
NavigationManager.NavigateTo(DashboardUrls.MetricsUrl(resource: GetResourceName(Resource)));
121-
return Task.CompletedTask;
122-
}
123-
});
124-
}
60+
ResourceMenuItems.AddMenuItems(
61+
_menuItems,
62+
_menuButton?.MenuButtonId,
63+
Resource,
64+
NavigationManager,
65+
TelemetryRepository,
66+
GetResourceName,
67+
ControlLoc,
68+
Loc,
69+
OnViewDetails,
70+
CommandSelected,
71+
IsCommandExecuting,
72+
showConsoleLogsItem: true);
12573

12674
// If display is desktop then we display highlighted commands next to the ... button.
12775
if (ViewportInformation.IsDesktop)
12876
{
129-
_highlightedCommands.AddRange(Commands.Where(c => c.IsHighlighted && c.State != CommandViewModelState.Hidden).Take(MaxHighlightedCount));
130-
}
131-
132-
var menuCommands = Commands.Where(c => !_highlightedCommands.Contains(c) && c.State != CommandViewModelState.Hidden).ToList();
133-
if (menuCommands.Count > 0)
134-
{
135-
_menuItems.Add(new MenuButtonItem { IsDivider = true });
136-
137-
foreach (var command in menuCommands)
138-
{
139-
var icon = (!string.IsNullOrEmpty(command.IconName) && IconResolver.ResolveIconName(command.IconName, IconSize.Size16, command.IconVariant) is { } i) ? i : null;
140-
141-
_menuItems.Add(new MenuButtonItem
142-
{
143-
Text = command.DisplayName,
144-
Tooltip = command.DisplayDescription,
145-
Icon = icon,
146-
OnClick = () => CommandSelected.InvokeAsync(command),
147-
IsDisabled = command.State == CommandViewModelState.Disabled || IsCommandExecuting(Resource, command)
148-
});
149-
}
77+
_highlightedCommands.AddRange(Resource.Commands.Where(c => c.IsHighlighted && c.State != CommandViewModelState.Hidden).Take(MaxHighlightedCount));
15078
}
15179
}
15280
}

src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
{
3535
if (_highlightedCommands.Count > 0)
3636
{
37-
<span style="margin-left: 10px;">@Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsResourceCommands)]</span>
37+
<span style="margin-left: 10px;">@Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsResourceActions)]</span>
3838

3939
@foreach (var command in _highlightedCommands)
4040
{
@@ -60,7 +60,7 @@
6060
<AspireMenuButton ButtonAppearance="Appearance.Lightweight"
6161
Icon="@(new Icons.Regular.Size20.MoreHorizontal())"
6262
Items="@_resourceMenuItems"
63-
Title="@Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsResourceCommands)]" />
63+
Title="@Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsResourceActions)]" />
6464
}
6565
}
6666

src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using Microsoft.AspNetCore.Components;
2020
using Microsoft.Extensions.Localization;
2121
using Microsoft.Extensions.Options;
22-
using Microsoft.FluentUI.AspNetCore.Components;
2322
using Microsoft.JSInterop;
2423
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
2524

@@ -57,12 +56,18 @@ private sealed class ConsoleLogsSubscription
5756
[Inject]
5857
public required NavigationManager NavigationManager { get; init; }
5958

59+
[Inject]
60+
public required TelemetryRepository TelemetryRepository { get; init; }
61+
6062
[Inject]
6163
public required ILogger<ConsoleLogs> Logger { get; init; }
6264

6365
[Inject]
6466
public required IStringLocalizer<Dashboard.Resources.ConsoleLogs> Loc { get; init; }
6567

68+
[Inject]
69+
public required IStringLocalizer<Dashboard.Resources.Resources> ResourcesLoc { get; init; }
70+
6671
[Inject]
6772
public required IStringLocalizer<ControlsStrings> ControlsStringsLoc { get; init; }
6873

@@ -331,23 +336,23 @@ private void UpdateMenuButtons()
331336
_highlightedCommands.AddRange(PageViewModel.SelectedResource.Commands.Where(c => c.IsHighlighted && c.State != CommandViewModelState.Hidden).Take(DashboardUIHelpers.MaxHighlightedCommands));
332337
}
333338

334-
var menuCommands = PageViewModel.SelectedResource.Commands.Where(c => !_highlightedCommands.Contains(c) && c.State != CommandViewModelState.Hidden).ToList();
335-
if (menuCommands.Count > 0)
336-
{
337-
foreach (var command in menuCommands)
339+
ResourceMenuItems.AddMenuItems(
340+
_resourceMenuItems,
341+
openingMenuButtonId: null,
342+
PageViewModel.SelectedResource,
343+
NavigationManager,
344+
TelemetryRepository,
345+
GetResourceName,
346+
ControlsStringsLoc,
347+
ResourcesLoc,
348+
buttonId =>
338349
{
339-
var icon = (!string.IsNullOrEmpty(command.IconName) && IconResolver.ResolveIconName(command.IconName, IconSize.Size16, command.IconVariant) is { } i) ? i : null;
340-
341-
_resourceMenuItems.Add(new MenuButtonItem
342-
{
343-
Text = command.DisplayName,
344-
Tooltip = command.DisplayDescription,
345-
Icon = icon,
346-
OnClick = () => ExecuteResourceCommandAsync(command),
347-
IsDisabled = command.State == CommandViewModelState.Disabled || DashboardCommandExecutor.IsExecuting(PageViewModel.SelectedResource!.Name, command.Name)
348-
});
349-
}
350-
}
350+
NavigationManager.NavigateTo(DashboardUrls.ResourcesUrl(resource: PageViewModel.SelectedResource.Name));
351+
return Task.CompletedTask;
352+
},
353+
ExecuteResourceCommandAsync,
354+
(resource, command) => DashboardCommandExecutor.IsExecuting(resource.Name, command.Name),
355+
showConsoleLogsItem: false);
351356
}
352357
}
353358

0 commit comments

Comments
 (0)