File: Components\Controls\ParameterOverview.razor.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// 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.Extensions;
using Aspire.Dashboard.Model.Interaction;
using Aspire.Dashboard.Model.Markdown;
using Aspire.Dashboard.Utils;
using Aspire.DashboardService.Proto.V1;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
 
namespace Aspire.Dashboard.Components.Controls;
 
public partial class ParameterOverview : ComponentBase
{
    private const string SaveToUserSecretsName = "SaveToUserSecrets";
 
    private bool _showJson;
    private string? _resourceName;
    private ExportResult _jsonExport = null!;
    private MarkdownProcessor _markdownProcessor = null!;
 
    [Inject]
    public required IJSRuntime JS { get; init; }
 
    [Inject]
    public required IDashboardClient DashboardClient { get; init; }
 
    [Parameter, EditorRequired]
    public required ResourceViewModel Resource { get; set; }
 
    [Parameter, EditorRequired]
    public required IDictionary<string, ResourceViewModel> ResourceByName { get; set; }
 
    [Parameter, EditorRequired]
    public required Func<ResourceViewModel, CommandViewModel, Task> OnExecuteCommandAsync { get; set; }
 
    [Parameter, EditorRequired]
    public required Func<ResourceViewModel, CommandViewModel, ExecuteResourceCommandOptions, Task> OnExecuteCommandWithOptionsAsync { get; set; }
 
    [Parameter, EditorRequired]
    public required Func<ResourceViewModel, CommandViewModel, bool> IsCommandExecuting { get; set; }
 
    private string? ParameterValue { get; set; }
    private bool IsSensitive { get; set; }
    private bool IsUnresolved { get; set; }
    private CommandViewModel? SetCommand { get; set; }
    private CommandViewModel? DeleteCommand { get; set; }
    private InputViewModel? ValueInputViewModel { get; set; }
    private InputViewModel? SaveInputViewModel { get; set; }
    private TextVisualizerViewModel JsonViewModel { get; set; } = null!;
    private bool CanShowJson => _showJson;
    private bool IsSetCommandDisabled => DashboardClient.IsReadOnly ||
        SetCommand is null ||
        SetCommand.State == CommandViewModelState.Disabled ||
        IsCommandExecuting(Resource, SetCommand);
    private bool IsDeleteCommandDisabled => DashboardClient.IsReadOnly ||
        DeleteCommand is null ||
        DeleteCommand.State == CommandViewModelState.Disabled ||
        IsCommandExecuting(Resource, DeleteCommand);
    private bool IsValueSubmitDisabled => IsSetCommandDisabled ||
        ValueInputViewModel is null ||
        (ValueInputViewModel.Input.Required &&
            ValueInputViewModel.Input.InputType != InputType.Boolean &&
            string.IsNullOrWhiteSpace(ValueInputViewModel.Value));
 
    protected override void OnInitialized()
    {
        _markdownProcessor = InteractionMarkdownHelper.CreateProcessor(ControlsStringsLoc);
    }
 
