Editor/Window/Containers/ContainersPage.cs (472 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.Build;
using AmazonGameLift.Runtime;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Diagnostics;
namespace AmazonGameLift.Editor
{
public enum ContainerSteps
{
ConfigureDCIStep,
CreateECRRepoStep,
PushImageStep,
ConfigureCGDStep,
CreateContainerFleetStep
}
public class ContainersPage : StatefulInput
{
private const string _primaryButtonClassName = "button--primary";
private const int RefreshUIMilliseconds = 2000;
private readonly VisualElement _container;
private readonly VisualElement _launchBarContainer;
private readonly VisualElement questionaireContainer;
private readonly VisualElement profileTableContainer;
private readonly VisualElement questionaireFoldoutContainer;
private readonly VisualElement profileTableFoldoutContainer;
private readonly VisualElement controlBarContainer;
private readonly Foldout questionnaireFoldout;
private readonly Foldout profileTableFoldout;
private readonly VisualElement userInputContainer;
private readonly VisualElement userInputFoldoutContainer;
private readonly Foldout userInputFoldout;
private readonly StateManager _stateManager;
private readonly ContainersUserInput _containersUserInput;
private readonly ContainersUserInput _containersUserInputFoldout;
private readonly ContainersQuestionnaire _containersQuestionnaire;
private readonly ContainersQuestionnaire _containersQuestionnaireFoldout;
private readonly Label userInputFoldoutTitle;
private readonly Label questionnaireFoldoutTitle;
private readonly Label profileTableFoldoutTitle;
private GameLiftClientSettings _gameLiftClientSettings;
private GameLiftClientSettingsLoader _gameLiftClientSettingsLoader;
private StatusBox _statusBox;
private StatusBox _launchStatusBox;
private StatusBox _containersUnsupportedStatusBox;
private StatusBox _missingWslDockerStatusBox;
private StatusBox _deploymentNoticeStatusBox;
private Button _launchServerButton;
private Button _configureClientButton;
private Button _launchClientButton;
private Button _deployContainerFleetButton;
private Button _resetDeploymentButton;
private Button _viewFleetOnConsoleButton;
private Action _startDeploymentAction;
private bool _isDockerInstalled;
private ContainersDeploymentSettings _deploymentSettings;
private readonly Dictionary<ContainerScenarios, ContainerSteps[]> _stepsByQuestionaireScenario = new Dictionary<ContainerScenarios, ContainerSteps[]>
{
{ ContainerScenarios.NoContainerImageNoExistingEcrRepo, new ContainerSteps[] {
ContainerSteps.ConfigureDCIStep,
ContainerSteps.CreateECRRepoStep,
ContainerSteps.PushImageStep,
ContainerSteps.ConfigureCGDStep,
ContainerSteps.CreateContainerFleetStep,
} },
{ ContainerScenarios.NoContainerImageUseExistingEcrRepo, new ContainerSteps[] {
ContainerSteps.ConfigureDCIStep,
ContainerSteps.PushImageStep,
ContainerSteps.ConfigureCGDStep,
ContainerSteps.CreateContainerFleetStep,
} },
{ ContainerScenarios.HaveContainerImageInDocker, new ContainerSteps[] {
ContainerSteps.CreateECRRepoStep,
ContainerSteps.PushImageStep,
ContainerSteps.ConfigureCGDStep,
ContainerSteps.CreateContainerFleetStep,
} },
{ ContainerScenarios.HaveContainerImageInEcr, new ContainerSteps[] {
ContainerSteps.ConfigureCGDStep,
ContainerSteps.CreateContainerFleetStep,
} },
};
private Dictionary<ContainerSteps, ContainerStepComponent> _containerStepsComponentsMap = new Dictionary<ContainerSteps, ContainerStepComponent>();
private List<ProgressBarStepComponent> progressBarSteps = new List<ProgressBarStepComponent>();
public ContainersPage(VisualElement container, StateManager stateManager)
{
_container = container;
_stateManager = stateManager;
var mVisualTreeAsset = UnityEngine.Resources.Load<VisualTreeAsset>("EditorWindow/Pages/ContainersPage");
var uxml = mVisualTreeAsset.Instantiate();
_deploymentSettings = DeploymentSettingsFactory.CreateContainerDeploymentSettings(_stateManager);
container.Add(uxml);
LocalizeText();
SetupStatusBoxes();
_stateManager.OnUserProfileUpdated += UpdateStatusBoxes;
_stateManager.OnUserProfileUpdated += DisableUIIfNotSetup;
_deployContainerFleetButton = container.Q<Button>("DeployContainerFleetButton");
_resetDeploymentButton = container.Q<Button>("ResetDeploymentButton");
_viewFleetOnConsoleButton = container.Q<Button>("AWSConsoleButton");
// Profile table
profileTableContainer = container.Q<VisualElement>("ProfileTableContainer");
//Profile table foldout
profileTableFoldoutTitle = container.Q<Label>("ProfileTableFoldoutTitle");
profileTableFoldoutContainer = container.Q<VisualElement>("ProfileTableFoldoutContainer");
profileTableFoldout = container.Q<Foldout>("ProfileTableFoldout");
InitializeInputFoldout(profileTableFoldout, profileTableFoldoutTitle);
//Questionnaire
questionaireContainer = container.Q<VisualElement>("ContainerQuestionnaireContainer");
_containersQuestionnaire = new ContainersQuestionnaire(questionaireContainer, stateManager, true);
// Questionnaire foldout
questionnaireFoldoutTitle = container.Q<Label>("QuestionnaireFoldoutTitle");
questionaireFoldoutContainer = container.Q<VisualElement>("ContainerQuestionnaireFoldoutContainer");
questionnaireFoldout = container.Q<Foldout>("ContainerQuestionnaireFoldout");
_containersQuestionnaireFoldout = new ContainersQuestionnaire(questionnaireFoldout, stateManager, false);
_containersQuestionnaireFoldout.PopulateContent();
InitializeInputFoldout(questionnaireFoldout, questionnaireFoldoutTitle);
// User input
userInputContainer = container.Q<VisualElement>("ConfigureImageDeploymentContainer");
_containersUserInput = new ContainersUserInput(userInputContainer, stateManager, true);
_containersUserInput.OnValidationEvent += ShowHideDeployButton;
// User input foldout
userInputFoldoutTitle = container.Q<Label>("UserInputFoldoutTitle");
userInputFoldoutContainer = container.Q<VisualElement>("ContainerUserInputFoldoutContainer");
userInputFoldout = container.Q<Foldout>("ContainerUserInputFoldout");
_containersUserInputFoldout = new ContainersUserInput(userInputFoldout, stateManager, false);
_containersUserInputFoldout.PopulateContent();
InitializeInputFoldout(userInputFoldout, userInputFoldoutTitle);
// Control bar
controlBarContainer = container.Q<VisualElement>("ContainerControlBar");
// Launch bar
_launchBarContainer = container.Q<VisualElement>("ContainersLaunchBar");
// Containers Unsupported Warning
_containersUnsupportedStatusBox = container.Q<StatusBox>("RegionUnsupportedStatusBox");
var unsupportedRegionWarning =
string.Format(new TextProvider().Get(Strings.ContainersPageRegionUnsupportedStatusBoxTemplate),
_stateManager.Region);
_containersUnsupportedStatusBox.Show(StatusBox.StatusBoxType.Warning, unsupportedRegionWarning,
externalButtonLink: Urls.SupportedContainersRegions, externalButtonText: "View supported regions");
_isDockerInstalled = IsDockerInstalled();
_missingWslDockerStatusBox = container.Q<StatusBox>("MissingWslDockerStatusBox");
_missingWslDockerStatusBox.Show(StatusBox.StatusBoxType.Warning, Strings.ContainersPageMissingWslDockerStatusBoxText,
externalButtonLink: Urls.InstallDockerEngine, externalButtonText: "Install Docker Engine");
_missingWslDockerStatusBox.HideCloseButton();
_deploymentNoticeStatusBox = container.Q<StatusBox>("DeploymentNoticeStatusBox");
_deploymentNoticeStatusBox.Show(StatusBox.StatusBoxType.Info, Strings.ContainersPageDeploymentNoticeStatusBoxText);
ShowHideNotices();
// Initialize all the steps
foreach (ContainerSteps step in getAllSteps())
{
InitializeStep(step);
}
container.Q<Button>("ManageCredentialsButton")
.RegisterCallback<ClickEvent>(_ => EditorMenu.OpenAccountProfilesTab());
container.Q<Button>("UpdateDeploymentButton")
.RegisterCallback<ClickEvent>(_ => OpenUpdateContainersFleetPopup());
stateManager.OnContainerQuestionnaireScenarioChanged += UpdateGUI;
stateManager.OnContainersDeploymentStatusChanged += ShowHideLaunchBar;
_gameLiftClientSettingsLoader = new GameLiftClientSettingsLoader();
LoadGameLiftClientSettings();
_launchClientButton = container.Q<Button>("StartClientButton");
_launchClientButton.RegisterCallback<ClickEvent>(_ =>
{
_gameLiftClientSettings.ConfigureContainersClientSettings(_stateManager.Region, _deploymentSettings.CurrentStackInfo.ApiGatewayEndpoint, _deploymentSettings.CurrentStackInfo.UserPoolClientId);
_stateManager.OnClientSettingsChanged?.Invoke();
EditorUserBuildSettings.SwitchActiveBuildTarget(NamedBuildTarget.Standalone,
EditorUserBuildSettings.selectedStandaloneTarget);
EditorApplication.EnterPlaymode();
});
DisableUIIfNotSetup();
UpdateGUI();
UpdateStatusBoxes();
}
private void InitializeInputFoldout(Foldout foldout, VisualElement foldoutTitle)
{
foldout.value = false;
// Run the OnClick event once to ensure accurate UI based on state
OnInputFoldoutClick(foldout, foldoutTitle);
foldout.RegisterCallback<ClickEvent>(_ => OnInputFoldoutClick(foldout, foldoutTitle));
}
private void OnInputFoldoutClick(Foldout foldout, VisualElement foldoutTitle)
{
// The extra title is outside the foldout so it should be visible iff the foldout is closed.
ShowHide(foldoutTitle, !foldout.value);
}
private void LoadGameLiftClientSettings()
{
_gameLiftClientSettings = _gameLiftClientSettingsLoader.LoadAsset();
_container.schedule.Execute(() => {
LoadGameLiftClientSettings();
UpdateGUI();
}).StartingIn(RefreshUIMilliseconds);
}
private IEnumerable<ContainerSteps> getAllSteps()
{
return Enum.GetValues(typeof(ContainerSteps)).Cast<ContainerSteps>();
}
private void HideAllSteps()
{
foreach (ContainerSteps step in getAllSteps())
{
Hide(getElementByStep(step));
}
}
private void ShowHideDeployButton()
{
if (_stateManager.ContainerDeploymentInProgress)
{
_deployContainerFleetButton.AddToClassList("hidden");
_resetDeploymentButton.RemoveFromClassList("hidden");
CollapseUserInput();
}
else
{
_deployContainerFleetButton.RemoveFromClassList("hidden");
if (_containersUserInput.AllInputsValid())
{
_deployContainerFleetButton.SetEnabled(true);
}
else
{
_deployContainerFleetButton.SetEnabled(false);
}
_resetDeploymentButton.AddToClassList("hidden");
ShowUserInput();
}
}
private void ShowHideNotices()
{
if (_stateManager.ContainerDeploymentInProgress || !_stateManager.IsBootstrapped() || !_stateManager.IsInContainersRegion())
{
_missingWslDockerStatusBox.AddToClassList("hidden");
_deploymentNoticeStatusBox.AddToClassList("hidden");
}
else
{
if (!_isDockerInstalled && _stateManager.ContainerQuestionnaireScenario != ContainerScenarios.HaveContainerImageInEcr)
{
_missingWslDockerStatusBox.RemoveFromClassList("hidden");
}
else
{
_missingWslDockerStatusBox.AddToClassList("hidden");
}
_deploymentNoticeStatusBox.RemoveFromClassList("hidden");
}
}
private void ShowHideLaunchBar()
{
// show launch bar logic
if (_stateManager.ContainerDeploymentInProgress)
{
_launchBarContainer.RemoveFromClassList("hidden");
}
else
{
_launchBarContainer.AddToClassList("hidden");
}
// enable launch bar logic
if (_stateManager.ContainersDeploymentComplete)
{
_launchBarContainer.SetEnabled(true);
}
else
{
_launchBarContainer.SetEnabled(false);
}
}
private void CollapseUserInput()
{
profileTableContainer.AddToClassList("hidden");
questionaireContainer.AddToClassList("hidden");
userInputContainer.AddToClassList("hidden");
userInputFoldoutContainer.RemoveFromClassList("hidden");
profileTableFoldoutContainer.RemoveFromClassList("hidden");
questionaireFoldoutContainer.RemoveFromClassList("hidden");
_containersUserInputFoldout.PopulateContent();
_containersQuestionnaireFoldout.PopulateContent();
}
private void ShowUserInput()
{
profileTableContainer.RemoveFromClassList("hidden");
questionaireContainer.RemoveFromClassList("hidden");
userInputContainer.RemoveFromClassList("hidden");
userInputFoldoutContainer.AddToClassList("hidden");
profileTableFoldoutContainer.AddToClassList("hidden");
questionaireFoldoutContainer.AddToClassList("hidden");
}
private void InitializeStep(ContainerSteps step)
{
var stepContainer = _container.Q(step.ToString());
switch (step)
{
case ContainerSteps.ConfigureDCIStep:
_containerStepsComponentsMap.Add(step, new ConfigureDCIStep(stepContainer, _stateManager));
return;
case ContainerSteps.CreateECRRepoStep:
_containerStepsComponentsMap.Add(step, new CreateECRRepoStep(stepContainer, _stateManager));
return;
case ContainerSteps.PushImageStep:
if (_isDockerInstalled)
{
_containerStepsComponentsMap.Add(step, new PushImageAutoStep(stepContainer, _stateManager));
}
else
{
_containerStepsComponentsMap.Add(step, new PushImageManualStep(stepContainer, _stateManager));
}
return;
case ContainerSteps.ConfigureCGDStep:
_containerStepsComponentsMap.Add(step, new ConfigureCGDStep(stepContainer, _stateManager, _deploymentSettings));
return;
case ContainerSteps.CreateContainerFleetStep:
_containerStepsComponentsMap.Add(step, new CreateContainerFleetStep(stepContainer, _stateManager, _deploymentSettings));
return;
}
}
private VisualElement getElementByStep(ContainerSteps step)
{
return _container.Q<VisualElement>(step.ToString());
}
protected sealed override void UpdateGUI()
{
ShowHideLaunchBar();
ShowHideDeployButton();
ShowHideNotices();
SetupSteps();
_deployContainerFleetButton.RegisterCallback<ClickEvent>(_ =>
{
// sanity input validation check
if (_containersUserInput.AllInputsValid())
{
questionnaireFoldout.value = false;
userInputFoldout.value = false;
profileTableFoldout.value = false;
_containersUserInput.SaveValues();
_stateManager.ContainerDeploymentInProgress = true;
ShowHideDeployButton();
ShowHideNotices();
_startDeploymentAction();
}
else
{
ShowHideDeployButton();
}
});
_resetDeploymentButton.RegisterCallback<ClickEvent>(_ =>
{
OpenResetPopup();
});
_viewFleetOnConsoleButton.RegisterCallback<ClickEvent>(_ =>
{
Application.OpenURL(string.Format(Urls.AwsContainerFleetsConsole, _stateManager.Region));
});
}
private void DisableUIIfNotSetup()
{
bool bootstrapped = _stateManager.IsBootstrapped();
bool containers = _stateManager.IsInContainersRegion();
questionaireContainer.SetEnabled(bootstrapped && containers);
userInputContainer.SetEnabled(bootstrapped && containers);
controlBarContainer.SetEnabled(bootstrapped && containers);
if (containers)
{
Hide(_containersUnsupportedStatusBox);
}
else
{
Show(_containersUnsupportedStatusBox);
}
}
private void SetupSteps()
{
HideAllSteps();
if (_stateManager.IsBootstrapped() && _stateManager.IsInContainersRegion())
{
var questionnaireScenario = _stateManager.ContainerQuestionnaireScenario;
ContainerSteps[] stepsToShow = _stepsByQuestionaireScenario[questionnaireScenario];
progressBarSteps = new();
foreach (ContainerSteps step in stepsToShow)
{
Show(getElementByStep(step));
progressBarSteps.Add(_containerStepsComponentsMap[step]);
}
_startDeploymentAction = ProgressFlowContainer.SetupSteps(progressBarSteps);
}
}
private void SetupStatusBoxes()
{
_statusBox = _container.Q<StatusBox>("ContainersStatusBox");
}
private bool IsDockerInstalled()
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "docker";
process.StartInfo.Arguments = "-v";
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
try
{
process.Start();
process.WaitForExit();
return process.ExitCode == 0;
}
catch (Exception)
{
// It will hit an exception if docker is not installed or in the PATH.
return false;
}
}
private void OpenResetPopup()
{
var popup = ScriptableObject.CreateInstance<ResetPopup>();
popup.Init(_stateManager.Region);
popup.OnConfirm += ResetDeployment;
popup.ShowModalUtility();
}
private void ResetDeployment()
{
userInputFoldoutTitle.RemoveFromClassList("hidden");
questionnaireFoldoutTitle.RemoveFromClassList("hidden");
profileTableFoldoutTitle.RemoveFromClassList("hidden");
_stateManager.ContainerDeploymentInProgress = false;
_stateManager.ContainersDeploymentComplete = false;
_deploymentSettings = DeploymentSettingsFactory.CreateContainerDeploymentSettings(_stateManager);
ShowHideDeployButton();
ShowHideNotices();
ResetDeploymentSteps();
ResetUserInput();
}
private void OpenUpdateContainersFleetPopup()
{
var popup = ScriptableObject.CreateInstance<ContainersFleetUpdatePopup>();
popup.Init(_stateManager);
popup.OnConfirm += UpdateDeployment;
popup.ShowModalUtility();
}
private void UpdateDeployment()
{
questionnaireFoldout.value = false;
userInputFoldout.value = false;
profileTableFoldout.value = false;
ResetDeploymentSteps();
_stateManager.ContainerDeploymentInProgress = true;
ShowHideDeployButton();
_startDeploymentAction = ProgressFlowContainer.SetupSteps(progressBarSteps);
_startDeploymentAction();
}
private void LocalizeText()
{
var l = new ElementLocalizer(_container);
l.SetElementText("WhatIsContainerImage", Strings.WhatIsContainerImage);
l.SetElementText("DockerDocumentation", Strings.DockerDocumentation);
}
private void UpdateStatusBoxes()
{
if (!_stateManager.IsBootstrapped())
{
_statusBox.Show(StatusBox.StatusBoxType.Warning, Strings.ManagedEC2StatusBoxNotBootstrappedWarning);
}
else
{
_statusBox.Close();
}
}
private void ResetUserInput()
{
// Reset ALL user input EXCEPT for default values
_stateManager.ContainerGameServerBuildPath = null;
_stateManager.ContainerGameServerExecutable = null;
_stateManager.ContainerDockerImageId = null;
_stateManager.ContainerECRRepositoryName = null;
_stateManager.ContainerECRRepositoryUri = null;
_stateManager.ContainerECRImageId = null;
// Reset default values to their defaults
_stateManager.ContainerPortRange = ContainersUserInputValidation.DEFAULT_PORT_RANGE;
_stateManager.ContainerTotalMemory = ContainersUserInputValidation.DEFAULT_MEMORY_LIMIT;
_stateManager.ContainerTotalVcpu = ContainersUserInputValidation.DEFAULT_VCPU_LIMIT;
_stateManager.ContainerImageTag = ContainersUserInputValidation.DEFAULT_IMAGE_TAG;
_stateManager.ContainerGameName = ContainersUserInputValidation.DEFAULT_GAME_NAME;
// Setup inputs in User Input container
_containersUserInput.SetupInputsValues();
}
private void ResetDeploymentSteps()
{
ProgressBarStepComponent _firstStep = progressBarSteps.First();
_firstStep.Reset();
}
}
}