Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</FluentBadge>
</div>
<PropertyGrid
TItem="ResourcePropertyViewModel"
TItem="DisplayedResourcePropertyViewModel"
Items="@FilteredResourceProperties"
IsValueMaskedChanged="@OnValueMaskedChanged"
HighlightText="@_filter"
Expand Down
16 changes: 11 additions & 5 deletions src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ public partial class ResourceDetails
[Inject]
public required IJSRuntime JS { get; init; }

[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }

private bool IsSpecOnlyToggleDisabled => !Resource.Environment.All(i => !i.FromSpec) && !GetResourceProperties(ordered: false).Any(static vm => vm.KnownProperty is null);

// NOTE Excludes URLs as they don't expose sensitive items (and enumerating URLs is non-trivial)
private IEnumerable<IPropertyGridItem> SensitiveGridItems => Resource.Environment.Cast<IPropertyGridItem>().Concat(Resource.Properties.Values).Where(static vm => vm.IsValueSensitive);
private IEnumerable<IPropertyGridItem> SensitiveGridItems => Resource.Environment.Cast<IPropertyGridItem>().Concat(_displayedResourcePropertyViewModels).Where(static vm => vm.IsValueSensitive);

private bool _showAll;
private ResourceViewModel? _resource;
private readonly List<DisplayedResourcePropertyViewModel> _displayedResourcePropertyViewModels = new();
private readonly HashSet<string> _unmaskedItemNames = new();

private ColumnResizeLabels _resizeLabels = ColumnResizeLabels.Default;
Expand Down Expand Up @@ -71,7 +75,7 @@ public partial class ResourceDetails
.Where(vm => vm.MatchesFilter(_filter))
.AsQueryable();

internal IQueryable<ResourcePropertyViewModel> FilteredResourceProperties =>
internal IQueryable<DisplayedResourcePropertyViewModel> FilteredResourceProperties =>
GetResourceProperties(ordered: true)
.Where(vm => (_showAll || vm.KnownProperty != null) && vm.MatchesFilter(_filter))
.AsQueryable();
Expand Down Expand Up @@ -108,6 +112,8 @@ protected override void OnParametersSet()
}

_resource = Resource;
_displayedResourcePropertyViewModels.Clear();
_displayedResourcePropertyViewModels.AddRange(_resource.Properties.Select(p => new DisplayedResourcePropertyViewModel(p.Value, Loc, TimeProvider)));

// Collapse details sections when they have no data.
_isUrlsExpanded = GetUrls().Count > 0;
Expand Down Expand Up @@ -216,13 +222,13 @@ private List<DisplayedUrl> GetUrls()
return ResourceUrlHelpers.GetUrls(Resource, includeInternalUrls: true, includeNonEndpointUrls: true);
}

private IEnumerable<ResourcePropertyViewModel> GetResourceProperties(bool ordered)
private IEnumerable<DisplayedResourcePropertyViewModel> GetResourceProperties(bool ordered)
{
var vms = Resource.Properties.Values
var vms = _displayedResourcePropertyViewModels
.Where(vm => vm.Value is { HasNullValue: false } and not { KindCase: Value.KindOneofCase.ListValue, ListValue.Values.Count: 0 });

return ordered
? vms.OrderBy(vm => vm.Priority).ThenBy(vm => vm.Name)
? vms.OrderBy(vm => vm.Priority).ThenBy(vm => vm.DisplayName)
: vms;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void UpdateDetailViewData()
}

Logger.LogInformation("Trace '{TraceId}' has {SpanCount} spans.", _trace.TraceId, _trace.Spans.Count);
_spanWaterfallViewModels = SpanWaterfallViewModel.Create(_trace, new SpanWaterfallViewModel.TraceDetailState(OutgoingPeerResolvers, _collapsedSpanIds));
_spanWaterfallViewModels = SpanWaterfallViewModel.Create(_trace, new SpanWaterfallViewModel.TraceDetailState(_collapsedSpanIds));
_maxDepth = _spanWaterfallViewModels.Max(s => s.Depth);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Traces.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ protected override async Task OnParametersSetAsync()

