Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add display name and priority support to endpoints #7442

4 changes: 4 additions & 0 deletions playground/TestShop/TestShop.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@

builder.AddProject<Projects.MyFrontend>("frontend")
.WithExternalHttpEndpoints()
.WithModifiedEndpoints(c =>
{
c.DisplayProperties.DisplayName = "TestShop frontend access";
})
Copy link
Member

@JamesNK JamesNK Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bit of a sledge hammer to give names to endpoints from the launch profile.

@davidfowl Do you want to be able to give names to individual endpoints from the launch profile? Is there an existing pattern for enriching EndpointAnnotation instances created from the launch profile with extra values?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so yes.

.WithReference(basketService)
.WithReference(catalogService);

Expand Down
24 changes: 24 additions & 0 deletions playground/TestShop/TestShop.AppHost/ResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Hosting;

public static class ResourceBuilderExtensions
{
public static IResourceBuilder<T> WithModifiedEndpoints<T>(this IResourceBuilder<T> builder, Action<EndpointAnnotation> callback) where T : IResourceWithEndpoints
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this?

{
ArgumentNullException.ThrowIfNull(builder);

if (!builder.Resource.TryGetAnnotationsOfType<EndpointAnnotation>(out var endpoints))
{
return builder;
}

foreach (var endpoint in endpoints)
{
callback(endpoint);
}

return builder;
}
}
52 changes: 44 additions & 8 deletions src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,50 @@
@FilteredEndpoints.Count()
</FluentBadge>
</div>
<PropertyGrid
TItem="DisplayedEndpoint"
Items="@FilteredEndpoints"
ContentAfterValue="(vm) => GetContentAfterValue(vm)"
ValueSort="_endpointValueSort"
IsValueMaskedChanged="@OnValueMaskedChanged"
HighlightText="@_filter"
GridTemplateColumns="1fr 1.5fr" />

@{
var hasAnyEndpointDisplayNames = FilteredEndpoints.Any(e => e.DisplayName != null);
}

<FluentDataGrid TGridItem="DisplayedEndpoint"
ItemKey="@(vm => ((IPropertyGridItem)vm).Key)"
Items="@FilteredEndpoints"
ColumnResizeLabels="@_resizeLabels"
ColumnSortLabels="@_sortLabels"
HeaderCellAsButtonWithMenu="true"
ResizableColumns="true"
ResizeType="DataGridResizeType.Discrete"
Style="width:100%"
RowSize="DataGridRowSize.Medium"
GridTemplateColumns="@(hasAnyEndpointDisplayNames ? "1fr 1.5fr 1.5fr" : "1.5fr 1.5fr")"
ShowHover="true">
<AspireTemplateColumn Sortable="true" SortBy="@(GridSort<DisplayedEndpoint>.ByAscending(i => i.Name))" Title="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
Value="@context.Name"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter" />
</AspireTemplateColumn>
@if (hasAnyEndpointDisplayNames)
{
<AspireTemplateColumn Sortable="true" SortBy="@(GridSort<DisplayedEndpoint>.ByAscending(i => i.Name))" Title="@ControlStringsLoc[nameof(ControlsStrings.DisplayNameColumnHeader)]">
adamint marked this conversation as resolved.
Show resolved Hide resolved
@if (context.DisplayName is not null)
adamint marked this conversation as resolved.
Show resolved Hide resolved
{
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.DisplayNameColumnHeader)]"
Value="@context.DisplayName"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
HighlightText="@_filter" />
}
</AspireTemplateColumn>
}
<AspireTemplateColumn Sortable="true" SortBy="@_endpointValueSort" Title="@ControlStringsLoc[nameof(ControlsStrings.PropertyGridValueColumnHeader)]">
<GridValue ValueDescription="@ControlStringsLoc[nameof(ControlsStrings.NameColumnHeader)]"
adamint marked this conversation as resolved.
Show resolved Hide resolved
Value="@context.OriginalUrlString"
ContentAfterValue="vm => GetContentAfterValue(context)"
EnableHighlighting="@(!string.IsNullOrEmpty(_filter))"
IsMaskedChanged="@(_ => OnValueMaskedChanged(context))"
HighlightText="@_filter" />
</AspireTemplateColumn>
</FluentDataGrid>
</FluentAccordionItem>
@if (Resource.IsContainer())
{
Expand Down
15 changes: 11 additions & 4 deletions src/Aspire.Dashboard/Model/ResourceEndpointHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ public static List<DisplayedEndpoint> GetEndpoints(ResourceViewModel resource, b
endpoints.Add(new DisplayedEndpoint
{
Name = url.Name,
Text = url.Url.OriginalString,
Address = url.Url.Host,
Port = url.Url.Port,
Url = url.Url.Scheme is "http" or "https" ? url.Url.OriginalString : null
Url = url.Url.Scheme is "http" or "https" ? url.Url.OriginalString : null,
SortOrder = url.DisplayProperties?.SortOrder,
DisplayName = url.DisplayProperties?.DisplayName,
OriginalUrlString = url.Url.OriginalString,
Text = url.DisplayProperties?.DisplayName ?? url.Url.OriginalString
});
}
}
Expand All @@ -36,7 +39,8 @@ public static List<DisplayedEndpoint> GetEndpoints(ResourceViewModel resource, b
// - other urls
// - endpoint name
var orderedEndpoints = endpoints
.OrderByDescending(e => e.Url?.StartsWith("https") == true)
.OrderByDescending(e => e.SortOrder ?? 0)
adamint marked this conversation as resolved.
Show resolved Hide resolved
.ThenByDescending(e => e.Url?.StartsWith("https") == true)
.ThenByDescending(e => e.Url != null)
.ThenBy(e => e.Name, StringComparers.EndpointAnnotationName)
.ToList();
Expand All @@ -45,14 +49,17 @@ public static List<DisplayedEndpoint> GetEndpoints(ResourceViewModel resource, b
}
}

