diff --git a/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs b/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs
index 8eae92ec6e..d38ae21e20 100644
--- a/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs
+++ b/src/Aspire.Dashboard/Components/Controls/ResourceDetails.razor.cs
@@ -74,7 +74,8 @@ protected override void OnInitialized()
new KnownProperty(KnownProperties.Resource.DisplayName, Loc[Resources.Resources.ResourcesDetailsDisplayNameProperty]),
new KnownProperty(KnownProperties.Resource.State, Loc[Resources.Resources.ResourcesDetailsStateProperty]),
new KnownProperty(KnownProperties.Resource.CreateTime, Loc[Resources.Resources.ResourcesDetailsStartTimeProperty]),
- new KnownProperty(KnownProperties.Resource.ExitCode, Loc[Resources.Resources.ResourcesDetailsExitCodeProperty])
+ new KnownProperty(KnownProperties.Resource.ExitCode, Loc[Resources.Resources.ResourcesDetailsExitCodeProperty]),
+ new KnownProperty(KnownProperties.Resource.HealthState, Loc[Resources.Resources.ResourcesDetailsHealthStateProperty])
];
_projectProperties =
[
diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor b/src/Aspire.Dashboard/Components/Pages/Resources.razor
index 3254250af3..fb18f71843 100644
--- a/src/Aspire.Dashboard/Components/Pages/Resources.razor
+++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor
@@ -94,7 +94,7 @@
-
+
diff --git a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
index d5fb9db3ab..9f717dcc6e 100644
--- a/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
+++ b/src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
@@ -11,6 +11,7 @@
using Aspire.Dashboard.Otlp.Storage;
using Aspire.Dashboard.Resources;
using Aspire.Dashboard.Utils;
+using Humanizer;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using Microsoft.FluentUI.AspNetCore.Components;
@@ -331,6 +332,11 @@ private async Task ExecuteResourceCommandAsync(ResourceViewModel resource, Comma
}
}
+ private static string GetResourceStateTooltip(ResourceViewModel resource) =>
+ resource.ShowReadinessState() ?
+ $"{resource.State.Humanize()} ({resource.ReadinessState.Humanize()})"
+ : resource.State.Humanize();
+
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
diff --git a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor
index acccf9e208..3374c26b3f 100644
--- a/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor
+++ b/src/Aspire.Dashboard/Components/ResourcesGridColumns/StateColumnDisplay.razor
@@ -3,6 +3,7 @@
@using Aspire.Dashboard.Otlp.Model
@using Aspire.Dashboard.Otlp.Storage
@using Aspire.Dashboard.Resources
+@using Microsoft.Extensions.Diagnostics.HealthChecks
@using Humanizer
@inject IStringLocalizer Loc
@@ -78,9 +79,20 @@ else if (!string.IsNullOrEmpty(Resource.StateStyle))
}
else
{
-
+ switch (Resource.ReadinessState)
+ {
+ // Unknown state is treated as ready state (we don't know if it's ready or not)
+ case ReadinessState.Unknown or ReadinessState.Ready:
+
+ break;
+ default:
+
+ break;
+ }
}
@if (Resource.HasNoState())
@@ -89,7 +101,14 @@ else
}
else
{
- @Resource.State.Humanize()
+ if (Resource.ShowReadinessState())
+ {
+ @Resource.State.Humanize() (@Resource.ReadinessState.Humanize())
+ }
+ else
+ {
+ @Resource.State.Humanize()
+ }
}
diff --git a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs
index 7932a6216f..a09b1a1735 100644
--- a/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs
+++ b/src/Aspire.Dashboard/Extensions/ResourceViewModelExtensions.cs
@@ -33,4 +33,8 @@ public static bool IsStartingOrBuildingOrWaiting(this ResourceViewModel resource
}
public static bool HasNoState(this ResourceViewModel resource) => string.IsNullOrEmpty(resource.State);
+
+ // We only care about the readiness state if the resource is running
+ public static bool ShowReadinessState(this ResourceViewModel resource) =>
+ resource.IsRunningState() && resource.ReadinessState is ReadinessState.NotReady;
}
diff --git a/src/Aspire.Dashboard/Model/ResourceViewModel.cs b/src/Aspire.Dashboard/Model/ResourceViewModel.cs
index c2d9c45f5c..0de2f04d59 100644
--- a/src/Aspire.Dashboard/Model/ResourceViewModel.cs
+++ b/src/Aspire.Dashboard/Model/ResourceViewModel.cs
@@ -20,6 +20,7 @@ public sealed class ResourceViewModel
public required string Uid { get; init; }
public required string? State { get; init; }
public required string? StateStyle { get; init; }
+ public required ReadinessState ReadinessState { get; init; }
public required DateTime? CreationTimeStamp { get; init; }
public required ImmutableArray Environment { get; init; }
public required ImmutableArray Urls { get; init; }
@@ -60,6 +61,13 @@ public static string GetResourceName(ResourceViewModel resource, IDictionary ReadinessState.Ready,
+ _ => ReadinessState.NotReady,
+ } : ReadinessState.Unknown,
Commands = GetCommands()
};
}
diff --git a/src/Aspire.Dashboard/Resources/Resources.Designer.cs b/src/Aspire.Dashboard/Resources/Resources.Designer.cs
index ade01090fc..4e82deeebf 100644
--- a/src/Aspire.Dashboard/Resources/Resources.Designer.cs
+++ b/src/Aspire.Dashboard/Resources/Resources.Designer.cs
@@ -240,6 +240,15 @@ public static string ResourcesDetailsExitCodeProperty {
}
}
+ ///
+ /// Looks up a localized string similar to Health State.
+ ///
+ public static string ResourcesDetailsHealthStateProperty {
+ get {
+ return ResourceManager.GetString("ResourcesDetailsHealthStateProperty", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Project path.
///
diff --git a/src/Aspire.Dashboard/Resources/Resources.resx b/src/Aspire.Dashboard/Resources/Resources.resx
index 29b1a4789b..431c2269f2 100644
--- a/src/Aspire.Dashboard/Resources/Resources.resx
+++ b/src/Aspire.Dashboard/Resources/Resources.resx
@@ -232,4 +232,7 @@
View console logs
+
+ Health State
+
\ No newline at end of file
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf
index 2f47e1ad30..8443f71a37 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf
@@ -102,6 +102,11 @@
Ukončovací kód
+
+
+ Health State
+
+
Cesta k projektu
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf
index 7b1c36a5ac..087f52c7c8 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf
@@ -102,6 +102,11 @@
Exitcode
+
+
+ Health State
+
+
Projektpfad
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf
index 48cca31b1e..351a734e4f 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf
@@ -102,6 +102,11 @@
Código de salida
+
+
+ Health State
+
+
Ruta de acceso del proyecto
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf
index 265cde3252..7931f273ad 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf
@@ -102,6 +102,11 @@
Code de sortie
+
+
+ Health State
+
+
Chemin de projet
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf
index be434f752a..44a2f1b39e 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf
@@ -102,6 +102,11 @@
Codice di uscita
+
+
+ Health State
+
+
Percorso del progetto
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf
index 587ef7feb6..f3b21a7d23 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf
@@ -102,6 +102,11 @@
終了コード
+
+
+ Health State
+
+
プロジェクト パス
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf
index fd7309ebb7..eb52ba41a8 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf
@@ -102,6 +102,11 @@
종료 코드
+
+
+ Health State
+
+
프로젝트 경로
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf
index 6c03742335..4f11f7ab0a 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf
@@ -102,6 +102,11 @@
Kod zakończenia
+
+
+ Health State
+
+
Ścieżka projektu
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf
index bcf3a79a9e..9524712f71 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf
@@ -102,6 +102,11 @@
Código de saída
+
+
+ Health State
+
+
Caminho do projeto
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf
index b7d808fcec..cc41bc60c1 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf
@@ -102,6 +102,11 @@
Код завершения
+
+
+ Health State
+
+
Путь к проекту
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf
index 325c7817f9..ad13c11d4e 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf
@@ -102,6 +102,11 @@
Çıkış kodu
+
+
+ Health State
+
+
Proje yolu
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf
index 5385d454f1..68a545e384 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf
@@ -102,6 +102,11 @@
退出代码
+
+
+ Health State
+
+
项目路径
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf
index 81cb162ee9..695f989c0e 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf
@@ -102,6 +102,11 @@
結束代碼
+
+
+ Health State
+
+
專案路徑
diff --git a/src/Aspire.Hosting/ApplicationModel/ReplicaInstancesAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/ReplicaInstancesAnnotation.cs
new file mode 100644
index 0000000000..fb83af0312
--- /dev/null
+++ b/src/Aspire.Hosting/ApplicationModel/ReplicaInstancesAnnotation.cs
@@ -0,0 +1,14 @@
+// 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;
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// Keep track of active replicas ids from DCP.
+///
+internal class ReplicaInstancesAnnotation : IResourceAnnotation
+{
+ public ConcurrentDictionary Instances { get; } = [];
+}
diff --git a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs
index 18b179875f..413cdb8457 100644
--- a/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs
+++ b/src/Aspire.Hosting/Dashboard/DashboardServiceData.cs
@@ -3,6 +3,8 @@
using System.Runtime.CompilerServices;
using Aspire.Hosting.ApplicationModel;
+using Aspire.ResourceService.Proto.V1;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
namespace Aspire.Hosting.Dashboard;
@@ -42,7 +44,14 @@ static GenericResourceSnapshot CreateResourceSnapshot(IResource resource, string
Environment = snapshot.EnvironmentVariables,
ExitCode = snapshot.ExitCode,
State = snapshot.State?.Text,
- StateStyle = snapshot.State?.Style
+ StateStyle = snapshot.State?.Style,
+ HealthState = resource.TryGetLastAnnotation(out _) ? snapshot.HealthStatus switch
+ {
+ HealthStatus.Healthy => HealthStateKind.Healthy,
+ HealthStatus.Unhealthy => HealthStateKind.Unhealthy,
+ HealthStatus.Degraded => HealthStateKind.Degraded,
+ _ => HealthStateKind.Unknown,
+ } : null
};
}
diff --git a/src/Aspire.Hosting/Dashboard/ResourceSnapshot.cs b/src/Aspire.Hosting/Dashboard/ResourceSnapshot.cs
index 14fb75f623..f899c0177f 100644
--- a/src/Aspire.Hosting/Dashboard/ResourceSnapshot.cs
+++ b/src/Aspire.Hosting/Dashboard/ResourceSnapshot.cs
@@ -5,6 +5,7 @@
using System.Globalization;
using Aspire.Dashboard.Model;
using Aspire.Hosting.ApplicationModel;
+using Aspire.ResourceService.Proto.V1;
using Google.Protobuf.WellKnownTypes;
namespace Aspire.Hosting.Dashboard;
@@ -22,8 +23,8 @@ internal abstract class ResourceSnapshot
public required DateTime? CreationTimeStamp { get; init; }
public required ImmutableArray Environment { get; init; }
public required ImmutableArray Volumes { get; init; }
-
public required ImmutableArray Urls { get; init; }
+ public required HealthStateKind? HealthState { get; set; }
protected abstract IEnumerable<(string Key, Value Value)> GetProperties();
@@ -38,6 +39,7 @@ internal abstract class ResourceSnapshot
yield return (KnownProperties.Resource.State, State is null ? Value.ForNull() : Value.ForString(State));
yield return (KnownProperties.Resource.ExitCode, ExitCode is null ? Value.ForNull() : Value.ForString(ExitCode.Value.ToString("D", CultureInfo.InvariantCulture)));
yield return (KnownProperties.Resource.CreateTime, CreationTimeStamp is null ? Value.ForNull() : Value.ForString(CreationTimeStamp.Value.ToString("O")));
+ yield return (KnownProperties.Resource.HealthState, HealthState is null ? Value.ForNull() : Value.ForString(HealthState.ToString()));
foreach (var pair in GetProperties())
{
diff --git a/src/Aspire.Hosting/Dashboard/proto/Partials.cs b/src/Aspire.Hosting/Dashboard/proto/Partials.cs
index 9b0665872b..ffe7ee8c9a 100644
--- a/src/Aspire.Hosting/Dashboard/proto/Partials.cs
+++ b/src/Aspire.Hosting/Dashboard/proto/Partials.cs
@@ -20,6 +20,11 @@ public static Resource FromSnapshot(ResourceSnapshot snapshot)
StateStyle = snapshot.StateStyle ?? "",
};
+ if (snapshot.HealthState is HealthStateKind healthState)
+ {
+ resource.HealthState = healthState;
+ }
+
if (snapshot.CreationTimeStamp.HasValue)
{
resource.CreatedAt = Timestamp.FromDateTime(snapshot.CreationTimeStamp.Value.ToUniversalTime());
diff --git a/src/Aspire.Hosting/Dashboard/proto/resource_service.proto b/src/Aspire.Hosting/Dashboard/proto/resource_service.proto
index fe78a23852..e876fbb478 100644
--- a/src/Aspire.Hosting/Dashboard/proto/resource_service.proto
+++ b/src/Aspire.Hosting/Dashboard/proto/resource_service.proto
@@ -170,6 +170,16 @@ message Resource {
// The set of volumes mounted to the resource. Only applies to containers.
repeated Volume volumes = 15;
+
+ // The aggregate health state of the resource
+ optional HealthStateKind HealthState = 16;
+}
+
+enum HealthStateKind {
+ UNKNOWN = 0;
+ HEALTHY = 1;
+ UNHEALTHY = 2;
+ DEGRADED = 3;
}
////////////////////////////////////////////
diff --git a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
index 26c7fdbe13..2d2cb3394e 100644
--- a/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
+++ b/src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
@@ -394,6 +394,11 @@ private async Task ProcessResourceChange(WatchEventType watchEventType, T res
{
_logger.LogTrace("Deleting application model resource {ResourceName} with {ResourceKind} resource {ResourceName}", appModelResource.Name, resourceKind, resource.Metadata.Name);
}
+
+ if (appModelResource.TryGetLastAnnotation(out var replicaAnnotation))
+ {
+ replicaAnnotation.Instances.TryRemove(resource.Metadata.Name, out _);
+ }
}
else
{
@@ -586,6 +591,15 @@ await notificationService.PublishUpdateAsync(appModelResource, cr.Metadata.Name,
private CustomResourceSnapshot ToSnapshot(Container container, CustomResourceSnapshot previous)
{
+ if (container.AppModelResourceName is not null &&
+ _applicationModel.TryGetValue(container.AppModelResourceName, out var appModelResource))
+ {
+ if (appModelResource.TryGetLastAnnotation(out var replicaAnnotation))
+ {
+ replicaAnnotation.Instances.TryAdd(container.Metadata.Name, container.Metadata.Name);
+ }
+ }
+
var containerId = container.Status?.ContainerId;
var urls = GetUrls(container);
var volumes = GetVolumes(container);
@@ -639,6 +653,11 @@ private CustomResourceSnapshot ToSnapshot(Executable executable, CustomResourceS
_applicationModel.TryGetValue(executable.AppModelResourceName, out var appModelResource))
{
projectPath = appModelResource is ProjectResource p ? p.GetProjectMetadata().ProjectPath : null;
+
+ if (appModelResource.TryGetLastAnnotation(out var replicaAnnotation))
+ {
+ replicaAnnotation.Instances.TryAdd(executable.Metadata.Name, executable.Metadata.Name);
+ }
}
var state = executable.AppModelInitialState is "Hidden" ? "Hidden" : executable.Status?.State;
@@ -1021,6 +1040,8 @@ private void PreparePlainExecutables()
foreach (var executable in modelExecutableResources)
{
+ EnsureReplicaInstancesAnnotation(executable);
+
var nameSuffix = GetRandomNameSuffix();
var exeName = GetObjectNameForResource(executable, nameSuffix);
var exePath = executable.Command;
@@ -1051,6 +1072,8 @@ private void PrepareProjectExecutables()
throw new InvalidOperationException("A project resource is missing required metadata"); // Should never happen.
}
+ EnsureReplicaInstancesAnnotation(project);
+
int replicas = project.GetReplicaCount();
var ers = ExecutableReplicaSet.Create(GetObjectNameForResource(project), replicas, "dotnet");
@@ -1102,7 +1125,7 @@ private void PrepareProjectExecutables()
if (!string.IsNullOrEmpty(_distributedApplicationOptions.Configuration))
{
- exeSpec.Args.AddRange(new [] {"-c", _distributedApplicationOptions.Configuration});
+ exeSpec.Args.AddRange(new[] { "-c", _distributedApplicationOptions.Configuration });
}
// We pretty much always want to suppress the normal launch profile handling
@@ -1134,6 +1157,16 @@ private void PrepareProjectExecutables()
}
}
+ private static void EnsureReplicaInstancesAnnotation(IResource resource)
+ {
+ // Make sure we have a replica annotation on the resource.
+ // this is so that we can populate the running instance ids
+ if (!resource.TryGetLastAnnotation(out _))
+ {
+ resource.Annotations.Add(new ReplicaInstancesAnnotation());
+ }
+ }
+
private static void SetInitialResourceState(IResource resource, IAnnotationHolder annotationHolder)
{
// Store the initial state of the resource
@@ -1370,6 +1403,8 @@ private void PrepareContainers()
throw new InvalidOperationException();
}
+ EnsureReplicaInstancesAnnotation(container);
+
var nameSuffix = container.GetContainerLifetimeType() switch
{
ContainerLifetime.Default => GetRandomNameSuffix(),
@@ -1718,9 +1753,9 @@ private static async Task ApplyBuildArgumentsAsync(Container dcpContainerResourc
{
var dcpBuildSecret = new BuildContextSecret
{
- Id = buildSecret.Key,
- Type = "env",
- Value = valueString
+ Id = buildSecret.Key,
+ Type = "env",
+ Value = valueString
};
dcpBuildSecrets.Add(dcpBuildSecret);
}
diff --git a/src/Aspire.Hosting/Health/ResourceNotificationHealthCheckPublisher.cs b/src/Aspire.Hosting/Health/ResourceNotificationHealthCheckPublisher.cs
index 7721e171e6..953510ec11 100644
--- a/src/Aspire.Hosting/Health/ResourceNotificationHealthCheckPublisher.cs
+++ b/src/Aspire.Hosting/Health/ResourceNotificationHealthCheckPublisher.cs
@@ -21,6 +21,17 @@ await resourceNotificationService.PublishUpdateAsync(resource, s => s with
{
HealthStatus = status
}).ConfigureAwait(false);
+
+ if (resource.TryGetLastAnnotation(out var replicaAnnotation))
+ {
+ foreach (var (id, _) in replicaAnnotation.Instances)
+ {
+ await resourceNotificationService.PublishUpdateAsync(resource, id, s => s with
+ {
+ HealthStatus = status
+ }).ConfigureAwait(false);
+ }
+ }
}
}
}
diff --git a/src/Shared/Model/KnownProperties.cs b/src/Shared/Model/KnownProperties.cs
index 9ea5b5ec85..47deaa3ede 100644
--- a/src/Shared/Model/KnownProperties.cs
+++ b/src/Shared/Model/KnownProperties.cs
@@ -22,6 +22,7 @@ public static class Resource
public const string ExitCode = "resource.exitCode";
public const string CreateTime = "resource.createTime";
public const string Source = "resource.source";
+ public const string HealthState = "resource.healthState";
public const string ConnectionString = "resource.connectionString";
}
diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs
index 1abc8fe171..7ffac8f04d 100644
--- a/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs
+++ b/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs
@@ -148,6 +148,7 @@ private static ResourceViewModel CreateResourceViewModel(string appName, KnownRe
State = state?.ToString(),
KnownState = state,
StateStyle = null,
+ ReadinessState = ReadinessState.Ready,
Commands = []
};
}
diff --git a/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/CreateResourceSelectModelsTests.cs b/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/CreateResourceSelectModelsTests.cs
index 472091f935..8ac67a0faa 100644
--- a/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/CreateResourceSelectModelsTests.cs
+++ b/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/CreateResourceSelectModelsTests.cs
@@ -118,6 +118,7 @@ private static ResourceViewModel CreateResourceViewModel(string appName, KnownRe
State = state?.ToString(),
KnownState = state,
StateStyle = null,
+ ReadinessState = ReadinessState.Ready,
Commands = []
};
}
diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs
index ae0d6f6de4..bf85ca18fa 100644
--- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs
+++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/Infrastructure/MockDashboardClient.cs
@@ -27,6 +27,7 @@ public sealed class MockDashboardClient : IDashboardClient
State = "Running",
Uid = Guid.NewGuid().ToString(),
StateStyle = null,
+ ReadinessState = ReadinessState.Ready,
Urls = [],
Volumes = []
};
diff --git a/tests/Aspire.Dashboard.Tests/Model/ResourceEndpointHelpersTests.cs b/tests/Aspire.Dashboard.Tests/Model/ResourceEndpointHelpersTests.cs
index 9c701c65ba..e1eccada84 100644
--- a/tests/Aspire.Dashboard.Tests/Model/ResourceEndpointHelpersTests.cs
+++ b/tests/Aspire.Dashboard.Tests/Model/ResourceEndpointHelpersTests.cs
@@ -203,6 +203,7 @@ private static ResourceViewModel CreateResource(ImmutableArray url
State = null,
KnownState = null,
StateStyle = null,
+ ReadinessState = ReadinessState.Ready,
Commands = []
};
}
diff --git a/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs b/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs
index f2c1e74302..d6cf0e830d 100644
--- a/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs
+++ b/tests/Aspire.Dashboard.Tests/ResourceOutgoingPeerResolverTests.cs
@@ -26,6 +26,7 @@ private static ResourceViewModel CreateResource(string name, string? serviceAddr
Urls = servicePort is null || servicePort is null ? [] : [new UrlViewModel(name, new($"http://{serviceAddress}:{servicePort}"), isInternal: false)],
Volumes = [],
State = null,
+ ReadinessState = ReadinessState.Ready,
KnownState = null,
StateStyle = null,
Commands = []
diff --git a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs
index 69214eebcf..51071e1426 100644
--- a/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs
+++ b/tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs
@@ -212,6 +212,7 @@ await pipeline.ExecuteAsync(async token =>
[Fact]
[RequiresDocker]
+ [ActiveIssue("https://github.com/dotnet/aspire/issues/5785")]
public async Task VerifyWithPgWeb()
{
using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);
@@ -237,6 +238,7 @@ public async Task VerifyWithPgWeb()
client.DefaultRequestHeaders.Add("x-session-id", Guid.NewGuid().ToString());
var response = await client.PostAsync("/api/connect", httpContent);
+ var d = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
}
diff --git a/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs b/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs
index 274f06bfed..666c646ad8 100644
--- a/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs
+++ b/tests/Aspire.Hosting.Tests/Dashboard/ResourcePublisherTests.cs
@@ -193,7 +193,8 @@ private static GenericResourceSnapshot CreateResourceSnapshot(string name)
DisplayName = "",
Urls = [],
Volumes = [],
- Environment = []
+ Environment = [],
+ HealthState = null
};
}
}