private void UpdateApplications()
{
_applications = TelemetryRepository.GetApplications();
_applications = TelemetryRepository.GetApplications(includeUninstrumentedPeers: true);
_applicationViewModels = ApplicationsSelectHelpers.CreateApplications(_applications);
_applicationViewModels.Insert(0, _allApplication);
UpdateSubscription();
Expand Down
8 changes: 4 additions & 4 deletions src/Aspire.Dashboard/DashboardWebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public DashboardWebApplication(
}

// Data from the server.
builder.Services.TryAddScoped<IDashboardClient, DashboardClient>();
builder.Services.TryAddSingleton<IDashboardClient, DashboardClient>();
builder.Services.TryAddSingleton<IDashboardClientStatus, DashboardClientStatus>();
builder.Services.TryAddScoped<DashboardCommandExecutor>();

Expand All @@ -244,8 +244,8 @@ public DashboardWebApplication(
builder.Services.AddTransient<OtlpMetricsService>();

builder.Services.AddTransient<TracesViewModel>();
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<IOutgoingPeerResolver, ResourceOutgoingPeerResolver>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<IOutgoingPeerResolver, BrowserLinkOutgoingPeerResolver>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOutgoingPeerResolver, ResourceOutgoingPeerResolver>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOutgoingPeerResolver, BrowserLinkOutgoingPeerResolver>());

builder.Services.AddFluentUIComponents();

Expand All @@ -261,7 +261,7 @@ public DashboardWebApplication(
builder.Services.AddScoped<ILocalStorage, LocalBrowserStorage>();
builder.Services.AddScoped<ISessionStorage, SessionBrowserStorage>();

builder.Services.AddScoped<IKnownPropertyLookup, KnownPropertyLookup>();
builder.Services.AddSingleton<IKnownPropertyLookup, KnownPropertyLookup>();

builder.Services.AddScoped<DimensionManager>();

Expand Down
5 changes: 3 additions & 2 deletions src/Aspire.Dashboard/Model/BrowserLinkOutgoingPeerResolver.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// 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.CodeAnalysis;
using Aspire.Dashboard.Otlp.Model;

namespace Aspire.Dashboard.Model;
Expand All @@ -20,7 +19,7 @@ public void Dispose()
}
}

public bool TryResolvePeerName(KeyValuePair<string, string>[] attributes, [NotNullWhen(true)] out string? name)
public bool TryResolvePeer(KeyValuePair<string, string>[] attributes, out string? name, out ResourceViewModel? matchedResource)
{
// There isn't a good way to identify the HTTP request the BrowserLink middleware makes to
// the IDE to get the script tag. The logic below looks at the host and URL and identifies
Expand Down Expand Up @@ -48,13 +47,15 @@ public bool TryResolvePeerName(KeyValuePair<string, string>[] attributes, [NotNu
if (Guid.TryParse(parts[0], out _) && string.Equals(parts[1], lastSegment, StringComparisons.UrlPath))
{
name = "Browser Link";
matchedResource = null;
return true;
}
}
}
}

name = null;
matchedResource = null;
return false;
}
}
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Model/IOutgoingPeerResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace Aspire.Dashboard.Model;

public interface IOutgoingPeerResolver
{
bool TryResolvePeerName(KeyValuePair<string, string>[] attributes, out string? name);
bool TryResolvePeer(KeyValuePair<string, string>[] attributes, out string? name, out ResourceViewModel? matchedResourced);
IDisposable OnPeerChanges(Func<Task> callback);
}
39 changes: 19 additions & 20 deletions src/Aspire.Dashboard/Model/KnownPropertyLookup.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Localization;
using static Aspire.Dashboard.Resources.Resources;

namespace Aspire.Dashboard.Model;
Expand All @@ -18,43 +17,43 @@ public sealed class KnownPropertyLookup : IKnownPropertyLookup
private readonly List<KnownProperty> _executableProperties;
private readonly List<KnownProperty> _containerProperties;

