diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor b/src/Aspire.Dashboard/Components/Pages/Resources.razor index 0ed6628c12..2721db793a 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor @@ -1,11 +1,12 @@ @page "/" @using Aspire.Dashboard.Components.ResourcesGridColumns -@using Aspire.Dashboard.Model @using Aspire.Dashboard.Resources @using Aspire.Dashboard.Utils @using System.Globalization +@using Humanizer @inject IStringLocalizer Loc @inject IStringLocalizer ControlsStringsLoc +@inject IStringLocalizer ColumnsLoc @@ -57,21 +58,28 @@ } - - + + - + - - + + @if (GetSourceColumnValueAndTooltip(context) is { } columnDisplay) + { + + } - - + + @ControlsStringsLoc[ControlsStrings.ViewAction] diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs index eb44c3c8f0..c44deee666 100644 --- a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs @@ -4,9 +4,11 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; +using System.Text; using Aspire.Dashboard.Model; using Aspire.Dashboard.Otlp.Model; using Aspire.Dashboard.Otlp.Storage; +using Aspire.Dashboard.Resources; using Aspire.Dashboard.Utils; using Microsoft.AspNetCore.Components; using Microsoft.FluentUI.AspNetCore.Components; @@ -294,6 +296,86 @@ private async Task ExecuteResourceCommandAsync(ResourceViewModel resource, Comma } } + private static (string Value, string? ContentAfterValue, string ValueToCopy, string Tooltip)? GetSourceColumnValueAndTooltip(ResourceViewModel resource) + { + // NOTE projects are also executables, so we have to check for projects first + if (resource.IsProject() && resource.TryGetProjectPath(out var projectPath)) + { + return (Value: Path.GetFileName(projectPath), ContentAfterValue: null, ValueToCopy: projectPath, Tooltip: projectPath); + } + + if (resource.TryGetExecutablePath(out var executablePath)) + { + resource.TryGetExecutableArguments(out var arguments); + var argumentsString = arguments.IsDefaultOrEmpty ? "" : string.Join(" ", arguments); + var fullCommandLine = $"{executablePath} {argumentsString}"; + + return (Value: Path.GetFileName(executablePath), ContentAfterValue: argumentsString, ValueToCopy: fullCommandLine, Tooltip: fullCommandLine); + } + + if (resource.TryGetContainerImage(out var containerImage)) + { + return (Value: containerImage, ContentAfterValue: null, ValueToCopy: containerImage, Tooltip: containerImage); + } + + if (resource.Properties.TryGetValue(KnownProperties.Resource.Source, out var value) && value.HasStringValue) + { + return (Value: value.StringValue, ContentAfterValue: null, ValueToCopy: value.StringValue, Tooltip: value.StringValue); + } + + return null; + } + + private string GetEndpointsTooltip(ResourceViewModel resource) + { + var displayedEndpoints = GetDisplayedEndpoints(resource, out var additionalMessage); + + if (additionalMessage is not null) + { + return additionalMessage; + } + + if (displayedEndpoints.Count == 1) + { + return displayedEndpoints.First().Text; + } + + var maxShownEndpoints = 3; + var tooltipBuilder = new StringBuilder(string.Join(", ", displayedEndpoints.Take(maxShownEndpoints).Select(endpoint => endpoint.Text))); + + if (displayedEndpoints.Count > maxShownEndpoints) + { + tooltipBuilder.Append(CultureInfo.CurrentCulture, $" + {displayedEndpoints.Count - maxShownEndpoints}"); + } + + return tooltipBuilder.ToString(); + } + + private List GetDisplayedEndpoints(ResourceViewModel resource, out string? additionalMessage) + { + if (resource.Urls.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 = ColumnsLoc[nameof(Columns.EndpointsColumnDisplayNone)]; + return []; + } + + additionalMessage = null; + + // Make sure that endpoints have a consistent ordering. Show https first, then everything else. + return [.. GetEndpoints(resource) + .OrderByDescending(e => e.Url?.StartsWith("https") == true) + .ThenBy(e=> e.Url ?? e.Text)]; + } + + /// + /// A resource has services and endpoints. These can overlap. This method attempts to return a single list without duplicates. + /// + private static List GetEndpoints(ResourceViewModel resource) + { + return ResourceEndpointHelpers.GetEndpoints(resource, includeInteralUrls: false); + } + public async ValueTask DisposeAsync() { _watchTaskCancellationTokenSource.Cancel(); diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor index f435571511..8480a1e7bd 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor @@ -1,26 +1,9 @@ @using Aspire.Dashboard.Model -@using Aspire.Dashboard.Resources @namespace Aspire.Dashboard.Components -@inject IStringLocalizer Loc -@{ - List displayedEndpoints; - string? additionalMessage = null; - if (Resource.Urls.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); - } -} - -@if (displayedEndpoints.Count == 1) +@if (DisplayedEndpoints.Count == 1) { - var displayedEndpoint = displayedEndpoints[0]; + var displayedEndpoint = DisplayedEndpoints[0]; if (displayedEndpoint.Url != null) { @displayedEndpoint.Text @@ -32,15 +15,12 @@ } else { - // Make sure that endpoints have a consistent ordering. Show https first, then everything else. - displayedEndpoints = displayedEndpoints.OrderByDescending(e => e.Url?.StartsWith("https") == true).ThenBy(e=> e.Url ?? e.Text).ToList(); - - @for (var i = 0; i < displayedEndpoints.Count; i++) + @for (var i = 0; i < DisplayedEndpoints.Count; i++) { - var displayedEndpoint = displayedEndpoints[i]; - var isLast = i == displayedEndpoints.Count - 1; + var displayedEndpoint = DisplayedEndpoints[i]; + var isLast = i == DisplayedEndpoints.Count - 1; @if (displayedEndpoint.Url != null) @@ -94,7 +74,7 @@ else } -@if (!string.IsNullOrEmpty(additionalMessage)) +@if (!string.IsNullOrEmpty(AdditionalMessage)) { -
@additionalMessage
+
@AdditionalMessage
} diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor.cs b/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor.cs index 44c8cb99bb..ddbd1f850a 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor.cs +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/EndpointsColumnDisplay.razor.cs @@ -14,16 +14,14 @@ public partial class EndpointsColumnDisplay [Parameter, EditorRequired] public required bool HasMultipleReplicas { get; set; } + [Parameter, EditorRequired] + public required IList DisplayedEndpoints { get; set; } + + [Parameter] + public string? AdditionalMessage { get; set; } + [Inject] public required ILogger Logger { get; init; } private bool _popoverVisible; - - /// - /// A resource has services and endpoints. These can overlap. This method attempts to return a single list without duplicates. - /// - private static List GetEndpoints(ResourceViewModel resource) - { - return ResourceEndpointHelpers.GetEndpoints(resource, includeInteralUrls: false); - } } diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/SourceColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/SourceColumnDisplay.razor index 769e2785a5..8190be0805 100644 --- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/SourceColumnDisplay.razor +++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/SourceColumnDisplay.razor @@ -3,48 +3,27 @@ @using Aspire.Dashboard.Resources @inject IStringLocalizer Loc -@if (Resource.IsProject() && Resource.TryGetProjectPath(out var projectPath)) +@if (ContentAfterValue is not null) { - // NOTE projects are also executables, so we have to check for projects first - -} -else if (Resource.TryGetExecutablePath(out var executablePath)) -{ - Resource.TryGetExecutableArguments(out var arguments); - var argumentsString = arguments.IsDefaultOrEmpty ? "" : string.Join(" ", arguments); - var fullCommandLine = $"{executablePath} {argumentsString}"; - - + ToolTip="@Tooltip"> - @argumentsString + @ContentAfterValue } -else if (Resource.TryGetContainerImage(out var containerImage)) -{ - -} -else if (Resource.Properties.TryGetValue(KnownProperties.Resource.Source, out var value) && value.HasStringValue) +else { - + PreCopyToolTip="@Loc[nameof(Columns.SourceColumnSourceCopyFullPathToClipboard)]" + ToolTip="@Tooltip" /> } @code { @@ -53,4 +32,17 @@ else if (Resource.Properties.TryGetValue(KnownProperties.Resource.Source, out va [Parameter, EditorRequired] public required string FilterText { get; set; } + + [Parameter, EditorRequired] + public required string Value { get; set; } + + [Parameter] + public required string? ContentAfterValue { get; set; } + + [Parameter, EditorRequired] + public required string ValueToCopy { get; set; } + + [Parameter, EditorRequired] + public required string Tooltip { get; set; } + }