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

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
12 changes: 9 additions & 3 deletions src/Aspire.Dashboard/Model/ResourceEndpointHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ 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,
Priority = url.Priority,
OriginalUrlString = url.Url.OriginalString,
Text = url.DisplayName ?? url.Url.OriginalString
});
}
}
Expand All @@ -36,7 +38,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.Priority ?? 0)
.ThenByDescending(e => e.Url?.StartsWith("https") == true)
.ThenByDescending(e => e.Url != null)
.ThenBy(e => e.Name, StringComparers.EndpointAnnotationName)
.ToList();
Expand All @@ -53,6 +56,9 @@ public sealed class DisplayedEndpoint : IPropertyGridItem
public string? Address { get; set; }
public int? Port { get; set; }
public string? Url { get; set; }
public int? Priority { 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,15 +324,19 @@ public sealed class UrlViewModel
public string Name { get; }
public Uri Url { get; }
public bool IsInternal { get; }
public string? DisplayName { get; }
public int? Priority { get; }

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

Name = name;
Url = url;
IsInternal = isInternal;
DisplayName = displayName;
Priority = priority;
}
}

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.HasDisplayName ? u.DisplayName : null, u.HasPriority ? u.Priority : null))
.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.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
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,10 @@ 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="DisplayName">The display name of the url.</param>
/// <param name="Priority">The order of the url in UI.</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, string? DisplayName = null, int? Priority = null);

/// <summary>
/// A snapshot of a volume, mounted to a container.
Expand Down
16 changes: 15 additions & 1 deletion src/Aspire.Hosting/ApplicationModel/EndpointAnnotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public sealed class EndpointAnnotation : IResourceAnnotation
/// <param name="targetPort">This is the port the resource is listening on. If the endpoint is used for the container, it is the container port.</param>
/// <param name="isExternal">Indicates that this endpoint should be exposed externally at publish time.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
public EndpointAnnotation(ProtocolType protocol, string? uriScheme = null, string? transport = null, [EndpointName] string? name = null, int? port = null, int? targetPort = null, bool? isExternal = null, bool isProxied = true)
/// <param name="displayName">Display name of the endpoint, to be displayed in the Aspire Dashboard.</param>
/// <param name="priority">Integer to control visual ordering of endpoints in the Aspire Dashboard.</param>
adamint marked this conversation as resolved.
Show resolved Hide resolved
public EndpointAnnotation(ProtocolType protocol, string? uriScheme = null, string? transport = null, [EndpointName] string? name = null, int? port = null, int? targetPort = null, bool? isExternal = null, bool isProxied = true, string? displayName = null, int? priority = null)
{
// If the URI scheme is null, we'll adopt either udp:// or tcp:// based on the
// protocol. If the name is null, we'll use the URI scheme as the default. This
Expand All @@ -51,6 +53,8 @@ public EndpointAnnotation(ProtocolType protocol, string? uriScheme = null, strin
_targetPort = targetPort;
IsExternal = isExternal ?? false;
IsProxied = isProxied;
DisplayName = displayName;
Priority = priority;
}

/// <summary>
Expand Down Expand Up @@ -131,6 +135,16 @@ public string Transport
/// <remarks>Defaults to <c>true</c>.</remarks>
public bool IsProxied { get; set; } = true;

/// <summary>
/// Display name of the endpoint, to be displayed in the Aspire Dashboard.
/// </summary>
public string? DisplayName { get; set; }

/// <summary>
/// Integer to control visual ordering of endpoints in the Aspire Dashboard.
/// </summary>
public int? Priority { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the endpoint is from a launch profile.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/EndpointReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ public EndpointReferenceExpression Property(EndpointProperty property)
/// </summary>
public string Url => AllocatedEndpoint.UriString;

/// <summary>
/// Gets the display name for this endpoint.
/// </summary>
public string? DisplayName => EndpointAnnotation.DisplayName;

/// <summary>
/// Gets the visual priority for this endpoint.
/// </summary>
public int? Priority => EndpointAnnotation.Priority;

internal AllocatedEndpoint AllocatedEndpoint =>
GetAllocatedEndpoint()
?? throw new InvalidOperationException($"The endpoint `{EndpointName}` is not allocated for the resource `{Resource.Name}`.");
Expand Down
Loading
Loading