public KnownPropertyLookup(IStringLocalizer<Resources.Resources> loc)
public KnownPropertyLookup()
{
_resourceProperties =
[
new(KnownProperties.Resource.DisplayName, loc[nameof(ResourcesDetailsDisplayNameProperty)]),
new(KnownProperties.Resource.State, loc[nameof(ResourcesDetailsStateProperty)]),
new(KnownProperties.Resource.StartTime, loc[nameof(ResourcesDetailsStartTimeProperty)]),
new(KnownProperties.Resource.StopTime, loc[nameof(ResourcesDetailsStopTimeProperty)]),
new(KnownProperties.Resource.ExitCode, loc[nameof(ResourcesDetailsExitCodeProperty)]),
new(KnownProperties.Resource.HealthState, loc[nameof(ResourcesDetailsHealthStateProperty)])
new(KnownProperties.Resource.DisplayName, loc => loc[nameof(ResourcesDetailsDisplayNameProperty)]),
new(KnownProperties.Resource.State, loc => loc[nameof(ResourcesDetailsStateProperty)]),
new(KnownProperties.Resource.StartTime, loc => loc[nameof(ResourcesDetailsStartTimeProperty)]),
new(KnownProperties.Resource.StopTime, loc => loc[nameof(ResourcesDetailsStopTimeProperty)]),
new(KnownProperties.Resource.ExitCode, loc => loc[nameof(ResourcesDetailsExitCodeProperty)]),
new(KnownProperties.Resource.HealthState, loc => loc[nameof(ResourcesDetailsHealthStateProperty)])
];

_projectProperties =
[
.. _resourceProperties,
new(KnownProperties.Project.Path, loc[nameof(ResourcesDetailsProjectPathProperty)]),
new(KnownProperties.Executable.Pid, loc[nameof(ResourcesDetailsExecutableProcessIdProperty)]),
new(KnownProperties.Project.Path, loc => loc[nameof(ResourcesDetailsProjectPathProperty)]),
new(KnownProperties.Executable.Pid, loc => loc[nameof(ResourcesDetailsExecutableProcessIdProperty)]),
];

_executableProperties =
[
.. _resourceProperties,
new(KnownProperties.Executable.Path, loc[nameof(ResourcesDetailsExecutablePathProperty)]),
new(KnownProperties.Executable.WorkDir, loc[nameof(ResourcesDetailsExecutableWorkingDirectoryProperty)]),
new(KnownProperties.Executable.Args, loc[nameof(ResourcesDetailsExecutableArgumentsProperty)]),
new(KnownProperties.Executable.Pid, loc[nameof(ResourcesDetailsExecutableProcessIdProperty)]),
new(KnownProperties.Executable.Path, loc => loc[nameof(ResourcesDetailsExecutablePathProperty)]),
new(KnownProperties.Executable.WorkDir, loc => loc[nameof(ResourcesDetailsExecutableWorkingDirectoryProperty)]),
new(KnownProperties.Executable.Args, loc => loc[nameof(ResourcesDetailsExecutableArgumentsProperty)]),
new(KnownProperties.Executable.Pid, loc => loc[nameof(ResourcesDetailsExecutableProcessIdProperty)]),
];

_containerProperties =
[
.. _resourceProperties,
new(KnownProperties.Container.Image, loc[nameof(ResourcesDetailsContainerImageProperty)]),
new(KnownProperties.Container.Id, loc[nameof(ResourcesDetailsContainerIdProperty)]),
new(KnownProperties.Container.Command, loc[nameof(ResourcesDetailsContainerCommandProperty)]),
new(KnownProperties.Container.Args, loc[nameof(ResourcesDetailsContainerArgumentsProperty)]),
new(KnownProperties.Container.Ports, loc[nameof(ResourcesDetailsContainerPortsProperty)]),
new(KnownProperties.Container.Lifetime, loc[nameof(ResourcesDetailsContainerLifetimeProperty)]),
new(KnownProperties.Container.Image, loc => loc[nameof(ResourcesDetailsContainerImageProperty)]),
new(KnownProperties.Container.Id, loc => loc[nameof(ResourcesDetailsContainerIdProperty)]),
new(KnownProperties.Container.Command, loc => loc[nameof(ResourcesDetailsContainerCommandProperty)]),
new(KnownProperties.Container.Args, loc => loc[nameof(ResourcesDetailsContainerArgumentsProperty)]),
new(KnownProperties.Container.Ports, loc => loc[nameof(ResourcesDetailsContainerPortsProperty)]),
new(KnownProperties.Container.Lifetime, loc => loc[nameof(ResourcesDetailsContainerLifetimeProperty)]),
];
}

