-
Notifications
You must be signed in to change notification settings - Fork 887
Show waiting dependency details in dashboard #17089
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
3346ce8
3ac6f77
cec54b4
e9a8191
f5e6cbb
3e277f1
cc42763
0dac353
bdbc841
0fae2e2
3818870
39547d0
86751b5
a1238ec
9c20407
23d6f2a
12452eb
00dcf27
3126b2b
d05e4da
8d13578
0679b59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| @using Aspire.Dashboard.Resources | ||
|
adamint marked this conversation as resolved.
Outdated
|
||
| @using Aspire.Dashboard.Utils | ||
|
|
||
| @if (_waitingResources.Count == 0) | ||
| { | ||
| <FluentHighlighter HighlightedText="@HighlightText" Text="@Value" /> | ||
| } | ||
| else | ||
| { | ||
| <FluentHighlighter HighlightedText="@HighlightText" Text="@_prefix" /> | ||
| @for (var index = 0; index < _waitingResources.Count; index++) | ||
| { | ||
| var waitingResource = _waitingResources[index]; | ||
|
|
||
| if (index > 0) | ||
| { | ||
| <span>, </span> | ||
| } | ||
|
|
||
| if (waitingResource.Resource is { } resource) | ||
| { | ||
| <FluentAnchor Href="@DashboardUrls.ResourcesUrl(resource: resource.Name)" Appearance="Appearance.Hypertext"> | ||
| <FluentHighlighter HighlightedText="@HighlightText" Text="@waitingResource.DisplayName" /> | ||
| </FluentAnchor> | ||
| } | ||
| else | ||
| { | ||
| <FluentHighlighter HighlightedText="@HighlightText" Text="@waitingResource.DisplayName" /> | ||
| } | ||
| } | ||
| <FluentHighlighter HighlightedText="@HighlightText" Text="@_suffix" /> | ||
| } | ||
|
|
||
| @if (StartCommand is not null) | ||
| { | ||
| <span> </span> | ||
| <button type="button" | ||
| class="state-description-action" | ||
| disabled="@IsStartCommandDisabled" | ||
| @onclick="OnStartCommandAsync" | ||
| title="@StartCommandTitle"> | ||
| <FluentIcon Value="@(new Icons.Filled.Size16.Play())" Width="0.8em" Class="state-description-action-icon" /> | ||
| <FluentHighlighter HighlightedText="@HighlightText" Text="@ControlsLoc[nameof(ControlsStrings.ResourceStateDescriptionStartNowAction)]" /> | ||
| </button> | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Dashboard.Model; | ||
| using Aspire.Dashboard.Resources; | ||
| using Microsoft.AspNetCore.Components; | ||
| using Microsoft.Extensions.Localization; | ||
|
|
||
| namespace Aspire.Dashboard.Components.Controls.PropertyValues; | ||
|
|
||
| public partial class ResourceStateDescriptionValue | ||
| { | ||
| private const string WaitingResourcePlaceholder = "{0}"; | ||
| private string _prefix = string.Empty; | ||
| private string _suffix = string.Empty; | ||
| private List<WaitingResource> _waitingResources = []; | ||
| private CommandViewModel? StartCommand { get; set; } | ||
| private bool IsStartCommandDisabled => StartCommand is null || StartCommand.State == CommandViewModelState.Disabled || OnExecuteCommandAsync is null || IsStartCommandExecuting; | ||
| private bool IsStartCommandExecuting => StartCommand is not null && (IsCommandExecuting?.Invoke(Resource, StartCommand) ?? false); | ||
| private string StartCommandTitle => StartCommand?.GetDisplayDescription() ?? StartCommand?.GetDisplayName() ?? string.Empty; | ||
|
|
||
| [Parameter, EditorRequired] | ||
| public required string Value { get; set; } | ||
|
|
||
| [Parameter, EditorRequired] | ||
| public required string HighlightText { get; set; } | ||
|
|
||
| [Parameter, EditorRequired] | ||
| public required ResourceViewModel Resource { get; set; } | ||
|
|
||
| [Parameter, EditorRequired] | ||
| public required IDictionary<string, ResourceViewModel> ResourceByName { get; set; } | ||
|
|
||
| [Parameter] | ||
| public bool ShowHiddenResources { get; set; } | ||
|
|
||
| [Parameter] | ||
| public Func<ResourceViewModel, CommandViewModel, Task>? OnExecuteCommandAsync { get; set; } | ||
|
|
||
| [Parameter] | ||
| public Func<ResourceViewModel, CommandViewModel, bool>? IsCommandExecuting { get; set; } | ||
|
|
||
| [Inject] | ||
| public required IStringLocalizer<Columns> Loc { get; init; } | ||
|
|
||
| [Inject] | ||
| public required IStringLocalizer<ControlsStrings> ControlsLoc { get; init; } | ||
|
|
||
| protected override void OnParametersSet() | ||
| { | ||
| _waitingResources = []; | ||
| _prefix = string.Empty; | ||
| _suffix = string.Empty; | ||
| StartCommand = GetVisibleStartCommand(); | ||
|
|
||
| if (!Resource.TryGetWaitingForDependencies(out var dependencies)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var waitingResourceNames = string.Join(", ", dependencies); | ||
| if (!TrySplitWaitingForFormat(waitingResourceNames, out _prefix, out _suffix)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| foreach (var dependency in dependencies) | ||
| { | ||
| if (TryGetVisibleResource(dependency, out var resource)) | ||
| { | ||
| _waitingResources.Add(new WaitingResource(resource, ResourceViewModel.GetResourceName(resource, ResourceByName))); | ||
| } | ||
| else | ||
| { | ||
| _waitingResources.Add(new WaitingResource(null, dependency)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private bool TrySplitWaitingForFormat(string waitingResourceNames, out string prefix, out string suffix) | ||
| { | ||
| var format = Loc[nameof(Columns.StateColumnResourceWaitingFor)].Value; | ||
| var placeholderIndex = format.IndexOf(WaitingResourcePlaceholder, StringComparison.Ordinal); | ||
|
|
||
| if (placeholderIndex >= 0) | ||
| { | ||
| prefix = format[..placeholderIndex]; | ||
| suffix = format[(placeholderIndex + WaitingResourcePlaceholder.Length)..]; | ||
| return true; | ||
| } | ||
|
|
||
| var resourceNamesIndex = Value.IndexOf(waitingResourceNames, StringComparison.Ordinal); | ||
| if (resourceNamesIndex >= 0) | ||
| { | ||
| prefix = Value[..resourceNamesIndex]; | ||
| suffix = Value[(resourceNamesIndex + waitingResourceNames.Length)..]; | ||
| return true; | ||
| } | ||
|
|
||
| prefix = string.Empty; | ||
| suffix = string.Empty; | ||
| return false; | ||
| } | ||
|
|
||
| private bool TryGetVisibleResource(string resourceName, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out ResourceViewModel? resource) | ||
| { | ||
| if (ResourceViewModel.TryGetResourceByName(resourceName, ResourceByName, out resource) && !resource.IsResourceHidden(ShowHiddenResources)) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| resource = null; | ||
| return false; | ||
| } | ||
|
|
||
| private CommandViewModel? GetVisibleStartCommand() | ||
| { | ||
| foreach (var command in Resource.Commands) | ||
| { | ||
| if (string.Equals(command.Name, CommandViewModel.StartCommand, StringComparisons.CommandName) && | ||
| command.State != CommandViewModelState.Hidden) | ||
| { | ||
| return command; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private Task OnStartCommandAsync() | ||
| { | ||
| if (StartCommand is not { } startCommand || | ||
| IsStartCommandDisabled || | ||
| OnExecuteCommandAsync is not { } onExecuteCommandAsync) | ||
| { | ||
| return Task.CompletedTask; | ||
| } | ||
|
|
||
| return onExecuteCommandAsync(Resource, startCommand); | ||
| } | ||
|
|
||
| private sealed record WaitingResource(ResourceViewModel? Resource, string DisplayName); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| .state-description-action { | ||
| display: inline; | ||
| padding: 0; | ||
| border: 0; | ||
| background: none; | ||
| color: var(--accent-foreground-rest); | ||
| cursor: pointer; | ||
| font: inherit; | ||
| font-weight: inherit; | ||
| line-height: inherit; | ||
| text-decoration: none; | ||
| vertical-align: baseline; | ||
| } | ||
|
|
||
| .state-description-action:hover:not(:disabled) { | ||
| color: var(--accent-foreground-hover); | ||
| } | ||
|
|
||
| .state-description-action:disabled { | ||
| cursor: default; | ||
| opacity: var(--disabled-opacity); | ||
| } | ||
|
|
||
| .state-description-action-icon { | ||
| display: inline; | ||
| margin-inline-end: 0.2em; | ||
| vertical-align: text-bottom; | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,15 +118,15 @@ | |
| <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||
| </resheader> | ||
| <data name="StateColumnResourceExitedUnexpectedly" xml:space="preserve"> | ||
| <value>{0} exited unexpectedly with exit code {1}</value> | ||
| <value>{0} exited unexpectedly with exit code {1}.</value> | ||
| <comment>{0} is a resource type, {1} is a number</comment> | ||
| </data> | ||
| <data name="StateColumnResourceExited" xml:space="preserve"> | ||
| <value>{0} is no longer running</value> | ||
| <value>{0} is no longer running.</value> | ||
| <comment>{0} is a resource type</comment> | ||
| </data> | ||
| <data name="StateColumnResourceFailedToStart" xml:space="preserve"> | ||
| <value>{0} failed to start</value> | ||
| <value>{0} failed to start.</value> | ||
| <comment>{0} is a resource type</comment> | ||
| </data> | ||
| <data name="SourceColumnDisplayCopyCommandToClipboard" xml:space="preserve"> | ||
|
|
@@ -155,13 +155,17 @@ | |
| </data> | ||
| <data name="StateColumnResourceContainerRuntimeUnhealthy" xml:space="preserve"> | ||
| <value>Container runtime was found but appears to be unhealthy. Ensure that it is running. | ||
| For more information, see https://aka.ms/aspire/container-runtime-unhealthy</value> | ||
| For more information, see https://aka.ms/aspire/container-runtime-unhealthy.</value> | ||
| <comment>Contains a new line</comment> | ||
| </data> | ||
| <data name="StateColumnResourceNotStarted" xml:space="preserve"> | ||
| <value>Resource has not started because it's configured to not automatically start.</value> | ||
| <value>Resource is not configured to start automatically.</value> | ||
| </data> | ||
| <data name="StateColumnResourceWaiting" xml:space="preserve"> | ||
| <value>Resource is waiting for other resources to be in a running and healthy state.</value> | ||
| <value>Resource is waiting for dependencies.</value> | ||
| </data> | ||
| <data name="StateColumnResourceWaitingFor" xml:space="preserve"> | ||
| <value>Waiting for dependencies: {0}.</value> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't put fullstop in messages than end with
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: The period after |
||
| <comment>{0} is a comma-separated list of dependency resource names.</comment> | ||
| </data> | ||
| </root> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
????
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removed Kestrel HTTP/2-only default was blocking the new HTTP health check on BasketService. This playground change needs
WithHttpHealthCheck("/health", endpointName: "http")so the dashboard can show healthy/starting dependency details for the waiting-state demo.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but this breaks the basket service. It needs HTTP/2