Skip to content

Commit

Permalink
Rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK committed Sep 10, 2024
1 parent 1704a8e commit 9996ae5
Show file tree
Hide file tree
Showing 25 changed files with 497 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
}
else
{
<FluentMenuItem OnClick="() => HandleItemClicked(item)" title="@item.Tooltip">
<FluentMenuItem OnClick="() => HandleItemClicked(item)" title="@item.Tooltip" Disabled="@item.IsDisabled">
@item.Text
@if (item.Icon != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
@using Aspire.Dashboard.Model
@using Microsoft.FluentUI.AspNetCore.Components

@foreach (var highlightedCommand in Commands.Where(c => c.IsHighlighted))
@foreach (var highlightedCommand in Commands.Where(c => c.IsHighlighted && c.State != CommandViewModelState.Hidden))
{
<FluentButton Appearance="Appearance.Lightweight" Title="@(highlightedCommand.DisplayDescription ?? highlightedCommand.DisplayName)" OnClick="@(() => CommandSelected.InvokeAsync(highlightedCommand))">
<FluentButton Appearance="Appearance.Lightweight" Title="@(highlightedCommand.DisplayDescription ?? highlightedCommand.DisplayName)" OnClick="@(() => CommandSelected.InvokeAsync(highlightedCommand))" Disabled="@(highlightedCommand.State == CommandViewModelState.Disabled)">
@if (!string.IsNullOrEmpty(highlightedCommand.IconName) && CommandViewModel.ResolveIconName(highlightedCommand.IconName) is { } icon)
{
<FluentIcon Value="icon" />
<FluentIcon Value="@icon" />
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected override void OnParametersSet()
OnClick = OnConsoleLogs.InvokeAsync
});

var menuCommands = Commands.Where(c => !c.IsHighlighted).ToList();
var menuCommands = Commands.Where(c => !c.IsHighlighted && c.State != CommandViewModelState.Hidden).ToList();
if (menuCommands.Count > 0)
{
_menuItems.Add(new MenuButtonItem { IsDivider = true });
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/Model/MenuButtonItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public class MenuButtonItem
public string? Tooltip { get; set; }
public Icon? Icon { get; set; }
public Func<Task>? OnClick { get; set; }
public bool IsDisabled { get; set; }
}
11 changes: 10 additions & 1 deletion src/Aspire.Dashboard/Model/ResourceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,21 @@ public sealed class CommandViewModel
private static readonly ConcurrentDictionary<string, CustomIcon?> s_iconCache = new();

public string CommandType { get; }
public CommandViewModelState State { get; }
public string DisplayName { get; }
public string? DisplayDescription { get; }
public string? ConfirmationMessage { get; }
public Value? Parameter { get; }
public bool IsHighlighted { get; }
public string? IconName { get; }

public CommandViewModel(string commandType, string displayName, string? displayDescription, string? confirmationMessage, Value? parameter, bool isHighlighted, string? iconName)
public CommandViewModel(string commandType, CommandViewModelState state, string displayName, string? displayDescription, string? confirmationMessage, Value? parameter, bool isHighlighted, string? iconName)
{
ArgumentException.ThrowIfNullOrWhiteSpace(commandType);
ArgumentException.ThrowIfNullOrWhiteSpace(displayName);

CommandType = commandType;
State = state;
DisplayName = displayName;
DisplayDescription = displayDescription;
ConfirmationMessage = confirmationMessage;
Expand Down Expand Up @@ -110,6 +112,13 @@ public CommandViewModel(string commandType, string displayName, string? displayD
}
}

public enum CommandViewModelState
{
Enabled,
Disabled,
Hidden
}

[DebuggerDisplay("Name = {Name}, Value = {Value}, FromSpec = {FromSpec}, IsValueMasked = {IsValueMasked}")]
public sealed class EnvironmentVariableViewModel
{
Expand Down
12 changes: 11 additions & 1 deletion src/Aspire.Dashboard/ResourceService/Partials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,18 @@ ImmutableArray<VolumeViewModel> GetVolumes()
ImmutableArray<CommandViewModel> GetCommands()
{
return Commands
.Select(c => new CommandViewModel(c.CommandType, c.DisplayName, c.HasDisplayDescription ? c.DisplayDescription : null, c.ConfirmationMessage, c.Parameter, c.IsHighlighted, c.HasIconName ? c.IconName : null))
.Select(c => new CommandViewModel(c.CommandType, Map(c.State), c.DisplayName, c.HasDisplayDescription ? c.DisplayDescription : null, c.ConfirmationMessage, c.Parameter, c.IsHighlighted, c.HasIconName ? c.IconName : null))
.ToImmutableArray();
static CommandViewModelState Map(ResourceCommandState state)
{
return state switch
{
ResourceCommandState.Enabled => CommandViewModelState.Enabled,
ResourceCommandState.Disabled => CommandViewModelState.Disabled,
ResourceCommandState.Hidden => CommandViewModelState.Hidden,
_ => throw new InvalidOperationException("Unknown state: " + state),
};
}
}

T ValidateNotNull<T>(T value, [CallerArgumentExpression(nameof(value))] string? expression = null) where T : class
Expand Down
19 changes: 19 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/CustomResourceSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public sealed record CustomResourceSnapshot
/// The volumes that should show up in the dashboard for this resource.
/// </summary>
public ImmutableArray<VolumeSnapshot> Volumes { get; init; } = [];

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable RS0016 // Add public types and members to the declared API
public ImmutableArray<ResourceCommandSnapshot> Commands { get; init; } = [];
#pragma warning restore RS0016 // Add public types and members to the declared API
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}

/// <summary>
Expand Down Expand Up @@ -105,6 +111,19 @@ public sealed record VolumeSnapshot(string? Source, string Target, string MountT
/// <param name="Value">The value of the property.</param>
public sealed record ResourcePropertySnapshot(string Name, object? Value);

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable RS0016 // Add public types and members to the declared API
public sealed record ResourceCommandSnapshot(string Type, ResourceCommandState State, string DisplayName, string? IconName, bool IsHighlighted);

public enum ResourceCommandState
{
Enabled,
Disabled,
Hidden
}
#pragma warning restore RS0016 // Add public types and members to the declared API
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

/// <summary>
/// The set of well known resource states.
/// </summary>
Expand Down
99 changes: 99 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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;

namespace Aspire.Hosting.ApplicationModel;

#pragma warning disable RS0016 // Add public types and members to the declared API
/// <summary>
/// Represents a command annotation for a resource.
/// </summary>
[DebuggerDisplay("Type = {GetType().Name,nq}, Type = {Type}")]
public sealed class ResourceCommandAnnotation : IResourceAnnotation
{
/// <summary>
/// Initializes a new instance of the <see cref="ResourceCommandAnnotation"/> class.
/// </summary>
public ResourceCommandAnnotation(
string type,
string displayName,
Func<UpdateCommandStateContext, ResourceCommandState> updateState,
Func<ExecuteCommandContext, Task> executeCommand,
string? iconName,
bool isHighlighted)
{
ArgumentNullException.ThrowIfNull(type);
ArgumentNullException.ThrowIfNull(displayName);

Type = type;
DisplayName = displayName;
UpdateState = updateState;
ExecuteCommand = executeCommand;
IconName = iconName;
IsHighlighted = isHighlighted;
}

/// <summary>
///
/// </summary>
public string Type { get; }

/// <summary>
///
/// </summary>
public string DisplayName { get; }

/// <summary>
///
/// </summary>
public Func<UpdateCommandStateContext, ResourceCommandState> UpdateState { get; }

/// <summary>
///
/// </summary>
public Func<ExecuteCommandContext, Task> ExecuteCommand { get; }

/// <summary>
///
/// </summary>
public string? IconName { get; }

/// <summary>
///
/// </summary>
public bool IsHighlighted { get; }
}

/// <summary>
///
/// </summary>
public class UpdateCommandStateContext
{
/// <summary>
///
/// </summary>
public required CustomResourceSnapshot ResourceSnapshot { get; init; }
}

/// <summary>
///
/// </summary>
public class ExecuteCommandContext
{
/// <summary>
///
/// </summary>
public required IServiceProvider ServiceProvider { get; init; }

/// <summary>
///
/// </summary>
public required string ResourceName { get; init; }

/// <summary>
///
/// </summary>
public required CancellationToken CancellationToken { get; init; }
}
#pragma warning restore RS0016 // Add public types and members to the declared API
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Threading.Channels;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -187,6 +188,8 @@ public Task PublishUpdateAsync(IResource resource, string resourceId, Func<Custo
{
var previousState = GetCurrentSnapshot(resource, notificationState);

previousState = UpdateCommands(resource, previousState);

var newState = stateFactory(previousState);

notificationState.LastSnapshot = newState;
Expand Down Expand Up @@ -219,8 +222,82 @@ public Task PublishUpdateAsync(IResource resource, string resourceId, Func<Custo
newState.ExitCode, string.Join(", ", newState.EnvironmentVariables.Select(e => $"{e.Name} = {e.Value}")), string.Join(", ", newState.Urls.Select(u => $"{u.Name} = {u.Url}")),
string.Join(", ", newState.Properties.Select(p => $"{p.Name} = {p.Value}")));
}
}

return Task.CompletedTask;
}

/// <summary>
/// Use command annotations to update resource snapshot.
/// </summary>
private static CustomResourceSnapshot UpdateCommands(IResource resource, CustomResourceSnapshot previousState)
{
ImmutableArray<ResourceCommandSnapshot>.Builder? builder = null;

foreach (var annotation in resource.Annotations.OfType<ResourceCommandAnnotation>())
{
var existingCommand = FindByType(previousState.Commands, annotation.Type);

if (existingCommand == null)
{
if (builder == null)
{
builder = ImmutableArray.CreateBuilder<ResourceCommandSnapshot>(previousState.Commands.Length);
builder.AddRange(previousState.Commands);
}

// Command doesn't exist in snapshot. Create from annotation.
builder.Add(CreateCommandFromAnnotation(annotation, previousState));
}
else
{
// Command already exists in snapshot. Update its state based on annotation callback.
var newState = annotation.UpdateState(new UpdateCommandStateContext { ResourceSnapshot = previousState });

if (existingCommand.State != newState)
{
if (builder == null)
{
builder = ImmutableArray.CreateBuilder<ResourceCommandSnapshot>(previousState.Commands.Length);
builder.AddRange(previousState.Commands);
}

var newCommand = existingCommand with
{
State = newState
};

builder.Replace(existingCommand, newCommand);
}
}
}

// Commands are unchanged. Return unchanged state.
if (builder == null)
{
return previousState;
}

return previousState with { Commands = builder.ToImmutable() };

static ResourceCommandSnapshot? FindByType(ImmutableArray<ResourceCommandSnapshot> commands, string type)
{
for (var i = 0; i < commands.Length; i++)
{
if (commands[i].Type == type)
{
return commands[i];
}
}

return null;
}

static ResourceCommandSnapshot CreateCommandFromAnnotation(ResourceCommandAnnotation annotation, CustomResourceSnapshot previousState)
{
var state = annotation.UpdateState(new UpdateCommandStateContext { ResourceSnapshot = previousState });

return Task.CompletedTask;
return new ResourceCommandSnapshot(annotation.Type, state, annotation.DisplayName, annotation.IconName, annotation.IsHighlighted);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Dashboard;
using Aspire.Hosting.Utils;

namespace Aspire.Hosting;
Expand Down Expand Up @@ -151,6 +152,7 @@ public static IResourceBuilder<T> WithImage<T>(this IResourceBuilder<T> builder,
// if the annotation doesn't exist, create it with the given image and add it to the collection
var containerImageAnnotation = new ContainerImageAnnotation() { Image = image, Tag = tag };
builder.Resource.Annotations.Add(containerImageAnnotation);
builder.WithLifeCycleCommands();
return builder;
}

Expand Down
Loading

0 comments on commit 9996ae5

Please sign in to comment.