Skip to content

Commit 9abbb30

Browse files
authored
Display nested resources in the dashboard (#6589)
1 parent 99341c9 commit 9abbb30

25 files changed

+502
-150
lines changed

playground/Stress/Stress.AppHost/Program.cs

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

99
for (var i = 0; i < 10; i++)
1010
{
11-
builder.AddTestResource($"test-{i:0000}");
11+
var name = $"test-{i:0000}";
12+
var rb = builder.AddTestResource(name);
13+
IResource parent = rb.Resource;
14+
15+
for (int j = 0; j < 3; j++)
16+
{
17+
name = name + $"-n{j}";
18+
var nestedRb = builder.AddNestedResource(name, parent);
19+
parent = nestedRb.Resource;
20+
}
1221
}
1322

1423
var serviceBuilder = builder.AddProject<Projects.Stress_ApiService>("stress-apiservice", launchProfileName: null);

playground/Stress/Stress.AppHost/Stress.AppHost.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<Compile Include="..\..\KnownResourceNames.cs" Link="KnownResourceNames.cs" />
12+
<Compile Include="$(SharedDir)KnownResourceNames.cs" Link="KnownResourceNames.cs" />
13+
<Compile Include="$(SharedDir)Model\KnownProperties.cs" Link="KnownProperties.cs" />
1314
</ItemGroup>
1415

1516
<ItemGroup>

playground/Stress/Stress.AppHost/TestResource.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Globalization;
5+
using Aspire.Dashboard.Model;
56
using Aspire.Hosting.Lifecycle;
67
using Microsoft.Extensions.Logging;
78

@@ -25,6 +26,24 @@ public static IResourceBuilder<TestResource> AddTestResource(this IDistributedAp
2526

2627
return rb;
2728
}
29+
30+
public static IResourceBuilder<TestNestedResource> AddNestedResource(this IDistributedApplicationBuilder builder, string name, IResource parent)
31+
{
32+
var rb = builder.AddResource(new TestNestedResource(name, parent))
33+
.WithInitialState(new()
34+
{
35+
ResourceType = "Test Nested Resource",
36+
State = "Starting",
37+
Properties = [
38+
new("P1", "P2"),
39+
new(CustomResourceKnownProperties.Source, "Custom"),
40+
new(KnownProperties.Resource.ParentName, parent.Name)
41+
]
42+
})
43+
.ExcludeFromManifest();
44+
45+
return rb;
46+
}
2847
}
2948

