Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@namespace Aspire.Dashboard.Components
@using Aspire.Dashboard.Model
@using Microsoft.FluentUI.AspNetCore.Components
@using Aspire.Dashboard.Model.Otlp

@if (ViewportInformation.IsDesktop)
{
Expand Down Expand Up @@ -29,7 +29,7 @@
}
}

<FluentButton Appearance="Appearance.Lightweight" Title="@Loc[nameof(Resources.Resources.ResourceActionConsoleLogsText)]" OnClick="@(() => NavigationManager.NavigateTo(Aspire.Dashboard.Utils.DashboardUrls.ConsoleLogsUrl(resource: Resource.Name)))">
<FluentButton Appearance="Appearance.Lightweight" Title="@Loc[nameof(Resources.Resources.ResourceActionConsoleLogsText)]" OnClick="@(() => NavigationManager.NavigateTo(Aspire.Dashboard.Utils.DashboardUrls.ConsoleLogsUrl(resource: ResourceViewModel.GetResourceName(Resource, ResourceByName))))">
<FluentIcon Value="@s_consoleLogsIcon" />
</FluentButton>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Otlp.Storage;
using Microsoft.AspNetCore.Components;
Expand Down Expand Up @@ -46,6 +47,9 @@ public partial class ResourceActions : ComponentBase
[Parameter]
public required int MaxHighlightedCount { get; set; }

[Parameter]
public required ConcurrentDictionary<string, ResourceViewModel> ResourceByName { get; set; }