    protected override void OnParametersSet()
    {
        var resourceChanged = !string.Equals(_resourceName, Resource.Name, StringComparison.Ordinal);
        _resourceName = Resource.Name;
 
        IsUnresolved = Resource.HasMissingParameterValueState();
        ParameterValue = null;
        IsSensitive = false;
        SetCommand = Resource.Commands.FirstOrDefault(command =>
            string.Equals(command.Name, CommandViewModel.SetParameterCommand, StringComparisons.CommandName) &&
            command.State != CommandViewModelState.Hidden);
        // The host only shows the delete command while the parameter has a value.
        DeleteCommand = Resource.Commands.FirstOrDefault(command =>
            string.Equals(command.Name, CommandViewModel.DeleteParameterCommand, StringComparisons.CommandName) &&
            command.State != CommandViewModelState.Hidden);
        var valueInput = SetCommand?.ArgumentInputs.FirstOrDefault(input =>
            !string.Equals(input.Name, SaveToUserSecretsName, StringComparisons.InteractionInputName));
        var saveInput = SetCommand?.ArgumentInputs.FirstOrDefault(input =>
            string.Equals(input.Name, SaveToUserSecretsName, StringComparisons.InteractionInputName));
 
        if (Resource.Properties.TryGetValue(KnownProperties.Parameter.Value, out var property))
        {
            IsSensitive = property.IsValueSensitive;
            if (!IsUnresolved && property.Value.HasStringValue)
            {
                ParameterValue = property.Value.StringValue;
            }
        }
 
        if (resourceChanged)
        {
            _showJson = false;
            ValueInputViewModel = valueInput is null ? null : new InputViewModel(valueInput);
            SaveInputViewModel = saveInput is null ? null : new InputViewModel(saveInput);
            if (ValueInputViewModel is not null && ParameterValue is not null)
            {
                ValueInputViewModel.Value = ParameterValue;
            }
        }
        else
        {
            if (valueInput is not null)
            {
                ValueInputViewModel?.SetInput(valueInput);
            }
            if (saveInput is not null)
            {
                SaveInputViewModel?.SetInput(saveInput);
            }
        }
 
        _jsonExport = ExportHelpers.GetResourceAsJson(Resource, ResourceByName);
        JsonViewModel = new TextVisualizerViewModel(_jsonExport.Content, indentText: true, DashboardUIHelpers.JsonFormat);
    }
 
    private void ToggleJson()
    {
        _showJson = !_showJson;
    }
 
    private async Task SetValueAsync()
    {
        if (SetCommand is null || ValueInputViewModel is null)
        {
            return;
        }
 
        if (ValueInputViewModel.Input.InputType == InputType.File)
        {
            await OnExecuteCommandAsync(Resource, SetCommand);
            return;
        }
 
        if (IsValueSubmitDisabled)
        {
            return;
        }
 
        var arguments = new Dictionary<string, Value>
        {
            [ValueInputViewModel.Input.Name] = GetArgumentValue(ValueInputViewModel)
        };
        if (SaveInputViewModel is not null)
        {
            arguments[SaveInputViewModel.Input.Name] = Value.ForBool(SaveInputViewModel.IsChecked);
        }
 
        await OnExecuteCommandWithOptionsAsync(Resource, SetCommand, new ExecuteResourceCommandOptions
        {
            Arguments = arguments,
            NonInteractive = true
        });
    }
 
    private async Task DeleteValueAsync()
    {
        if (DeleteCommand is null || IsDeleteCommandDisabled)
        {
            return;
        }
 
        // The delete command asks whether to also remove the value from user secrets, so it runs through the
        // regular command path, which prompts for its arguments.
        await OnExecuteCommandAsync(Resource, DeleteCommand);
    }
 
    private static Value GetArgumentValue(InputViewModel input)
    {
        return input.Input.InputType switch
        {
            InputType.Boolean => Value.ForBool(input.IsChecked),
            InputType.Number when input.NumberValue is { } number => Value.ForNumber(number),
            _ => Value.ForString(input.Value)
        };
    }
 
    private void OnChoiceTextChanged(string? text)
    {
        if (ValueInputViewModel is not { } input || input.SelectedOption?.Name == text)
        {
            return;
        }
 
        text ??= string.Empty;
        input.SelectedOption = input.SelectOptions.FirstOrDefault(option => option.Name == text)
            ?? new SelectViewModel<string> { Id = text, Name = text };
        UpdateChoiceValueState();
    }
 
    private void UpdateChoiceValueState()
    {
        if (ValueInputViewModel is not { } input)
        {
            return;
        }
 
        input.SelectedOption ??= input.SelectOptions.FirstOrDefault(option => option.Id == input.Value)
            ?? new SelectViewModel<string> { Id = input.Value, Name = input.Value };
        input.Value = input.SelectedOption.Id;
    }
 
    private Task DownloadJsonAsync()
    {
        return JS.DownloadFileAsync(_jsonExport.FileName, JsonViewModel.FormattedText);
    }
}