3049
internal sealed class TestResourceLifecycleHook(ResourceNotificationService notificationService, ResourceLoggerService loggerService) : IDistributedApplicationLifecycleHook, IAsyncDisposable
@@ -81,3 +100,8 @@ sealed class TestResource(string name) : Resource(name)
81100
{
82101

83102
}
103+
104+
sealed class TestNestedResource(string name, IResource parent) : Resource(name), IResourceWithParent
105+
{
106+
public IResource Parent { get; } = parent;
107+
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
<FluentCheckbox Label="@ControlsStringsLoc[nameof(ControlsStrings.LabelAll)]"
1010
ThreeState="true"
1111
ShowIndeterminate="false"
12-
onkeydown="e => console.log(e)"
1312
ThreeStateOrderUncheckToIntermediate="true"
1413
@bind-CheckState:get="@AreAllTypesVisible()"
15-
@bind-CheckState:set="@OnAllResourceTypesCheckedChanged"
14+
@bind-CheckState:set="@OnAllResourceTypesCheckedChangedAsync"
1615
/>
1716
@foreach (var (resourceType, _) in AllResourceTypes)
1817
{
@@ -33,7 +32,7 @@
3332
public required ConcurrentDictionary<string,bool> VisibleResourceTypes { get; set; }
3433

3534
[Parameter, EditorRequired]
36-
public required Action<bool?> OnAllResourceTypesCheckedChanged { get; set; }
35+
public required Func<bool?, Task> OnAllResourceTypesCheckedChangedAsync { get; set; }
3736

3837
[Parameter, EditorRequired]
3938
public required Func<string, bool,Task> OnResourceTypeVisibilityChangedAsync { get; set; }

src/Aspire.Dashboard/Components/Layout/AspirePageContentLayout.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
{
1313
@PageTitleSection
1414

15-
<FluentToolbar Orientation="Orientation.Horizontal">
15+
<FluentToolbar Class="main-toolbar" Orientation="Orientation.Horizontal">
1616
@ToolbarSection
1717
</FluentToolbar>
1818
}

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

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
<SelectResourceTypes AllResourceTypes="_allResourceTypes"
4949
VisibleResourceTypes="_visibleResourceTypes"
50-
OnAllResourceTypesCheckedChanged="@(b => AreAllTypesVisible = b)"
50+
OnAllResourceTypesCheckedChangedAsync="@OnAllResourceTypesCheckedChangedAsync"
5151
AreAllTypesVisible="@(() => AreAllTypesVisible)"
5252
OnResourceTypeVisibilityChangedAsync="@OnResourceTypeVisibilityChangedAsync" />
5353
</div>
@@ -58,12 +58,11 @@
5858
<FluentPopover AnchorId="typeFilterButton" @bind-Open="_isTypeFilterVisible" AutoFocus="true" FixedPlacement="true">
5959
<Header>@Loc[nameof(Dashboard.Resources.Resources.ResourcesResourceTypesHeader)]</Header>
6060
<Body>
61-
<SelectResourceTypes
62-
AllResourceTypes="_allResourceTypes"
63-
VisibleResourceTypes="_visibleResourceTypes"
64-
OnAllResourceTypesCheckedChanged="@(b => AreAllTypesVisible = b)"
65-
AreAllTypesVisible="@(() => AreAllTypesVisible)"
66-
OnResourceTypeVisibilityChangedAsync="@OnResourceTypeVisibilityChangedAsync" />
61+
<SelectResourceTypes AllResourceTypes="_allResourceTypes"
62+
VisibleResourceTypes="_visibleResourceTypes"
63+
OnAllResourceTypesCheckedChangedAsync="@OnAllResourceTypesCheckedChangedAsync"
64+
AreAllTypesVisible="@(() => AreAllTypesVisible)"
65+
OnResourceTypeVisibilityChangedAsync="@OnResourceTypeVisibilityChangedAsync" />
6766
</Body>
6867
</FluentPopover>
6968

@@ -75,34 +74,48 @@
7574
OnResize="@(r => _manager.SetWidthFraction(r.Orientation == Orientation.Horizontal ? r.Panel1Fraction : 1))">
7675
<Summary>
7776
<GridColumnManager @ref="_manager" Columns="@_gridColumns">
78-
<FluentDataGrid ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(ControlsStringsLoc)"
77+
<FluentDataGrid @ref="_dataGrid"
78+
ResizeLabel="@AspireFluentDataGridHeaderCell.GetResizeLabel(ControlsStringsLoc)"
7979
ResizeType="DataGridResizeType.Discrete"
8080
Virtualize="true"
8181
GenerateHeader="GenerateHeaderOption.Sticky"
8282
ItemSize="46"
83-
Items="@FilteredResources"
83+
ItemsProvider="@GetData"
8484
ResizableColumns="true"
8585
GridTemplateColumns="@_manager.GetGridTemplateColumns()"
86-
RowClass="GetRowClass"
86+
RowClass="@(r => GetRowClass(r.Resource))"
8787
Loading="_isLoading"
8888
ShowHover="true"
89-
TGridItem="ResourceViewModel"
90-
ItemKey="@(r => r.Name)"
91-
OnRowClick="@(r => r.ExecuteOnDefault(d => ShowResourceDetailsAsync(d, buttonId: null)))"
92-
Class="enable-row-click">
89+
TGridItem="ResourceGridViewModel"
90+
ItemKey="@(r => r.Resource.Name)"
91+
OnRowClick="@(r => r.ExecuteOnDefault(d => ShowResourceDetailsAsync(d.Resource, buttonId: null)))"
92+
Class="main-grid enable-row-click">
9393
<ChildContent>
94-
<AspirePropertyColumn ColumnId="@TypeColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesTypeColumnHeader)]" Property="@(c => c.ResourceType)" Sortable="true" Tooltip="true" TooltipText="@(c => c.ResourceType)" />
95-
<AspireTemplateColumn ColumnId="@NameColumn" ColumnManager="@_manager" Title="@ControlsStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Sortable="true" SortBy="@_nameSort" Tooltip="true" TooltipText="@(c => GetResourceName(c))">
96-
<ResourceNameDisplay Resource="context" FilterText="@_filter" FormatName="GetResourceName" />
94+
<AspireTemplateColumn ColumnId="@NameColumn" ColumnManager="@_manager" Title="@ControlsStringsLoc[nameof(ControlsStrings.NameColumnHeader)]" Sortable="true" SortBy="@_nameSort" Tooltip="true" TooltipText="@(c => GetResourceName(c.Resource))" Class="expand-col">
95+
@{
96+
var indent = context.Depth * 16;
97+
}
98+
<span class="resources-name-container" style="margin-left: @(indent)px;">
99+
<span @onclick:stopPropagation="true" class="main-grid-expand-container @(context.IsCollapsed ? "main-grid-collapsed" : "main-grid-expanded")">
100+
@if (context.Children.Count > 0)
101+
{
102+
<FluentButton Appearance="Appearance.Lightweight" Class="main-grid-expand-button" OnClick="@(() => OnToggleCollapse(context))">
103+
<FluentIcon Icon="Icons.Regular.Size12.ChevronRight" Color="Color.Neutral" />
104+
</FluentButton>
105+
}
106+
</span>
107+
<ResourceNameDisplay Resource="context.Resource" FilterText="@_filter" FormatName="GetResourceName" />
108+
</span>
97109
</AspireTemplateColumn>
98-
<AspireTemplateColumn ColumnId="@StateColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort" Tooltip="true" TooltipText="@(c => ResourceStateViewModel.GetResourceStateTooltip(c, ColumnsLoc))">
99-
<StateColumnDisplay Resource="@context" UnviewedErrorCounts="@_applicationUnviewedErrorCounts" />
110+
<AspireTemplateColumn ColumnId="@StateColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStateColumnHeader)]" Sortable="true" SortBy="@_stateSort" Tooltip="true" TooltipText="@(c => ResourceStateViewModel.GetResourceStateTooltip(c.Resource, ColumnsLoc))">
111+
<StateColumnDisplay Resource="@context.Resource" UnviewedErrorCounts="@_applicationUnviewedErrorCounts" />
100112
</AspireTemplateColumn>
101-
<AspireTemplateColumn ColumnId="@StartTimeColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStartTimeColumnHeader)]" Sortable="true" SortBy="@_startTimeSort" TooltipText="@(context => context.CreationTimeStamp != null ? FormatHelpers.FormatDateTime(TimeProvider, context.CreationTimeStamp.Value, MillisecondsDisplay.None, CultureInfo.CurrentCulture) : null)" Tooltip="true">
102-
<StartTimeColumnDisplay Resource="@context" />
113+
<AspireTemplateColumn ColumnId="@StartTimeColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesStartTimeColumnHeader)]" Sortable="true" SortBy="@_startTimeSort" TooltipText="@(context => context.Resource.CreationTimeStamp != null ? FormatHelpers.FormatDateTime(TimeProvider, context.Resource.CreationTimeStamp.Value, MillisecondsDisplay.None, CultureInfo.CurrentCulture) : null)" Tooltip="true">
114+
<StartTimeColumnDisplay Resource="@context.Resource" />
103115
</AspireTemplateColumn>
104-
<AspireTemplateColumn ColumnId="@SourceColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesSourceColumnHeader)]" Tooltip="true" TooltipText="@(ctx => GetSourceColumnValueAndTooltip(ctx)?.Tooltip)">
105-
@if (GetSourceColumnValueAndTooltip(context) is { } columnDisplay)
116+
<AspirePropertyColumn ColumnId="@TypeColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesTypeColumnHeader)]" Property="@(c => c.Resource.ResourceType)" Sortable="true" SortBy="@_typeSort" IsDefaultSortColumn="true" Tooltip="true" TooltipText="@(c => c.Resource.ResourceType)" />
117+
<AspireTemplateColumn ColumnId="@SourceColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesSourceColumnHeader)]" Tooltip="true" TooltipText="@(ctx => GetSourceColumnValueAndTooltip(ctx.Resource)?.Tooltip)">
118+
@if (GetSourceColumnValueAndTooltip(context.Resource) is { } columnDisplay)
106119
{
107120
<SourceColumnDisplay FilterText="@_filter" Value="@columnDisplay.Value" ContentAfterValue="@columnDisplay.ContentAfterValue" ValueToCopy="@columnDisplay.ValueToCopy" Tooltip="@columnDisplay.Tooltip" />
108121
}
@@ -111,17 +124,17 @@
111124
<span class="empty-data"></span>
112125
}
113126
</AspireTemplateColumn>
114-
<AspireTemplateColumn ColumnId="@EndpointsColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesEndpointsColumnHeader)]" Tooltip="true" TooltipText="@(ctx => GetEndpointsTooltip(ctx))">
115-
<EndpointsColumnDisplay Resource="context"
116-
HasMultipleReplicas="HasMultipleReplicas(context)"
117-
DisplayedEndpoints="GetDisplayedEndpoints(context)" />
127+
<AspireTemplateColumn ColumnId="@EndpointsColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesEndpointsColumnHeader)]" Tooltip="true" TooltipText="@(ctx => GetEndpointsTooltip(ctx.Resource))">
128+
<EndpointsColumnDisplay Resource="context.Resource"
129+
HasMultipleReplicas="HasMultipleReplicas(context.Resource)"
130+
DisplayedEndpoints="GetDisplayedEndpoints(context.Resource)" />
118131
</AspireTemplateColumn>
119132
<AspireTemplateColumn ColumnId="@ActionsColumn" ColumnManager="@_manager" Title="@Loc[nameof(Dashboard.Resources.Resources.ResourcesActionsColumnHeader)]" Class="no-ellipsis">
120133
<div class="grid-action-container" @onclick:stopPropagation="true">
121-
<ResourceActions Commands="@context.Commands"
122-
CommandSelected="async (command) => await ExecuteResourceCommandAsync(context, command)"
123-
OnViewDetails="@((buttonId) => ShowResourceDetailsAsync(context, buttonId))"
124-
Resource="context"
134+
<ResourceActions Commands="@context.Resource.Commands"
135+
CommandSelected="async (command) => await ExecuteResourceCommandAsync(context.Resource, command)"
136+
OnViewDetails="@((buttonId) => ShowResourceDetailsAsync(context.Resource, buttonId))"
137+
Resource="context.Resource"
125138
GetResourceName="GetResourceName"
126139
MaxHighlightedCount="_maxHighlightedCount" />
127140
</div>

0 commit comments

Comments
 (0)