[CascadingParameter]
public required ViewportInformation ViewportInformation { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
@using Aspire.Dashboard.Components.Controls.Grid
@using Aspire.Dashboard.Model
@using Aspire.Dashboard.Otlp.Model
@using Aspire.Dashboard.Resources
@using Aspire.Dashboard.Utils
@using Humanizer
@using Microsoft.Extensions.Diagnostics.HealthChecks
@using System.Net
@using Aspire.Dashboard.Model.Otlp

@inject IStringLocalizer<ControlsStrings> ControlStringsLoc
@inject IStringLocalizer<Resources> Loc

<div class="resource-details-layout">

<FluentToolbar Orientation="Orientation.Horizontal">
<FluentAnchor Appearance="Appearance.Lightweight" Href="@DashboardUrls.ConsoleLogsUrl(Resource?.Name)" slot="end">@Loc[nameof(Resources.ResourceDetailsViewConsoleLogs)]</FluentAnchor>
<FluentAnchor Appearance="Appearance.Lightweight" Href="@DashboardUrls.ConsoleLogsUrl(ResourceViewModel.GetResourceName(Resource, ResourceByName))" slot="end">@Loc[nameof(Resources.ResourceDetailsViewConsoleLogs)]</FluentAnchor>

@if (ShowSpecOnlyToggle)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class="@LabelClass" />

<FluentSelect Items="Resources"
Disabled="@(Resources is null)"
Id="@_selectId"
OptionValue="@(c => c!.Name)"
OptionDisabled="@(c => !CanSelectGrouping && c!.Id?.Type is Otlp.Model.OtlpApplicationType.ResourceGrouping)"
Expand Down
10 changes: 5 additions & 5 deletions src/Aspire.Dashboard/Components/Controls/ResourceSelect.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public partial class ResourceSelect
private readonly string _selectId = $"resource-select-{Guid.NewGuid():N}";

[Parameter]
public IEnumerable<SelectViewModel<ResourceTypeDetails>> Resources { get; set; } = default!;
public IEnumerable<SelectViewModel<ResourceTypeDetails>>? Resources { get; set; }

[Parameter]
public SelectViewModel<ResourceTypeDetails> SelectedResource { get; set; } = default!;
public SelectViewModel<ResourceTypeDetails>? SelectedResource { get; set; }

[Parameter]
public EventCallback<SelectViewModel<ResourceTypeDetails>> SelectedResourceChanged { get; set; }
Expand All @@ -33,12 +33,12 @@ public partial class ResourceSelect
[Parameter]
public string? LabelClass { get; set; }

private async Task SelectedResourceChangedCore()
private Task SelectedResourceChangedCore()
{
await SelectedResourceChanged.InvokeAsync(SelectedResource);
return InvokeAsync(() => SelectedResourceChanged.InvokeAsync(SelectedResource));
}

private static void ValuedChanged(string value)
private static void ValuedChanged(string? value)
{
// Do nothing. Required for bunit change to trigger SelectedOptionChanged.
}
Expand Down
25 changes: 15 additions & 10 deletions src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using Aspire.Dashboard.Extensions;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Model.Otlp;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Resources;
using Aspire.Dashboard.Utils;
Expand Down Expand Up @@ -188,7 +187,7 @@ async Task TrackResourceSnapshotsAsync()
// Set loading task result if the selected resource is already in the snapshot or there is no selected resource.
if (ResourceName != null)
{
if (_resourceByName.TryGetValue(ResourceName, out var selectedResource))
if (ResourceViewModel.TryGetResourceByName(ResourceName, _resourceByName, out var selectedResource))
{
SetSelectedResourceOption(selectedResource);
}
Expand Down Expand Up @@ -232,16 +231,20 @@ await InvokeAsync(() =>

void SetSelectedResourceOption(ResourceViewModel resource)
{
Debug.Assert(_resources is not null);

PageViewModel.SelectedOption = _resources.Single(option => option.Id?.Type is not OtlpApplicationType.ResourceGrouping && string.Equals(ResourceName, option.Id?.InstanceId, StringComparison.Ordinal));
PageViewModel.SelectedOption = GetSelectedOption();
PageViewModel.SelectedResource = resource;

Logger.LogDebug("Selected console resource from name {ResourceName}.", ResourceName);
loadingTcs.TrySetResult();
}
}

private SelectViewModel<ResourceTypeDetails> GetSelectedOption()
{
Debug.Assert(_resources is not null);
return _resources.GetApplication(Logger, ResourceName, canSelectGrouping: false, fallback: _noSelection);
}

protected override async Task OnParametersSetAsync()
{
Logger.LogDebug("Initializing console logs view model.");
Expand Down Expand Up @@ -692,10 +695,8 @@ public Task UpdateViewModelFromQueryAsync(ConsoleLogsViewModel viewModel)
{
if (_resources is not null && ResourceName is not null)
{
var selectedOption = _resources.FirstOrDefault(c => string.Equals(ResourceName, c.Id?.InstanceId, StringComparisons.ResourceName)) ?? _noSelection;

viewModel.SelectedOption = selectedOption;
viewModel.SelectedResource = selectedOption.Id?.InstanceId is null ? null : _resourceByName[selectedOption.Id.InstanceId];
viewModel.SelectedOption = GetSelectedOption();
viewModel.SelectedResource = viewModel.SelectedOption.Id?.InstanceId is null ? null : _resourceByName[viewModel.SelectedOption.Id.InstanceId];
viewModel.Status ??= Loc[nameof(Dashboard.Resources.ConsoleLogs.ConsoleLogsLogsNotYetAvailable)];
}
else
Expand All @@ -715,6 +716,10 @@ public string GetUrlFromSerializableViewModel(ConsoleLogsPageState serializable)

public ConsoleLogsPageState ConvertViewModelToSerializable()
{
return new ConsoleLogsPageState(PageViewModel.SelectedResource?.Name);
var selectedResourceName = PageViewModel.SelectedOption.Id is not null
? PageViewModel.SelectedOption.Name
// _noSelection, which doesn't have a resource attached to it
: null;
return new ConsoleLogsPageState(selectedResourceName);
}
}
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@
OnViewDetails="@((buttonId) => ShowResourceDetailsAsync(context.Resource, buttonId))"
Resource="context.Resource"
GetResourceName="GetResourceName"
MaxHighlightedCount="_maxHighlightedCount" />
MaxHighlightedCount="_maxHighlightedCount"
ResourceByName="@_resourceByName" />
</div>
</AspireTemplateColumn>
</ChildContent>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Model/DashboardCommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public async Task ExecuteAsyncCore(ResourceViewModel resource, CommandViewModel
toastParameters.Icon = GetIntentIcon(ToastIntent.Error);
toastParameters.Content.Details = response.ErrorMessage;
toastParameters.PrimaryAction = loc[nameof(Dashboard.Resources.Resources.ResourceCommandToastViewLogs)];
toastParameters.OnPrimaryAction = EventCallback.Factory.Create<ToastResult>(this, () => navigationManager.NavigateTo(DashboardUrls.ConsoleLogsUrl(resource: resource.Name)));
toastParameters.OnPrimaryAction = EventCallback.Factory.Create<ToastResult>(this, () => navigationManager.NavigateTo(DashboardUrls.ConsoleLogsUrl(resource: getResourceName(resource))));
}

if (!toastClosed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Aspire.Dashboard.Model.Otlp;

public static class ApplicationsSelectHelpers
{
public static SelectViewModel<ResourceTypeDetails> GetApplication(this List<SelectViewModel<ResourceTypeDetails>> applications, ILogger logger, string? name, bool canSelectGrouping, SelectViewModel<ResourceTypeDetails> fallback)
public static SelectViewModel<ResourceTypeDetails> GetApplication(this ICollection<SelectViewModel<ResourceTypeDetails>> applications, ILogger logger, string? name, bool canSelectGrouping, SelectViewModel<ResourceTypeDetails> fallback)
{
if (name is null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Model/ResourceMenuItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static void AddMenuItems(
Icon = s_consoleLogsIcon,
OnClick = () =>
{
navigationManager.NavigateTo(DashboardUrls.ConsoleLogsUrl(resource: resource.Name));
navigationManager.NavigateTo(DashboardUrls.ConsoleLogsUrl(resource: getResourceName(resource)));
return Task.CompletedTask;
}
});
Expand Down
18 changes: 18 additions & 0 deletions src/Aspire.Dashboard/Model/ResourceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Aspire.Dashboard.Components.Controls;
using Aspire.Dashboard.Extensions;
Expand Down Expand Up @@ -117,6 +118,23 @@ public static string GetResourceName(ResourceViewModel resource, IDictionary<str

return resource.DisplayName;
}

public static bool TryGetResourceByName(string resourceName, IDictionary<string, ResourceViewModel> resourceByName, [NotNullWhen(true)] out ResourceViewModel? resource)
{
if (resourceByName.TryGetValue(resourceName, out resource))
{
return true;
}

var resourcesWithDisplayName = resourceByName.Values.Where(r => string.Equals(resourceName, r.DisplayName, StringComparisons.ResourceName)).ToList();
if (resourcesWithDisplayName.Count == 1)
{
resource = resourcesWithDisplayName.Single();
return true;
}

return false;
}
}

public sealed class ResourceViewModelNameComparer : IComparer<ResourceViewModel>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Collections.Immutable;
using Aspire.Dashboard.Components.Controls;
using Aspire.Dashboard.Components.Tests.Shared;
Expand Down Expand Up @@ -34,6 +35,7 @@ public async Task ClickMaskAllSwitch_UpdatedResource_MaskChanged()
{
builder.Add(p => p.ShowSpecOnlyToggle, true);
builder.Add(p => p.Resource, resource1);
builder.Add(p => p.ResourceByName, new ConcurrentDictionary<string, ResourceViewModel>([new KeyValuePair<string, ResourceViewModel> (resource1.Name, resource1)]));
});

// Assert
Expand Down Expand Up @@ -117,6 +119,7 @@ public async Task ClickMaskAllSwitch_NewResource_MaskChanged()
{
builder.Add(p => p.ShowSpecOnlyToggle, true);
builder.Add(p => p.Resource, resource1);
builder.Add(p => p.ResourceByName, new ConcurrentDictionary<string, ResourceViewModel>([new KeyValuePair<string, ResourceViewModel> (resource1.Name, resource1)]));
});

// Assert
Expand Down Expand Up @@ -200,6 +203,7 @@ public async Task ClickMaskEnvVarSwitch_UpdatedResource_MaskChanged()
{
builder.Add(p => p.ShowSpecOnlyToggle, true);
builder.Add(p => p.Resource, resource1);
builder.Add(p => p.ResourceByName, new ConcurrentDictionary<string, ResourceViewModel>([new KeyValuePair<string, ResourceViewModel> (resource1.Name, resource1)]));
});

// Assert
Expand Down Expand Up @@ -283,6 +287,7 @@ public async Task ClickMaskEnvVarSwitch_NewResource_MaskChanged()
{
builder.Add(p => p.ShowSpecOnlyToggle, true);
builder.Add(p => p.Resource, resource1);
builder.Add(p => p.ResourceByName, new ConcurrentDictionary<string, ResourceViewModel>([new KeyValuePair<string, ResourceViewModel> (resource1.Name, resource1)]));
});

// Assert
Expand Down