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
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
@using Aspire.Dashboard.Components.CustomIcons;
@using Aspire.Dashboard.Model
@using Microsoft.AspNetCore.Http
@inject IDashboardViewModelService dashboardViewModelService
@inject IResourceService resourceService
@inject IDialogService dialogService
@inherits LayoutComponentBase

<div class="layout">
<FluentHeader>
<div class="header-title">
<FluentAnchor IconStart="@(new AspireIcons.Size32.Logo())" Appearance="Appearance.Stealth" Href="/" Class="logo">
@dashboardViewModelService.ApplicationName Dashboard
@resourceService.ApplicationName Dashboard
</FluentAnchor>
</div>
<div class="header-right">
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@namespace Aspire.Dashboard.Components.Pages
@using Aspire.Dashboard.Model

<PageTitle>@DashboardViewModelService.ApplicationName Console Logs</PageTitle>
<PageTitle>@ResourceService.ApplicationName Console Logs</PageTitle>

<div class="resource-logs-layout">
<h1 class="page-header">Console Logs</h1>
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Aspire.Dashboard.Components.Pages;
public partial class ConsoleLogs : ComponentBase, IAsyncDisposable
{
[Inject]
public required IDashboardViewModelService DashboardViewModelService { get; init; }
public required IResourceService ResourceService { get; init; }
[Inject]
public required IJSRuntime JS { get; init; }
[Inject]
Expand Down Expand Up @@ -41,7 +41,7 @@ protected override void OnInitialized()
{
_status = LogStatus.LoadingResources;

var (snapshot, subscription) = DashboardViewModelService.GetResources();
var (snapshot, subscription) = ResourceService.Subscribe();

foreach (var resource in snapshot)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Metrics.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@using Aspire.Dashboard.Model.Otlp

<PageTitle>@DashboardViewModelService.ApplicationName Metrics</PageTitle>
<PageTitle>@ResourceService.ApplicationName Metrics</PageTitle>

<div class="metrics-layout">
<h1 class="page-header">Metrics</h1>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Metrics.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public partial class Metrics : IDisposable
public required NavigationManager NavigationManager { get; set; }

[Inject]
public required IDashboardViewModelService DashboardViewModelService { get; set; }
public required IResourceService ResourceService { get; set; }

[Inject]
public required ProtectedSessionStorage ProtectedSessionStore { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
@using Aspire.Dashboard.Components.ResourcesGridColumns
@implements IDisposable

<PageTitle>@DashboardViewModelService.ApplicationName Resources</PageTitle>
<PageTitle>@ResourceService.ApplicationName Resources</PageTitle>

<div class="content-layout-with-toolbar">
<FluentToolbar Orientation="Orientation.Horizontal">
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public partial class Resources : ComponentBase, IDisposable
private Dictionary<OtlpApplication, int>? _applicationUnviewedErrorCounts;

[Inject]
public required IDashboardViewModelService DashboardViewModelService { get; init; }
public required IResourceService ResourceService { get; init; }
[Inject]
public required TelemetryRepository TelemetryRepository { get; init; }
[Inject]
Expand Down Expand Up @@ -89,7 +89,7 @@ protected override void OnInitialized()
{
_applicationUnviewedErrorCounts = TelemetryRepository.GetApplicationUnviewedErrorLogsCount();

var (snapshot, subscription) = DashboardViewModelService.GetResources();
var (snapshot, subscription) = ResourceService.Subscribe();

foreach (var resource in snapshot)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/StructuredLogs.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
@using Microsoft.Extensions.Logging
@using System.Web
@inject NavigationManager NavigationManager
@inject IDashboardViewModelService DashboardViewModelService
@inject IResourceService resourceService
@inject IJSRuntime JS
@implements IDisposable

<PageTitle>@DashboardViewModelService.ApplicationName Structured Logs</PageTitle>
<PageTitle>@resourceService.ApplicationName Structured Logs</PageTitle>

<div class="logs-layout">
<h1 class="page-header">Structured Logs</h1>
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/TraceDetail.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
@using Aspire.Dashboard.Otlp.Storage
@using System.Diagnostics
@using System.Globalization
@inject IDashboardViewModelService DashboardViewModelService
@inject IResourceService dashboardService

<PageTitle>@DashboardViewModelService.ApplicationName Traces</PageTitle>
<PageTitle>@dashboardService.ApplicationName Traces</PageTitle>

@if (_trace != null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Dashboard/Components/Pages/Traces.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
@using Aspire.Dashboard.Otlp.Storage
@using System.Web
@inject NavigationManager NavigationManager
@inject IDashboardViewModelService DashboardViewModelService
@inject IResourceService resourceService
@inject IJSRuntime JS
@implements IDisposable

<PageTitle>@DashboardViewModelService.ApplicationName Traces</PageTitle>
<PageTitle>@resourceService.ApplicationName Traces</PageTitle>


<div class="traces-layout">
Expand Down
15 changes: 0 additions & 15 deletions src/Aspire.Dashboard/Model/IDashboardViewModelService.cs

This file was deleted.

21 changes: 21 additions & 0 deletions src/Aspire.Dashboard/Model/IResourceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Dashboard.Model;

/// <summary>
/// Provides data about active resources to external components, such as the dashboard.
/// </summary>
public interface IResourceService
{
string ApplicationName { get; }

/// <summary>
/// Gets the current set of resources and a stream of updates.
/// </summary>
ResourceSubscription Subscribe();
}

public sealed record ResourceSubscription(
List<ResourceViewModel> Snapshot,
IAsyncEnumerable<ResourceChange> Subscription);
12 changes: 4 additions & 8 deletions src/Aspire.Dashboard/Model/ResourceOutgoingPeerResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@ namespace Aspire.Dashboard.Model;

public sealed class ResourceOutgoingPeerResolver : IOutgoingPeerResolver, IAsyncDisposable
{
private readonly IDashboardViewModelService _dashboardViewModelService;
private readonly ConcurrentDictionary<string, ResourceViewModel> _resourceNameMapping = new();
private readonly CancellationTokenSource _watchContainersTokenSource = new();
private readonly Task _watchTask;
private readonly List<ModelSubscription> _subscriptions;
private readonly object _lock = new object();
private readonly List<ModelSubscription> _subscriptions = [];
private readonly object _lock = new();

public ResourceOutgoingPeerResolver(IDashboardViewModelService dashboardViewModelService)
public ResourceOutgoingPeerResolver(IResourceService resourceService)
{
_dashboardViewModelService = dashboardViewModelService;
_subscriptions = new List<ModelSubscription>();

var (snapshot, subscription) = _dashboardViewModelService.GetResources();
var (snapshot, subscription) = resourceService.Subscribe();

foreach (var resource in snapshot)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,38 @@

namespace Aspire.Hosting.Dashboard;

internal sealed class ViewModelProcessor
/// <summary>
/// Builds a collection of resources by integrating incoming changes from a channel,
/// and allowing multiple subscribers to receive the current resource snapshot and future
/// updates.
/// </summary>
internal sealed class ResourceCollection
{
private readonly object _syncLock = new();
private readonly Channel<ResourceChange> _incomingChannel;
private readonly CancellationToken _cancellationToken;
private readonly Dictionary<string, ResourceViewModel> _snapshot = [];
private ImmutableHashSet<Channel<ResourceChange>> _outgoingChannels = [];

public ViewModelProcessor(Channel<ResourceChange> incomingChannel, CancellationToken cancellationToken)
public ResourceCollection(Channel<ResourceChange> incomingChannel, CancellationToken cancellationToken)
{
_incomingChannel = incomingChannel;
_cancellationToken = cancellationToken;

Task.Run(ProcessChanges, cancellationToken);
}

public ViewModelMonitor GetMonitor()
public ResourceSubscription Subscribe()
{
lock (_syncLock)
{
var channel = Channel.CreateUnbounded<ResourceChange>();

ImmutableInterlocked.Update(ref _outgoingChannels, static (set, channel) => set.Add(channel), channel);

return new ViewModelMonitor(
return new ResourceSubscription(
Snapshot: _snapshot.Values.ToList(),
Watch: new ChangeEnumerable(channel, RemoveChannel));
Subscription: new ChangeEnumerable(channel, RemoveChannel));
}

void RemoveChannel(Channel<ResourceChange> channel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@

namespace Aspire.Hosting.Dashboard;

internal sealed partial class DashboardViewModelService : IDashboardViewModelService, IAsyncDisposable
internal sealed partial class ResourceService : IResourceService, IAsyncDisposable
{
private const string AppHostSuffix = ".AppHost";

private readonly string _applicationName;
private readonly KubernetesService _kubernetesService;
private readonly DistributedApplicationModel _applicationModel;
private readonly ILogger _logger;
Expand All @@ -33,7 +30,7 @@ internal sealed partial class DashboardViewModelService : IDashboardViewModelSer

// Private channels, for decoupling producer/consumer and serialising updates.
private readonly Channel<(WatchEventType, string, CustomResource?)> _kubernetesChangesChannel;
private readonly Channel<ResourceChange> _resourceViewModelChangesChannel;
private readonly Channel<ResourceChange> _resourceChannel;

private readonly Dictionary<string, Container> _containersMap = [];
private readonly Dictionary<string, Executable> _executablesMap = [];
Expand All @@ -43,19 +40,19 @@ internal sealed partial class DashboardViewModelService : IDashboardViewModelSer
private readonly ConcurrentDictionary<string, List<EnvVar>> _additionalEnvVarsMap = [];
private readonly HashSet<string> _containersWithTaskStarted = [];

private readonly ViewModelProcessor _resourceViewModelProcessor;
private readonly ResourceCollection _resourceCollection;

public DashboardViewModelService(
public ResourceService(
DistributedApplicationModel applicationModel, KubernetesService kubernetesService, IHostEnvironment hostEnvironment, ILoggerFactory loggerFactory)
{
_applicationModel = applicationModel;
_kubernetesService = kubernetesService;
_applicationName = ComputeApplicationName(hostEnvironment.ApplicationName);
_logger = loggerFactory.CreateLogger<DashboardViewModelService>();
ApplicationName = ComputeApplicationName(hostEnvironment.ApplicationName);
_logger = loggerFactory.CreateLogger<ResourceService>();
_cancellationToken = _cancellationTokenSource.Token;

_kubernetesChangesChannel = Channel.CreateUnbounded<(WatchEventType, string, CustomResource?)>();
_resourceViewModelChangesChannel = Channel.CreateUnbounded<ResourceChange>();
_resourceChannel = Channel.CreateUnbounded<ResourceChange>();

RunWatchTask<Executable>();
RunWatchTask<Service>();
Expand All @@ -64,12 +61,24 @@ public DashboardViewModelService(

Task.Run(ProcessKubernetesChanges);

_resourceViewModelProcessor = new ViewModelProcessor(_resourceViewModelChangesChannel, _cancellationToken);
_resourceCollection = new ResourceCollection(_resourceChannel, _cancellationToken);

static string ComputeApplicationName(string applicationName)
{
const string AppHostSuffix = ".AppHost";

if (applicationName.EndsWith(AppHostSuffix, StringComparison.OrdinalIgnoreCase))
{
applicationName = applicationName[..^AppHostSuffix.Length];
}

return applicationName;
}
}

public string ApplicationName => _applicationName;
public string ApplicationName { get; }

public ViewModelMonitor GetResources() => _resourceViewModelProcessor.GetMonitor();
public ResourceSubscription Subscribe() => _resourceCollection.Subscribe();

private void RunWatchTask<T>()
where T : CustomResource
Expand Down Expand Up @@ -131,7 +140,7 @@ when executable.IsCSharpProject():
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Task to compute view model changes terminated");
_logger.LogError(ex, "Task to compute resource changes terminated");
}
}

Expand Down Expand Up @@ -287,7 +296,7 @@ private async Task ProcessServiceChange(WatchEventType watchEventType, Service s

private async Task WriteChange(ResourceViewModel resourceViewModel, ObjectChangeType changeType = ObjectChangeType.Modified)
{
await _resourceViewModelChangesChannel.Writer.WriteAsync(
await _resourceChannel.Writer.WriteAsync(
new ResourceChange(changeType, resourceViewModel), _cancellationToken)
.ConfigureAwait(false);
}
Expand Down Expand Up @@ -436,7 +445,7 @@ private void FillEndpoints(
var service = _servicesMap.Values.FirstOrDefault(s => s.Metadata.Name == resourceViewModel.Name);
if (service != null)
{
resourceViewModel.Services.Add(new ResourceService(service.Metadata.Name, service.AllocatedAddress, service.AllocatedPort));
resourceViewModel.Services.Add(new Aspire.Dashboard.Model.ResourceService(service.Metadata.Name, service.AllocatedAddress, service.AllocatedPort));
}
}
}
Expand Down Expand Up @@ -612,16 +621,6 @@ public async ValueTask DisposeAsync()
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
}

private static string ComputeApplicationName(string applicationName)
{
if (applicationName.EndsWith(AppHostSuffix, StringComparison.OrdinalIgnoreCase))
{
applicationName = applicationName[..^AppHostSuffix.Length];
}

return applicationName;
}

private static string ComputeExecutableDisplayName(Executable executable)
{
var displayName = executable.Metadata.Name;
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Hosting/Dcp/DcpHostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using Aspire.Dashboard;
using Aspire.Dashboard.Model;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Dashboard;
using Aspire.Hosting.Dcp.Process;
using Aspire.Hosting.Properties;
using Aspire.Hosting.Publishing;
Expand All @@ -21,6 +20,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ResourceService = Aspire.Hosting.Dashboard.ResourceService;

namespace Aspire.Hosting.Dcp;

Expand Down Expand Up @@ -62,7 +62,7 @@ public DcpHostService(DistributedApplicationModel applicationModel,
{
serviceCollection.AddSingleton(_applicationModel);
serviceCollection.AddSingleton(kubernetesService);
serviceCollection.AddScoped<IDashboardViewModelService, DashboardViewModelService>();
serviceCollection.AddScoped<IResourceService, ResourceService>();
});
}
}
Expand Down