Expand Down
15 changes: 6 additions & 9 deletions src/Aspire.Dashboard/Model/Otlp/SpanWaterfallViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private void UpdateHidden(bool isParentCollapsed = false)

private readonly record struct SpanWaterfallViewModelState(SpanWaterfallViewModel? Parent, int Depth, bool Hidden);

public sealed record TraceDetailState(IEnumerable<IOutgoingPeerResolver> OutgoingPeerResolvers, List<string> CollapsedSpanIds);
public sealed record TraceDetailState(List<string> CollapsedSpanIds);

public static string GetTitle(OtlpSpan span, List<OtlpApplication> allApplications)
{
Expand Down Expand Up @@ -146,7 +146,7 @@ static SpanWaterfallViewModel CreateViewModel(OtlpSpan span, int depth, bool hid
// A span may indicate a call to another service but the service isn't instrumented.
var hasPeerService = OtlpHelpers.GetPeerAddress(span.Attributes) != null;
var isUninstrumentedPeer = hasPeerService && span.Kind is OtlpSpanKind.Client or OtlpSpanKind.Producer && !span.GetChildSpans().Any();
var uninstrumentedPeer = isUninstrumentedPeer ? ResolveUninstrumentedPeerName(span, state.OutgoingPeerResolvers) : null;
var uninstrumentedPeer = isUninstrumentedPeer ? ResolveUninstrumentedPeerName(span) : null;

var viewModel = new SpanWaterfallViewModel
{
Expand All @@ -173,15 +173,12 @@ static SpanWaterfallViewModel CreateViewModel(OtlpSpan span, int depth, bool hid
}
}

private static string? ResolveUninstrumentedPeerName(OtlpSpan span, IEnumerable<IOutgoingPeerResolver> outgoingPeerResolvers)
private static string? ResolveUninstrumentedPeerName(OtlpSpan span)
{
// Attempt to resolve uninstrumented peer to a friendly name from the span.
foreach (var resolver in outgoingPeerResolvers)
if (span.UninstrumentedPeer?.ApplicationName is { } peerName)
{
if (resolver.TryResolvePeerName(span.Attributes, out var name))
{
return name;
}
// If the span has a peer name, use it.
return peerName;
}

// Fallback to the peer address.
Expand Down
35 changes: 33 additions & 2 deletions src/Aspire.Dashboard/Model/Otlp/TelemetryFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,39 @@ public IEnumerable<OtlpLogEntry> Apply(IEnumerable<OtlpLogEntry> input)
public bool Apply(OtlpSpan span)
{
var fieldValue = OtlpSpan.GetFieldValue(span, Field);
var func = ConditionToFuncString(Condition);
return func(fieldValue, Value);
var isNot = Condition is FilterCondition.NotEqual or FilterCondition.NotContains;

if (!isNot)
{
// Or
if (fieldValue.Value1 != null && IsMatch(fieldValue.Value1, Value, Condition))
{
return true;
}
if (fieldValue.Value2 != null && IsMatch(fieldValue.Value2, Value, Condition))
{
return true;
}
}
else
{
// And
if (fieldValue.Value1 != null && IsMatch(fieldValue.Value1, Value, Condition))
{
if (fieldValue.Value2 != null && IsMatch(fieldValue.Value2, Value, Condition))
{
return true;
}
}
}

return false;

static bool IsMatch(string fieldValue, string filterValue, FilterCondition condition)
{
var func = ConditionToFuncString(condition);
return func(fieldValue, filterValue);
}
}

public bool Equals(TelemetryFilter? other)
Expand Down
Loading