sources/Google.Solutions.IapDesktop.Extensions.Management/ToolWindows/InstanceControlCommands.cs (229 lines of code) (raw):
//
// Copyright 2023 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.Apis.Compute;
using Google.Solutions.IapDesktop.Application.Theme;
using Google.Solutions.IapDesktop.Application.Windows;
using Google.Solutions.IapDesktop.Application.Windows.Dialog;
using Google.Solutions.IapDesktop.Core.ObjectModel;
using Google.Solutions.IapDesktop.Core.ProjectModel;
using Google.Solutions.IapDesktop.Extensions.Management.GuestOs.ActiveDirectory;
using Google.Solutions.IapDesktop.Extensions.Management.Properties;
using Google.Solutions.IapDesktop.Extensions.Management.ToolWindows.ActiveDirectory;
using Google.Solutions.Mvvm.Binding;
using Google.Solutions.Mvvm.Binding.Commands;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Google.Solutions.IapDesktop.Extensions.Management.ToolWindows
{
[Service]
public class InstanceControlCommands
{
public InstanceControlCommands(IServiceProvider serviceProvider)
{
this.ContextMenuStart = new ControlInstanceCommand(
"&Start",
InstanceControlCommand.Start,
serviceProvider)
{
Image = Resources.Start_16,
ActivityText = "Starting VM instance"
};
this.ContextMenuResume = new ControlInstanceCommand(
"&Resume",
InstanceControlCommand.Resume,
serviceProvider)
{
Image = Resources.Start_16,
ActivityText = "Resuming VM instance"
};
this.ContextMenuStop = new ControlInstanceCommand(
"Sto&p",
InstanceControlCommand.Stop,
serviceProvider)
{
Image = Resources.Stop_16,
ActivityText = "Stopping VM instance"
};
this.ContextMenuSuspend = new ControlInstanceCommand(
"Suspe&nd",
InstanceControlCommand.Suspend,
serviceProvider)
{
Image = Resources.Pause_16,
ActivityText = "Suspending VM instance"
};
this.ContextMenuReset = new ControlInstanceCommand(
"Rese&t",
InstanceControlCommand.Reset,
serviceProvider)
{
Image = Resources.Reset_16,
ActivityText = "Resetting VM instance"
};
this.ContextMenuJoinToActiveDirectory = new JoinToActiveDirectoryCommand(
"&Join to Active Directory",
serviceProvider)
{
ActivityText = "Joining to Active Directory"
};
}
//---------------------------------------------------------------------
// Context commands.
//---------------------------------------------------------------------
public IContextCommand<IProjectModelNode> ContextMenuStart { get; }
public IContextCommand<IProjectModelNode> ContextMenuResume { get; }
public IContextCommand<IProjectModelNode> ContextMenuStop { get; }
public IContextCommand<IProjectModelNode> ContextMenuSuspend { get; }
public IContextCommand<IProjectModelNode> ContextMenuReset { get; }
public IContextCommand<IProjectModelNode> ContextMenuJoinToActiveDirectory { get; }
//---------------------------------------------------------------------
// Commands classes.
//---------------------------------------------------------------------
internal class ControlInstanceCommand : MenuCommandBase<IProjectModelNode>
{
private readonly InstanceControlCommand controlCommand;
private readonly IServiceProvider serviceProvider;
public ControlInstanceCommand(
string text,
InstanceControlCommand controlCommand,
IServiceProvider serviceProvider)
: base(text)
{
this.controlCommand = controlCommand;
this.serviceProvider = serviceProvider;
}
public override string Id
{
get => $"ControlInstance.{this.controlCommand}";
}
protected override bool IsAvailable(IProjectModelNode context)
{
return context is IProjectModelInstanceNode;
}
protected override bool IsEnabled(IProjectModelNode context)
{
var instance = (IProjectModelInstanceNode)context;
switch (this.controlCommand)
{
case InstanceControlCommand.Start:
return instance.CanStart;
case InstanceControlCommand.Stop:
return instance.CanStop;
case InstanceControlCommand.Suspend:
return instance.CanSuspend;
case InstanceControlCommand.Resume:
return instance.CanResume;
case InstanceControlCommand.Reset:
return instance.CanReset;
default:
Debug.Fail("Unknown InstanceControlCommand: " + this.controlCommand);
return false;
}
}
public override async Task ExecuteAsync(IProjectModelNode context)
{
var instanceNode = (IProjectModelInstanceNode)context;
var instance = instanceNode.Instance;
var mainWindow = this.serviceProvider.GetService<IMainWindow>();
if (this.serviceProvider.GetService<IConfirmationDialog>()
.Confirm(
mainWindow,
"Are you sure you want to " +
$"{this.controlCommand.ToString().ToLower()} {instance.Name}?",
$"{this.controlCommand} {instance.Name}?",
$"{this.controlCommand} VM instance") != DialogResult.Yes)
{
return;
}
//
// Load data using a job so that the task is retried in case
// of authentication issues.
//
await this.serviceProvider
.GetService<IJobService>()
.RunAsync(
new JobDescription(
$"{this.ActivityText} {instance.Name}...",
JobUserFeedbackType.BackgroundFeedback),
async jobToken =>
{
await instanceNode
.ControlInstanceAsync(
this.controlCommand,
jobToken)
.ConfigureAwait(false);
})
.ConfigureAwait(true); // Back to original (UI) thread.
}
}
internal class JoinToActiveDirectoryCommand : MenuCommandBase<IProjectModelNode>
{
private readonly IServiceProvider serviceProvider;
public JoinToActiveDirectoryCommand(
string text,
IServiceProvider serviceProvider)
: base(text)
{
this.serviceProvider = serviceProvider;
}
protected override bool IsAvailable(IProjectModelNode context)
{
return context is IProjectModelInstanceNode vm &&
vm.OperatingSystem == OperatingSystems.Windows;
}
protected override bool IsEnabled(IProjectModelNode context)
{
return ((IProjectModelInstanceNode)context).IsRunning;
}
public override async Task ExecuteAsync(IProjectModelNode context)
{
var instance = (IProjectModelInstanceNode)context;
var mainWindow = this.serviceProvider.GetService<IMainWindow>();
string domainName;
string? newComputerName;
using (var dialog = this.serviceProvider
.GetService<WindowActivator<JoinView, JoinViewModel, IDialogTheme>>()
.CreateDialog())
{
dialog.ViewModel.ComputerName.Value = instance.DisplayName;
if (dialog.ShowDialog(mainWindow) != DialogResult.OK)
{
return;
}
domainName = dialog.ViewModel.DomainName.Value.Trim();
var computerName = dialog.ViewModel.ComputerName.Value.Trim();
//
// Only specify a "new" computer name if it's different.
//
newComputerName = computerName
.Equals(instance.DisplayName, StringComparison.OrdinalIgnoreCase)
? null
: computerName;
}
//
// Prompt for credentials.
//
if (this.serviceProvider.GetService<ICredentialDialog>()
.PromptForWindowsCredentials(
mainWindow,
new CredentialDialogParameters()
{
Caption = $"Join {instance.DisplayName} to domain",
Message = $"Enter Active Directory credentials for {domainName}.\n\n" +
"The credentials will be used to join the computer to the " +
"domain and will not be saved.",
Package = AuthenticationPackage.Kerberos
},
out var _,
out var credential) != DialogResult.OK)
{
return;
}
//
// Perform join in background job.
//
await this.serviceProvider
.GetService<IJobService>()
.RunAsync(
new JobDescription(
$"Joining {instance.DisplayName} to {domainName}...",
JobUserFeedbackType.BackgroundFeedback),
async jobToken =>
{
await this.serviceProvider
.GetService<IDomainJoinService>()
.JoinDomainAsync(
instance.Instance,
domainName,
newComputerName,
credential!,
jobToken)
.ConfigureAwait(false);
})
.ConfigureAwait(true); // Back to original (UI) thread.
}
}
}
}