-
Notifications
You must be signed in to change notification settings - Fork 462
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Show all resource endpoints on resource grid (#2413)
- Loading branch information
Showing
11 changed files
with
375 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 40 additions & 27 deletions
67
src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,54 @@ | ||
@using Aspire.Dashboard.Model | ||
@using Aspire.Dashboard.Resources | ||
@namespace Aspire.Dashboard.Components | ||
@inject IStringLocalizer<Columns> Loc | ||
|
||
<FluentStack Orientation="Orientation.Vertical"> | ||
@* If we have no endpoints, and the app isn't running anymore or we're not expecting any, then just say None *@ | ||
@if (Resource.Endpoints.Length == 0 && (Resource.State == ResourceStates.FinishedState || Resource.ExpectedEndpointsCount == 0)) | ||
@{ | ||
List<DisplayedEndpoint> displayedEndpoints; | ||
string? additionalMessage = null; | ||
if (Resource.Endpoints.Length == 0 && (Resource.State == ResourceStates.FinishedState || Resource.ExpectedEndpointsCount == 0)) | ||
{ | ||
<span class="long-inner-content">@Loc[nameof(Columns.EndpointsColumnDisplayNone)]</span> | ||
if (Resource.Services.Length == 0) | ||
{ | ||
// If we have no endpoints, and the app isn't running anymore or we're not expecting any, then just say None | ||
additionalMessage = Loc[nameof(Columns.EndpointsColumnDisplayNone)]; | ||
displayedEndpoints = []; | ||
} | ||
else | ||
{ | ||
displayedEndpoints = GetEndpoints(Resource); | ||
} | ||
} | ||
else if (Resource.State != ResourceStates.FinishedState | ||
&& (Resource.ExpectedEndpointsCount is null || Resource.ExpectedEndpointsCount > Resource.Endpoints.Length)) | ||
{ | ||
// If we're expecting more endpoints, say Starting..., unless the app isn't running anymore. | ||
// Don't include service only endpoints in the list until finished loading endpoints. | ||
displayedEndpoints = GetEndpoints(Resource, excludeServices: true); | ||
additionalMessage = Loc[nameof(Columns.EndpointsColumnDisplayPlaceholder)]; | ||
} | ||
else | ||
{ | ||
@* If we have any, regardless of the state, go ahead and display them *@ | ||
foreach (var endpoint in Resource.Endpoints.OrderBy(e => e.ProxyUrl)) | ||
{ | ||
if (HasMultipleReplicas) | ||
displayedEndpoints = GetEndpoints(Resource); | ||
} | ||
} | ||
|
||
<ul class="endpoint-list"> | ||
@foreach (var displayedEndpoint in displayedEndpoints) | ||
{ | ||
<li> | ||
@if (displayedEndpoint.Url != null) | ||
{ | ||
<a href="@endpoint.ProxyUrl" target="_blank" class="long-inner-content">@endpoint.ProxyUrl</a> | ||
<span class="long-inner-content">(<a href="@endpoint.EndpointUrl" target="_blank">@endpoint.EndpointUrl</a>)</span> | ||
<a href="@displayedEndpoint.Url" target="_blank">@displayedEndpoint.Url</a> | ||
} | ||
else | ||
{ | ||
<a href="@endpoint.ProxyUrl" target="_blank" class="long-inner-content">@endpoint.ProxyUrl</a> | ||
@displayedEndpoint.Text | ||
} | ||
} | ||
@* If we're expecting more, say Starting..., unless the app isn't running anymore *@ | ||
if (Resource.State != ResourceStates.FinishedState | ||
&& (Resource.ExpectedEndpointsCount is null || Resource.ExpectedEndpointsCount > Resource.Endpoints.Length)) | ||
{ | ||
<span class="long-inner-content">@Loc[nameof(Columns.EndpointsColumnDisplayPlaceholder)]</span> | ||
} | ||
</li> | ||
} | ||
</FluentStack> | ||
|
||
@code { | ||
[Parameter, EditorRequired] | ||
public required ResourceViewModel Resource { get; set; } | ||
|
||
[Parameter, EditorRequired] | ||
public required bool HasMultipleReplicas { get; set; } | ||
} | ||
@if (!string.IsNullOrEmpty(additionalMessage)) | ||
{ | ||
<li>@additionalMessage</li> | ||
} | ||
</ul> |
27 changes: 27 additions & 0 deletions
27
src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Aspire.Dashboard.Model; | ||
using Microsoft.AspNetCore.Components; | ||
|
||
namespace Aspire.Dashboard.Components; | ||
|
||
public partial class EndpointsColumnDisplay | ||
{ | ||
[Parameter, EditorRequired] | ||
public required ResourceViewModel Resource { get; set; } | ||
|
||
[Parameter, EditorRequired] | ||
public required bool HasMultipleReplicas { get; set; } | ||
|
||
[Inject] | ||
public required ILogger<EndpointsColumnDisplay> Logger { get; init; } | ||
|
||
/// <summary> | ||
/// A resource has services and endpoints. These can overlap. This method attempts to return a single list without duplicates. | ||
/// </summary> | ||
private List<DisplayedEndpoint> GetEndpoints(ResourceViewModel resource, bool excludeServices = false) | ||
{ | ||
return ResourceEndpointHelpers.GetEndpoints(Logger, resource, excludeServices, includeEndpointUrl: false); | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
::deep.endpoint-list { | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
::deep.endpoint-list li { | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Diagnostics; | ||
|
||
namespace Aspire.Dashboard.Model; | ||
|
||
internal static class ResourceEndpointHelpers | ||
{ | ||
/// <summary> | ||
/// A resource has services and endpoints. These can overlap. This method attempts to return a single list without duplicates. | ||
/// </summary> | ||
public static List<DisplayedEndpoint> GetEndpoints(ILogger logger, ResourceViewModel resource, bool excludeServices = false, bool includeEndpointUrl = false) | ||
{ | ||
var displayedEndpoints = new List<DisplayedEndpoint>(); | ||
|
||
if (!excludeServices) | ||
{ | ||
foreach (var service in resource.Services) | ||
{ | ||
displayedEndpoints.Add(new DisplayedEndpoint | ||
{ | ||
Name = service.Name, | ||
Text = service.AddressAndPort, | ||
Address = service.AllocatedAddress, | ||
Port = service.AllocatedPort | ||
}); | ||
} | ||
} | ||
|
||
foreach (var endpoint in resource.Endpoints) | ||
{ | ||
ProcessUrl(logger, resource, displayedEndpoints, endpoint.ProxyUrl, "ProxyUrl"); | ||
if (includeEndpointUrl) | ||
{ | ||
ProcessUrl(logger, resource, displayedEndpoints, endpoint.EndpointUrl, "EndpointUrl"); | ||
} | ||
} | ||
|
||
// Display endpoints with a URL first, then by address and port. | ||
return displayedEndpoints.OrderBy(e => e.Url == null).ThenBy(e => e.Address).ThenBy(e => e.Port).ToList(); | ||
} | ||
|
||
private static void ProcessUrl(ILogger logger, ResourceViewModel resource, List<DisplayedEndpoint> displayedEndpoints, string url, string name) | ||
{ | ||
Uri uri; | ||
try | ||
{ | ||
uri = new Uri(url, UriKind.Absolute); | ||
} | ||
catch (Exception ex) | ||
{ | ||
logger.LogWarning(ex, "Couldn't parse '{Url}' to a URI for resource {ResourceName}.", url, resource.Name); | ||
return; | ||
} | ||
|
||
// There isn't a good way to match services and endpoints other than by address and port. | ||
var existingMatches = displayedEndpoints.Where(e => string.Equals(e.Address, uri.Host, StringComparisons.UrlHost) && e.Port == uri.Port).ToList(); | ||
|
||
if (existingMatches.Count > 0) | ||
{ | ||
foreach (var e in existingMatches) | ||
{ | ||
e.Url = uri.OriginalString; | ||
e.Text = uri.OriginalString; | ||
} | ||
} | ||
else | ||
{ | ||
displayedEndpoints.Add(new DisplayedEndpoint | ||
{ | ||
Name = name, | ||
Text = url, | ||
Address = uri.Host, | ||
Port = uri.Port, | ||
Url = uri.OriginalString | ||
}); | ||
} | ||
} | ||
} | ||
|
||
[DebuggerDisplay("Name = {Name}, Text = {Text}, Address = {Address}:{Port}, Url = {Url}")] | ||
public sealed class DisplayedEndpoint | ||
{ | ||
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; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.