[DebuggerDisplay("Name = {Name}, Text = {Text}, Address = {Address}:{Port}, Url = {Url}")]
[DebuggerDisplay("Name = {Name}, Text = {Text}, Address = {Address}:{Port}, Url = {Url}, DisplayName = {DisplayName}, OriginalUrlString = {OriginalUrlString}, SortOrder = {SortOrder}")]
public sealed class DisplayedEndpoint : IPropertyGridItem
{
public required string Name { get; set; }
public required string Text { get; set; }
public string? Address { get; set; }
public int? Port { get; set; }
public string? Url { get; set; }
public int? SortOrder { get; set; }
public string? DisplayName { get; set; }
public required string OriginalUrlString { get; set; }

/// <summary>
/// Don't display a plain string value here. The URL will be displayed as a hyperlink
Expand Down
6 changes: 5 additions & 1 deletion src/Aspire.Dashboard/Model/ResourceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,18 +324,22 @@ public sealed class UrlViewModel
public string Name { get; }
public Uri Url { get; }
public bool IsInternal { get; }
public UrlDisplayPropertiesViewModel? DisplayProperties { get; }

public UrlViewModel(string name, Uri url, bool isInternal)
public UrlViewModel(string name, Uri url, bool isInternal, UrlDisplayPropertiesViewModel? displayProperties = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(url);

Name = name;
Url = url;
IsInternal = isInternal;
DisplayProperties = displayProperties;
}
}

public record UrlDisplayPropertiesViewModel(string? DisplayName, int? SortOrder);
adamint marked this conversation as resolved.
Show resolved Hide resolved

public sealed record class VolumeViewModel(int index, string Source, string Target, string MountType, bool IsReadOnly) : IPropertyGridItem
{
string IPropertyGridItem.Name => Source;
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/ResourceService/Partials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ ImmutableArray<UrlViewModel> GetUrls()
return (from u in Urls
let parsedUri = Uri.TryCreate(u.FullUrl, UriKind.Absolute, out var uri) ? uri : null
where parsedUri != null
select new UrlViewModel(u.Name, parsedUri, u.IsInternal))
select new UrlViewModel(u.Name, parsedUri, u.IsInternal, u.DisplayProperties is not null ? new UrlDisplayPropertiesViewModel(u.DisplayProperties.HasDisplayName ? u.DisplayProperties.DisplayName : null, u.DisplayProperties.HasSortOrder ? u.DisplayProperties.SortOrder : null) : null))
adamint marked this conversation as resolved.
Show resolved Hide resolved
.ToImmutableArray();
}

Expand Down
9 changes: 9 additions & 0 deletions src/Aspire.Dashboard/Resources/ControlsStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/Aspire.Dashboard/Resources/ControlsStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@
<data name="NameColumnHeader" xml:space="preserve">
<value>Name</value>
</data>
<data name="DisplayNameColumnHeader" xml:space="preserve">
<value>Display name</value>
</data>
<data name="PropertyGridValueColumnHeader" xml:space="preserve">
<value>Value</value>
</data>
Expand Down Expand Up @@ -437,4 +440,4 @@
<data name="ClearPendingSelectedResource" xml:space="preserve">
<value>Remove for resource</value>
</data>
</root>
</root>
5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Dashboard/Resources/xlf/ControlsStrings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,11 @@ public static IResourceBuilder<AzureCosmosDBEmulatorResource> WithDataExplorer(t
endpoint.UriScheme = "http";
endpoint.TargetPort = 1234;
endpoint.Port = port;
endpoint.DisplayProperties = new EndpointDisplayProperties { DisplayName = "Data Explorer" };
});
}

/// <summary>
/// <summary>
/// Configures the resource to use access key authentication with Azure Cosmos DB.
/// </summary>
/// <param name="builder">The Azure Cosmos DB resource builder.</param>
Expand Down
10 changes: 9 additions & 1 deletion src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,16 @@ public sealed record EnvironmentVariableSnapshot(string Name, string? Value, boo
/// <param name="Name">Name of the url.</param>
/// <param name="Url">The full uri.</param>
/// <param name="IsInternal">Determines if this url is internal.</param>
/// <param name="DisplayProperties">The UI display properties for the url.</param>
[DebuggerDisplay("{Url}", Name = "{Name}")]
public sealed record UrlSnapshot(string Name, string Url, bool IsInternal);
public sealed record UrlSnapshot(string Name, string Url, bool IsInternal, UrlDisplayPropertiesSnapshot? DisplayProperties = null);
adamint marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// A snapshot of the display properties for a url.
/// </summary>
/// <param name="DisplayName">The display name of the url.</param>
/// <param name="SortOrder">The order of the url in UI. Higher numbers are displayed first in the UI.</param>
public sealed record UrlDisplayPropertiesSnapshot(string? DisplayName = null, int? SortOrder = null);

/// <summary>
/// A snapshot of a volume, mounted to a container.
Expand Down
Loading
Loading